Compare commits

..

2 Commits

Author SHA1 Message Date
Jess 54f2b93aca windows fix process windows for solvers 2026-05-23 09:58:57 -07:00
Jess 8e01eff876 windows 2026-05-23 09:40:30 -07:00
16 changed files with 681 additions and 129 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ build/ffi/
*.o *.o
wavs/ wavs/
*.a *.a
build/
target/ target/
examples/vids/ examples/vids/
assets/old/ assets/old/

View File

@ -19,3 +19,6 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
libc = "0.2" libc = "0.2"
tiny-skia = "0.11" tiny-skia = "0.11"
roxmltree = "0.20" roxmltree = "0.20"
[target.'cfg(windows)'.build-dependencies]
winresource = "0.1"

19
crates/femm-app/build.rs Normal file
View File

@ -0,0 +1,19 @@
fn main() {
#[cfg(target_os = "windows")]
embed_windows_icon();
}
#[cfg(target_os = "windows")]
fn embed_windows_icon() {
let manifest = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap());
let repo_root = manifest.parent().unwrap().parent().unwrap();
let icon = repo_root.join("build").join("assets").join("femm.ico");
println!("cargo:rerun-if-changed={}", icon.display());
if !icon.is_file() {
println!("cargo:warning=femm.ico not found at {} - skipping icon embed (run scripts/windows/build.ps1 to generate)", icon.display());
return;
}
let mut res = winresource::WindowsResource::new();
res.set_icon(icon.to_str().expect("non-utf8 icon path"));
res.compile().expect("winresource compile failed");
}

View File

@ -1,5 +1,7 @@
//! iced shell entry point for the FEMM 4.2 port. //! iced shell entry point for the FEMM 4.2 port.
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
mod doc_canvas; mod doc_canvas;
mod export; mod export;
mod kinematic; mod kinematic;
@ -380,7 +382,7 @@ impl App {
.pick_file(); .pick_file();
let Some(path) = picked else { return Task::none(); }; let Some(path) = picked else { return Task::none(); };
match svg_io::import_svg(&path) { match svg_io::import_svg(&path) {
Ok(doc) => { Ok((doc, warnings)) => {
self.doc = doc; self.doc = doc;
self.mesh = None; self.mesh = None;
self.solution = None; self.solution = None;
@ -388,8 +390,9 @@ impl App {
self.simulation = None; self.simulation = None;
self.source_label = path.file_name().and_then(|s| s.to_str()).unwrap_or("?").to_string(); self.source_label = path.file_name().and_then(|s| s.to_str()).unwrap_or("?").to_string();
self.source_path = Some(path.clone()); self.source_path = Some(path.clone());
let suffix = if warnings.is_empty() { String::new() } else { format!(" ({})", warnings.join("; ")) };
self.status = format!( self.status = format!(
"imported {}: {} nodes, {} segs, {} arcs, {} labels", "imported {}: {} nodes, {} segs, {} arcs, {} labels{suffix}",
path.display(), path.display(),
self.doc.nodes.len(), self.doc.nodes.len(),
self.doc.segments.len(), self.doc.segments.len(),
@ -2065,7 +2068,7 @@ fn compute_frame(base: FemmDoc, tracks: Vec<kinematic::Track>, frame_idx: i64, d
title: String::from("frame save .pbc failed"), title: String::from("frame save .pbc failed"),
body: format!("{pbc_path:?}\n\n{e}"), body: format!("{pbc_path:?}\n\n{e}"),
})?; })?;
let tri_out = std::process::Command::new(&triangle) let tri_out = quiet_command(&triangle)
.args([ .args([
"-p", "-P", "-e", "-A", "-a", "-z", "-Q", "-I", "-p", "-P", "-e", "-A", "-a", "-z", "-Q", "-I",
&format!("-q{min_angle}"), &format!("-q{min_angle}"),
@ -2104,7 +2107,7 @@ fn compute_frame(base: FemmDoc, tracks: Vec<kinematic::Track>, frame_idx: i64, d
title: String::from("femm-mag-solve binary missing"), title: String::from("femm-mag-solve binary missing"),
body: String::from("run `cargo xtask install`"), body: String::from("run `cargo xtask install`"),
})?; })?;
let solver_out = std::process::Command::new(&helper) let solver_out = quiet_command(&helper)
.arg(&stem_str) .arg(&stem_str)
.output() .output()
.map_err(|e| ErrorReport { .map_err(|e| ErrorReport {
@ -2438,7 +2441,7 @@ fn run_mesh(doc: &FemmDoc) -> Result<Mesh, ErrorReport> {
let triangle = locate_triangle().ok_or_else(|| ErrorReport { let triangle = locate_triangle().ok_or_else(|| ErrorReport {
title: String::from("triangle binary missing"), title: String::from("triangle binary missing"),
body: String::from("expected triangle next to femm.app/Contents/MacOS/triangle, or under build/triangle/triangle, or at $FEMM_TRIANGLE - run scripts/macos/build_triangle.sh"), body: String::from("run `cargo xtask install` (or set $FEMM_TRIANGLE to the triangle executable)"),
})?; })?;
let stem_str = stem.to_str().ok_or_else(|| ErrorReport { let stem_str = stem.to_str().ok_or_else(|| ErrorReport {
@ -2446,7 +2449,7 @@ fn run_mesh(doc: &FemmDoc) -> Result<Mesh, ErrorReport> {
body: format!("{stem:?}"), body: format!("{stem:?}"),
})?; })?;
let min_angle = if doc.min_angle > 0.0 { doc.min_angle } else { MIN_ANGLE_DEG }; let min_angle = if doc.min_angle > 0.0 { doc.min_angle } else { MIN_ANGLE_DEG };
let output = std::process::Command::new(&triangle) let output = quiet_command(&triangle)
.args([ .args([
"-p", "-P", "-e", "-A", "-a", "-z", "-Q", "-I", "-p", "-P", "-e", "-A", "-a", "-z", "-Q", "-I",
&format!("-q{min_angle}"), &format!("-q{min_angle}"),
@ -2483,10 +2486,10 @@ fn run_solve(stem: &Path) -> Result<(), ErrorReport> {
let helper = locate_mag_solver().ok_or_else(|| ErrorReport { let helper = locate_mag_solver().ok_or_else(|| ErrorReport {
title: String::from("femm-mag-solve binary missing"), title: String::from("femm-mag-solve binary missing"),
body: String::from("expected femm-mag-solve next to femm.app/Contents/MacOS/, or under target/release/, or at $FEMM_MAG_SOLVE - run `cargo xtask install`"), body: String::from("run `cargo xtask install` (or set $FEMM_MAG_SOLVE to the femm-mag-solve executable)"),
})?; })?;
let output = std::process::Command::new(&helper) let output = quiet_command(&helper)
.arg(stem_str) .arg(stem_str)
.output() .output()
.map_err(|e| ErrorReport { .map_err(|e| ErrorReport {
@ -2544,16 +2547,24 @@ fn unix_signal(status: &std::process::ExitStatus) -> Option<i32> {
#[cfg(not(unix))] #[cfg(not(unix))]
fn unix_signal(_status: &std::process::ExitStatus) -> Option<i32> { None } fn unix_signal(_status: &std::process::ExitStatus) -> Option<i32> { None }
/// resolves the femm-mag-solve helper path from the bundled .app, the dev target dir, or $FEMM_MAG_SOLVE. fn quiet_command(program: &std::path::Path) -> std::process::Command {
let mut cmd = std::process::Command::new(program);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
cmd.creation_flags(CREATE_NO_WINDOW);
}
cmd
}
/// resolves the femm-mag-solve helper path from the bundled app, the dev target dir, or $FEMM_MAG_SOLVE.
fn locate_mag_solver() -> Option<std::path::PathBuf> { fn locate_mag_solver() -> Option<std::path::PathBuf> {
let exe_name = format!("femm-mag-solve{}", std::env::consts::EXE_SUFFIX);
if let Ok(exe) = std::env::current_exe() { if let Ok(exe) = std::env::current_exe() {
if let Some(parent) = exe.parent() { if let Some(parent) = exe.parent() {
let bundled = parent.join("femm-mag-solve"); let bundled = parent.join(&exe_name);
if bundled.is_file() { return Some(bundled); } if bundled.is_file() { return Some(bundled); }
let dev = parent.join("femm-mag-solve");
if let Ok(canon) = dev.canonicalize() {
if canon.is_file() { return Some(canon); }
}
} }
} }
if let Ok(env_path) = std::env::var("FEMM_MAG_SOLVE") { if let Ok(env_path) = std::env::var("FEMM_MAG_SOLVE") {
@ -2574,15 +2585,16 @@ fn panic_payload_text(payload: &Box<dyn std::any::Any + Send>) -> String {
String::from("panic payload was not a string") String::from("panic payload was not a string")
} }
/// resolves the Triangle binary path from the bundled .app, the dev build dir, or $FEMM_TRIANGLE. /// resolves the Triangle binary path from the bundled app, the dev build dir, or $FEMM_TRIANGLE.
fn locate_triangle() -> Option<std::path::PathBuf> { fn locate_triangle() -> Option<std::path::PathBuf> {
let exe_name = format!("triangle{}", std::env::consts::EXE_SUFFIX);
if let Ok(exe) = std::env::current_exe() { if let Ok(exe) = std::env::current_exe() {
if let Some(parent) = exe.parent() { if let Some(parent) = exe.parent() {
let bundled = parent.join("triangle"); let bundled = parent.join(&exe_name);
if bundled.is_file() { return Some(bundled); } if bundled.is_file() { return Some(bundled); }
let dev = parent.join("../../build/triangle/triangle"); for ancestor in parent.ancestors().take(6) {
if let Ok(canon) = dev.canonicalize() { let dev = ancestor.join("build").join("triangle").join(&exe_name);
if canon.is_file() { return Some(canon); } if dev.is_file() { return Some(dev); }
} }
} }
} }

View File

@ -9,58 +9,42 @@ use femm_doc_mag::{
const NODE_MERGE_TOL: f64 = 1e-3; const NODE_MERGE_TOL: f64 = 1e-3;
/// produces a fresh FemmDoc from the SVG at path, prompting (via rfd) to convert any non-circular ellipse into a circle on ambiguous radii. /// produces a fresh FemmDoc from the SVG at path, returning any non-fatal warnings (bezier approximation, missing materials, etc.) alongside the doc.
pub fn import_svg(path: &Path) -> Result<FemmDoc, String> { pub fn import_svg(path: &Path) -> Result<(FemmDoc, Vec<String>), String> {
let raw = std::fs::read_to_string(path).map_err(|e| format!("read svg: {e}"))?; let raw = std::fs::read_to_string(path).map_err(|e| format!("read svg: {e}"))?;
parse_svg_text(&raw, true) parse_svg_text_with_warnings(&raw)
} }
/// parses an SVG text buffer into a FemmDoc; if prompt_for_ellipse is true and any non-circular ellipse is found, asks the user (rfd dialog) whether to convert. /// parses an SVG text buffer into a FemmDoc; non-circular ellipses are silently converted to circles (average radius). the `_prompt_for_ellipse` argument is accepted for backwards compatibility but ignored.
pub fn parse_svg_text(src: &str, prompt_for_ellipse: bool) -> Result<FemmDoc, String> { pub fn parse_svg_text(src: &str, _prompt_for_ellipse: bool) -> Result<FemmDoc, String> {
let (doc, _) = parse_svg_text_with_warnings(src)?;
Ok(doc)
}
/// like parse_svg_text but also returns a list of human-readable warnings the caller can surface (bezier approximation, shapes missing material, etc.).
pub fn parse_svg_text_with_warnings(src: &str) -> Result<(FemmDoc, Vec<String>), String> {
let xml = Document::parse(src).map_err(|e| format!("svg parse: {e}"))?; let xml = Document::parse(src).map_err(|e| format!("svg parse: {e}"))?;
let mut builder = Builder::new(); let mut builder = Builder::new();
let mut warnings = Warnings::default(); let mut warnings = Warnings::default();
walk(xml.root_element(), Mat3::identity(), &mut builder, &mut warnings)?; walk(xml.root_element(), Mat3::identity(), &mut builder, &mut warnings)?;
let convert_ellipses = if warnings.has_ellipses() && prompt_for_ellipse { let mut messages = Vec::new();
let r = rfd::MessageDialog::new() if warnings.has_ellipses() {
.set_title("Non-circular ellipses detected") messages.push(format!("converted {} non-circular ellipse(s) to circles", warnings.ellipse_count));
.set_description(&format!( }
"Found {} non-circular ellipse(s). Converting to circles (average radius) keeps the mesh clean and avoids the polygon-approximation penalty. Convert?",
warnings.ellipse_count
))
.set_buttons(rfd::MessageButtons::YesNo)
.show();
matches!(r, rfd::MessageDialogResult::Yes)
} else {
true
};
if !warnings.bezier_paths.is_empty() { if !warnings.bezier_paths.is_empty() {
rfd::MessageDialog::new() messages.push(format!("approximated {} bezier curve(s) with polylines", warnings.bezier_paths.len()));
.set_title("Bezier curves approximated")
.set_description(&format!(
"{} bezier curve(s) approximated with straight-line polylines. For smooth curves, replace with circles or arc paths.",
warnings.bezier_paths.len(),
))
.set_buttons(rfd::MessageButtons::Ok)
.show();
} }
if !warnings.no_material_shapes.is_empty() { if !warnings.no_material_shapes.is_empty() {
let ids = warnings.no_material_shapes.join(", "); messages.push(format!(
rfd::MessageDialog::new() "{} shape(s) missing material (add <title>MaterialName</title>): {}",
.set_title("Shapes missing material")
.set_description(&format!(
"{} shape(s) have a recognized class (Block/Segment/Toroid/Disc) but no material name. Add a <title> child like <title>Steel</title> so the importer can label the region. Affected: {ids}",
warnings.no_material_shapes.len(), warnings.no_material_shapes.len(),
)) warnings.no_material_shapes.join(", "),
.set_buttons(rfd::MessageButtons::Ok) ));
.show();
} }
let doc = builder.finalize(convert_ellipses); let doc = builder.finalize(true);
Ok(doc) Ok((doc, messages))
} }
/// emits an SVG containing every node (as `<circle>`), segment (as `<line>`), arc (as `<path>`), and block label (as `<g><circle><text>`); writes y-flipped so it renders right-side-up in any viewer. /// emits an SVG containing every node (as `<circle>`), segment (as `<line>`), arc (as `<path>`), and block label (as `<g><circle><text>`); writes y-flipped so it renders right-side-up in any viewer.

View File

@ -13,20 +13,34 @@ fn main() {
// re-run on changes to any FFI header, build script, or solver source dir. // re-run on changes to any FFI header, build script, or solver source dir.
println!("cargo:rerun-if-changed={}", ffi_dir.display()); println!("cargo:rerun-if-changed={}", ffi_dir.display());
println!("cargo:rerun-if-changed={}", repo_root.join("scripts/macos/build_ffi.sh").display()); println!("cargo:rerun-if-changed={}", repo_root.join("scripts/macos/build_ffi.sh").display());
println!("cargo:rerun-if-changed={}", repo_root.join("scripts/windows/build_ffi.ps1").display());
println!("cargo:rerun-if-changed={}", repo_root.join("scripts/windows/_toolchain.ps1").display());
for engine in ["fkn", "belasolv", "csolv", "hsolv", "liblua", "compat"] { for engine in ["fkn", "belasolv", "csolv", "hsolv", "liblua", "compat"] {
println!("cargo:rerun-if-changed={}", repo_root.join(engine).display()); println!("cargo:rerun-if-changed={}", repo_root.join(engine).display());
} }
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let script = match target_os.as_str() { let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
"macos" => repo_root.join("scripts/macos/build_ffi.sh"), let status = match target_os.as_str() {
other => panic!("femm-sys: no engine build script wired up for target_os={other}"), "macos" => Command::new("bash")
}; .arg(repo_root.join("scripts/macos/build_ffi.sh"))
.status()
let status = Command::new("bash") .expect("failed to invoke bash for engine build script"),
"windows" => {
let script = repo_root.join("scripts/windows/build_ffi.ps1");
Command::new("pwsh")
.args([
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
])
.arg(&script) .arg(&script)
.status() .status()
.expect("failed to invoke bash for engine build script"); .expect("failed to invoke pwsh for engine build script")
}
other => panic!("femm-sys: no engine build script wired up for target_os={other}"),
};
if !status.success() { if !status.success() {
panic!("engine build script exited with {status}"); panic!("engine build script exited with {status}");
} }
@ -35,7 +49,13 @@ fn main() {
for lib in ["femm_mag", "femm_elec", "femm_heat", "femm_curr"] { for lib in ["femm_mag", "femm_elec", "femm_heat", "femm_curr"] {
println!("cargo:rustc-link-lib=static={lib}"); println!("cargo:rustc-link-lib=static={lib}");
} }
println!("cargo:rustc-link-lib=dylib=c++"); match (target_os.as_str(), target_env.as_str()) {
("macos", _) => println!("cargo:rustc-link-lib=dylib=c++"),
("windows", "gnu") => println!("cargo:rustc-link-lib=dylib=stdc++"),
("windows", "gnullvm") => println!("cargo:rustc-link-lib=dylib=c++"),
("windows", "msvc") => { /* MSVC links the C++ runtime automatically */ }
_ => println!("cargo:rustc-link-lib=dylib=stdc++"),
}
let bindings = bindgen::Builder::default() let bindings = bindgen::Builder::default()
.header(manifest.join("wrapper.h").to_str().unwrap()) .header(manifest.join("wrapper.h").to_str().unwrap())

View File

@ -10,6 +10,7 @@
#include <limits.h> #include <limits.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
@ -58,7 +59,7 @@ typedef unsigned long lint32; /* unsigned int with at least 32 bits */
** conversion of pointer to int (for hashing only) ** conversion of pointer to int (for hashing only)
** (the shift removes bits that are usually 0 because of alignment) ** (the shift removes bits that are usually 0 because of alignment)
*/ */
#define IntPoint(p) (((unsigned long)(p)) >> 3) #define IntPoint(p) (((uintptr_t)(p)) >> 3)

View File

@ -0,0 +1,119 @@
# Toolchain detection. Picks a backend appropriate for the host arch:
# x64 host -> MSVC (x64) or MinGW (x86_64), Rust target = host default or x86_64-pc-windows-gnu
# ARM64 host -> CLANGARM64 (msys2) or MinGW (x86_64, emulated, warned), Rust target = aarch64-pc-windows-gnullvm
# Dot-source this file: . "$PSScriptRoot\_toolchain.ps1"
$ErrorActionPreference = 'Stop'
function Get-HostArch {
if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { return 'arm64' }
return 'x64'
}
function Initialize-VsEnv {
param([string]$VcVars)
$tmp = New-TemporaryFile
try {
cmd /c "`"$VcVars`" x64 >NUL && set" | Out-File -FilePath $tmp -Encoding ascii
foreach ($line in Get-Content $tmp) {
if ($line -match '^([^=]+)=(.*)$') {
Set-Item -Path "env:$($Matches[1])" -Value $Matches[2]
}
}
} finally {
Remove-Item $tmp -Force -ErrorAction SilentlyContinue
}
}
function Find-VsInstall {
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
if (-not (Test-Path $vswhere)) {
$vswhere = Join-Path $env:ProgramFiles 'Microsoft Visual Studio\Installer\vswhere.exe'
}
if (-not (Test-Path $vswhere)) { return $null }
$install = & $vswhere -latest -products * `
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
-property installationPath 2>$null
if (-not $install) { return $null }
$vcvars = Join-Path $install 'VC\Auxiliary\Build\vcvars64.bat'
if (Test-Path $vcvars) { return $vcvars }
return $null
}
function Find-MingwBin {
if ($env:MINGW_PREFIX -and (Test-Path (Join-Path $env:MINGW_PREFIX 'g++.exe'))) {
return $env:MINGW_PREFIX
}
$gxx = Get-Command g++.exe -ErrorAction SilentlyContinue
if ($gxx) { return (Split-Path $gxx.Source -Parent) }
foreach ($p in @(
'C:\msys64\mingw64\bin',
'C:\msys64\ucrt64\bin',
'C:\mingw64\bin',
'C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin'
)) {
if (Test-Path (Join-Path $p 'g++.exe')) { return $p }
}
return $null
}
function Find-ClangArm64Bin {
if ($env:CLANGARM64_PREFIX -and (Test-Path (Join-Path $env:CLANGARM64_PREFIX 'clang++.exe'))) {
return $env:CLANGARM64_PREFIX
}
foreach ($p in @('C:\msys64\clangarm64\bin')) {
if (Test-Path (Join-Path $p 'clang++.exe')) { return $p }
}
return $null
}
function Get-Toolchain {
$hostArch = Get-HostArch
if ($hostArch -eq 'arm64') {
$clangBin = Find-ClangArm64Bin
if ($clangBin) {
return [pscustomobject]@{
Kind = 'clangarm64'
Ready = $true
Bin = $clangBin
CargoTarget = 'aarch64-pc-windows-gnullvm'
}
}
$mingw = Find-MingwBin
if ($mingw) {
Write-Warning "ARM64 host but only x86_64 MinGW found; resulting binaries will run under emulation and may crash at startup. Install msys2 CLANGARM64 (pacman -S mingw-w64-clang-aarch64-toolchain)."
return [pscustomobject]@{ Kind = 'mingw'; Ready = $true; MingwBin = $mingw; CargoTarget = 'x86_64-pc-windows-gnu' }
}
throw "No ARM64 C++ toolchain found. Install msys2 CLANGARM64 (pacman -S mingw-w64-clang-aarch64-toolchain) or Visual Studio ARM64 C++ tools."
}
if (Get-Command cl.exe -ErrorAction SilentlyContinue) {
return [pscustomobject]@{ Kind = 'msvc'; Ready = $true; VcVars = $null; CargoTarget = '' }
}
$vcvars = Find-VsInstall
if ($vcvars) {
return [pscustomobject]@{ Kind = 'msvc'; Ready = $false; VcVars = $vcvars; CargoTarget = '' }
}
$mingw = Find-MingwBin
if ($mingw) {
return [pscustomobject]@{ Kind = 'mingw'; Ready = $true; MingwBin = $mingw; CargoTarget = 'x86_64-pc-windows-gnu' }
}
throw "No C++ toolchain found. Install Visual Studio Build Tools (with the C++ workload) or MinGW-w64."
}
function Initialize-Toolchain {
param([pscustomobject]$Toolchain)
if ($Toolchain.Kind -eq 'msvc' -and -not $Toolchain.Ready) {
Write-Host "Importing MSVC environment from $($Toolchain.VcVars)"
Initialize-VsEnv -VcVars $Toolchain.VcVars
}
if ($Toolchain.Kind -eq 'mingw' -and $Toolchain.MingwBin -and ($env:PATH -notlike "*$($Toolchain.MingwBin)*")) {
$env:PATH = "$($Toolchain.MingwBin);$env:PATH"
}
if ($Toolchain.Kind -eq 'clangarm64' -and $Toolchain.Bin -and ($env:PATH -notlike "*$($Toolchain.Bin)*")) {
$env:PATH = "$($Toolchain.Bin);$env:PATH"
}
}

74
scripts/windows/build.ps1 Normal file
View File

@ -0,0 +1,74 @@
#requires -Version 7
# Top-level Windows release build. Mirrors scripts/macos/build.sh.
$ErrorActionPreference = 'Stop'
$Root = Resolve-Path (Join-Path $PSScriptRoot '..\..')
Set-Location $Root
if (-not $IsWindows) {
Write-Error "wrong platform: $($PSVersionTable.OS) - use cargo xtask build"
exit 1
}
. (Join-Path $PSScriptRoot '_toolchain.ps1')
$tc = Get-Toolchain
Initialize-Toolchain $tc
Write-Host "toolchain: $($tc.Kind)"
$env:PROFILE = 'release'
$Build = Join-Path $Root 'build'
$BinDir = Join-Path $Build 'bin\femm'
Write-Host 'Building Triangle...'
& (Join-Path $PSScriptRoot 'build_triangle.ps1')
$tri = Join-Path $Root 'build\triangle\triangle.exe'
if (-not (Test-Path $tri)) { Write-Error "triangle binary missing at $tri"; exit 1 }
# femm-app/build.rs embeds this into femm.exe, so it must exist before cargo build.
$svg = Join-Path $Root 'assets\femm.svg'
$icoOut = Join-Path $Build 'assets\femm.ico'
New-Item -ItemType Directory -Force -Path (Split-Path $icoOut) | Out-Null
if (Test-Path $svg) {
$magick = Get-Command magick.exe -ErrorAction SilentlyContinue
if ($magick) {
& $magick.Source -background none $svg -define icon:auto-resize=16,32,48,64,128,256 $icoOut
if ($LASTEXITCODE -ne 0) { Write-Warning "magick failed to build $icoOut ($LASTEXITCODE)" }
} else {
Write-Warning 'magick not found - femm.exe will be built without an embedded icon'
}
}
Write-Host 'Building Rust workspace (release)...'
$cargoArgs = @('build','--release','-p','femm-app','-p','femm-mag-solve')
if ($tc.CargoTarget) {
$cargoArgs += @('--target',$tc.CargoTarget)
}
& cargo @cargoArgs
if ($LASTEXITCODE -ne 0) { throw "cargo build failed ($LASTEXITCODE)" }
$targetDir = if ($tc.CargoTarget) {
Join-Path $Root "target\$($tc.CargoTarget)\release"
} else {
Join-Path $Root 'target\release'
}
$bin = Join-Path $targetDir 'femm.exe'
$solve = Join-Path $targetDir 'femm-mag-solve.exe'
foreach ($p in @($bin, $solve)) {
if (-not (Test-Path $p)) { Write-Error "missing build output: $p"; exit 1 }
}
if (Test-Path $BinDir) { Remove-Item $BinDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
Copy-Item $bin (Join-Path $BinDir 'femm.exe')
Copy-Item $solve (Join-Path $BinDir 'femm-mag-solve.exe')
Copy-Item $tri (Join-Path $BinDir 'triangle.exe')
if (Test-Path $icoOut) {
Copy-Item $icoOut (Join-Path $BinDir 'femm.ico')
}
Write-Host "Built: $BinDir"

View File

@ -0,0 +1,227 @@
#requires -Version 7
# Per-engine static archives for mag/elec/heat/curr.
# MSVC: cl /c + lib /OUT (no inter-engine symbol localization yet).
# MinGW: g++ -c + ld -r + objcopy --keep-global-symbols + ar — mirrors scripts/macos/build_ffi.sh.
$ErrorActionPreference = 'Stop'
$Root = Resolve-Path (Join-Path $PSScriptRoot '..\..')
$Build = if ($env:BUILD) { $env:BUILD } else { Join-Path $Root 'build\ffi' }
. (Join-Path $PSScriptRoot '_toolchain.ps1')
$tc = Get-Toolchain
Initialize-Toolchain $tc
$buildProfile = if ($env:PROFILE) { $env:PROFILE } else { 'release' }
foreach ($d in @('fkn','liblua','belasolv','csolv','hsolv','ffi','exports')) {
New-Item -ItemType Directory -Force -Path (Join-Path $Build $d) | Out-Null
}
function Compile-Many {
param(
[string[]]$Includes,
[string]$OutDir,
[string[]]$Sources
)
foreach ($src in $Sources) {
$base = [IO.Path]::GetFileNameWithoutExtension($src)
if ($tc.Kind -eq 'msvc') {
$obj = Join-Path $OutDir "$base.obj"
$cxxflags = if ($buildProfile -eq 'release') {
@('/nologo','/std:c++17','/EHs-c-','/GR-','/O2','/DNDEBUG','/W0','/D_CRT_SECURE_NO_WARNINGS')
} else {
@('/nologo','/std:c++17','/EHs-c-','/GR-','/Od','/Zi','/W0','/D_CRT_SECURE_NO_WARNINGS')
}
$incFlags = $Includes | ForEach-Object { "/I$_" }
& cl.exe @cxxflags @incFlags /c $src "/Fo:$obj" | Out-Null
if ($LASTEXITCODE -ne 0) { throw "cl.exe failed on $src ($LASTEXITCODE)" }
} else {
$obj = Join-Path $OutDir "$base.o"
$cxxflags = if ($buildProfile -eq 'release') {
@('-std=c++17','-fno-exceptions','-fno-rtti','-fpermissive','-O3','-DNDEBUG','-w')
} else {
@('-std=c++17','-fno-exceptions','-fno-rtti','-fpermissive','-O0','-g','-w')
}
$incFlags = $Includes | ForEach-Object { "-I$_" }
$cxx = if ($tc.Kind -eq 'clangarm64') { Join-Path $tc.Bin 'clang++.exe' } else { 'g++.exe' }
& $cxx @cxxflags @incFlags -c $src -o $obj
if ($LASTEXITCODE -ne 0) { throw "$cxx failed on $src ($LASTEXITCODE)" }
}
}
}
# liblua — compiled against fkn's complex.h
Compile-Many `
-Includes @((Join-Path $Root 'fkn'), (Join-Path $Root 'liblua'), (Join-Path $Root 'compat')) `
-OutDir (Join-Path $Build 'liblua') `
-Sources @(
(Join-Path $Root 'liblua\lapi.cpp'),
(Join-Path $Root 'liblua\lauxlib.cpp'),
(Join-Path $Root 'liblua\lbaselib.cpp'),
(Join-Path $Root 'liblua\lcode.cpp'),
(Join-Path $Root 'liblua\ldblib.cpp'),
(Join-Path $Root 'liblua\ldebug.cpp'),
(Join-Path $Root 'liblua\ldo.cpp'),
(Join-Path $Root 'liblua\lfunc.cpp'),
(Join-Path $Root 'liblua\lgc.cpp'),
(Join-Path $Root 'liblua\liolib.cpp'),
(Join-Path $Root 'liblua\llex.cpp'),
(Join-Path $Root 'liblua\lmathlib.cpp'),
(Join-Path $Root 'liblua\lmem.cpp'),
(Join-Path $Root 'liblua\lobject.cpp'),
(Join-Path $Root 'liblua\lparser.cpp'),
(Join-Path $Root 'liblua\lstate.cpp'),
(Join-Path $Root 'liblua\lstring.cpp'),
(Join-Path $Root 'liblua\lstrlib.cpp'),
(Join-Path $Root 'liblua\ltable.cpp'),
(Join-Path $Root 'liblua\ltests.cpp'),
(Join-Path $Root 'liblua\ltm.cpp'),
(Join-Path $Root 'liblua\lundump.cpp'),
(Join-Path $Root 'liblua\lvm.cpp'),
(Join-Path $Root 'liblua\lzio.cpp')
)
Compile-Many `
-Includes @((Join-Path $Root 'fkn'), (Join-Path $Root 'compat')) `
-OutDir (Join-Path $Build 'fkn') `
-Sources @(
(Join-Path $Root 'fkn\complex.cpp'),
(Join-Path $Root 'fkn\cspars.cpp'),
(Join-Path $Root 'fkn\cuthill.cpp'),
(Join-Path $Root 'fkn\femmedoccore.cpp'),
(Join-Path $Root 'fkn\fullmatrix.cpp'),
(Join-Path $Root 'fkn\matprop.cpp'),
(Join-Path $Root 'fkn\prob1big.cpp'),
(Join-Path $Root 'fkn\prob2big.cpp'),
(Join-Path $Root 'fkn\prob3big.cpp'),
(Join-Path $Root 'fkn\prob4big.cpp'),
(Join-Path $Root 'fkn\spars.cpp')
)
Compile-Many `
-Includes @((Join-Path $Root 'belasolv'), (Join-Path $Root 'compat')) `
-OutDir (Join-Path $Build 'belasolv') `
-Sources @(
(Join-Path $Root 'belasolv\cuthill.cpp'),
(Join-Path $Root 'belasolv\femmedoccore.cpp'),
(Join-Path $Root 'belasolv\prob1big.cpp'),
(Join-Path $Root 'belasolv\spars.cpp')
)
Compile-Many `
-Includes @((Join-Path $Root 'csolv'), (Join-Path $Root 'compat')) `
-OutDir (Join-Path $Build 'csolv') `
-Sources @(
(Join-Path $Root 'csolv\complex.cpp'),
(Join-Path $Root 'csolv\cspars.cpp'),
(Join-Path $Root 'csolv\CUTHILL.CPP'),
(Join-Path $Root 'csolv\femmedoccore.cpp'),
(Join-Path $Root 'csolv\PROB1BIG.CPP')
)
Compile-Many `
-Includes @((Join-Path $Root 'hsolv'), (Join-Path $Root 'compat')) `
-OutDir (Join-Path $Build 'hsolv') `
-Sources @(
(Join-Path $Root 'hsolv\complex.cpp'),
(Join-Path $Root 'hsolv\CUTHILL.CPP'),
(Join-Path $Root 'hsolv\hsolvdoc.cpp'),
(Join-Path $Root 'hsolv\prob1big.cpp'),
(Join-Path $Root 'hsolv\SPARS.CPP')
)
# ffi shims — one TU per engine, plus the liblua complex-op shim.
Compile-Many -Includes @((Join-Path $Root 'fkn'), (Join-Path $Root 'compat')) -OutDir (Join-Path $Build 'ffi') -Sources @((Join-Path $Root 'ffi\femm_mag.cpp'))
Compile-Many -Includes @((Join-Path $Root 'belasolv'), (Join-Path $Root 'compat')) -OutDir (Join-Path $Build 'ffi') -Sources @((Join-Path $Root 'ffi\femm_elec.cpp'))
Compile-Many -Includes @((Join-Path $Root 'hsolv'), (Join-Path $Root 'compat')) -OutDir (Join-Path $Build 'ffi') -Sources @((Join-Path $Root 'ffi\femm_heat.cpp'))
Compile-Many -Includes @((Join-Path $Root 'csolv'), (Join-Path $Root 'compat')) -OutDir (Join-Path $Build 'ffi') -Sources @((Join-Path $Root 'ffi\femm_curr.cpp'))
Compile-Many -Includes @((Join-Path $Root 'liblua'), (Join-Path $Root 'compat')) -OutDir (Join-Path $Build 'ffi') -Sources @((Join-Path $Root 'ffi\femm_lua_complex_ops.cpp'))
if ($tc.Kind -eq 'msvc') {
# MSVC: bundle objects per engine via lib.exe. Note: inter-engine internal-symbol collisions
# (cuthill / femmedoccore / prob1big / spars all repeat across engines) are not localized here —
# if the Rust link step trips LNK2005, we'll need to merge to DLLs or namespace the C++ sources.
function Pack-Lib {
param([string]$Name, [string[]]$Objs)
$out = Join-Path $Build "$Name.lib"
if (Test-Path $out) { Remove-Item $out -Force }
& lib.exe /NOLOGO "/OUT:$out" @Objs | Out-Null
if ($LASTEXITCODE -ne 0) { throw "lib.exe failed for $Name ($LASTEXITCODE)" }
}
Pack-Lib 'femm_mag' ((Get-ChildItem (Join-Path $Build 'fkn\*.obj') | ForEach-Object FullName) +
(Get-ChildItem (Join-Path $Build 'liblua\*.obj') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_mag.obj'),
(Join-Path $Build 'ffi\femm_lua_complex_ops.obj')))
Pack-Lib 'femm_elec' ((Get-ChildItem (Join-Path $Build 'belasolv\*.obj') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_elec.obj')))
Pack-Lib 'femm_heat' ((Get-ChildItem (Join-Path $Build 'hsolv\*.obj') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_heat.obj')))
Pack-Lib 'femm_curr' ((Get-ChildItem (Join-Path $Build 'csolv\*.obj') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_curr.obj')))
Write-Host 'built:'
Get-ChildItem (Join-Path $Build 'femm_*.lib') | ForEach-Object { Write-Host " $($_.FullName) ($([math]::Round($_.Length/1KB)) KB)" }
} else {
$binDir = if ($tc.Kind -eq 'clangarm64') { $tc.Bin } else { $tc.MingwBin }
$nmExe = Join-Path $binDir 'nm.exe'
$arExe = Join-Path $binDir 'ar.exe'
$objcopyExe = if ($tc.Kind -eq 'clangarm64') {
Join-Path $binDir 'llvm-objcopy.exe'
} else {
Join-Path $binDir 'objcopy.exe'
}
foreach ($p in @($nmExe, $arExe, $objcopyExe)) {
if (-not (Test-Path $p)) { throw "missing binutil: $p" }
}
function Pack-Engine {
param([string]$Prefix, [string[]]$Objs)
$publicRx = "^femm_${Prefix}_"
$renameSet = [System.Collections.Generic.HashSet[string]]::new()
foreach ($obj in $Objs) {
$lines = & $nmExe --defined-only -g $obj
if ($LASTEXITCODE -ne 0) { throw "nm failed on $obj" }
foreach ($line in $lines) {
if ($line -match '^\s*(?:[0-9a-fA-F]+\s+)?([A-Z])\s+(\S+)\s*$') {
$sym = $Matches[2]
if ($sym -notmatch $publicRx) { $null = $renameSet.Add($sym) }
}
}
}
$renameFile = Join-Path $Build "exports\${Prefix}_rename.txt"
(($renameSet | Sort-Object | ForEach-Object { "$_ __femm_${Prefix}__$_" }) -join "`n") | Set-Content -Path $renameFile -Encoding ascii
$engineDir = Join-Path $Build "engines\$Prefix"
if (Test-Path $engineDir) { Remove-Item $engineDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $engineDir | Out-Null
$renamedObjs = foreach ($obj in $Objs) {
$base = [IO.Path]::GetFileName($obj)
$newPath = Join-Path $engineDir "${Prefix}_${base}"
Copy-Item $obj $newPath -Force
& $objcopyExe "--redefine-syms=$renameFile" $newPath $newPath
if ($LASTEXITCODE -ne 0) { throw "objcopy failed on $newPath ($LASTEXITCODE)" }
$newPath
}
$archive = Join-Path $Build "libfemm_${Prefix}.a"
if (Test-Path $archive) { Remove-Item $archive -Force }
& $arExe rcs $archive @renamedObjs
if ($LASTEXITCODE -ne 0) { throw "ar failed for $Prefix ($LASTEXITCODE)" }
}
Pack-Engine 'mag' ((Get-ChildItem (Join-Path $Build 'fkn\*.o') | ForEach-Object FullName) +
(Get-ChildItem (Join-Path $Build 'liblua\*.o') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_mag.o'),
(Join-Path $Build 'ffi\femm_lua_complex_ops.o')))
Pack-Engine 'elec' ((Get-ChildItem (Join-Path $Build 'belasolv\*.o') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_elec.o')))
Pack-Engine 'heat' ((Get-ChildItem (Join-Path $Build 'hsolv\*.o') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_heat.o')))
Pack-Engine 'curr' ((Get-ChildItem (Join-Path $Build 'csolv\*.o') | ForEach-Object FullName) +
@((Join-Path $Build 'ffi\femm_curr.o')))
Write-Host 'built:'
Get-ChildItem (Join-Path $Build 'libfemm_*.a') | ForEach-Object { Write-Host " $($_.FullName) ($([math]::Round($_.Length/1KB)) KB)" }
}

View File

@ -0,0 +1,32 @@
#requires -Version 7
# Builds the Triangle mesher into build\triangle\triangle.exe.
$ErrorActionPreference = 'Stop'
$Root = Resolve-Path (Join-Path $PSScriptRoot '..\..')
$Build = if ($env:BUILD) { $env:BUILD } else { Join-Path $Root 'build\triangle' }
New-Item -ItemType Directory -Force -Path $Build | Out-Null
. (Join-Path $PSScriptRoot '_toolchain.ps1')
$tc = Get-Toolchain
Initialize-Toolchain $tc
$buildProfile = if ($env:PROFILE) { $env:PROFILE } else { 'release' }
$src = Join-Path $Root 'triangle\triangle.c'
$out = Join-Path $Build 'triangle.exe'
if ($tc.Kind -eq 'msvc') {
$cflags = if ($buildProfile -eq 'release') { @('/nologo','/O2','/DNDEBUG','/W0') } else { @('/nologo','/Od','/Zi','/W0') }
& cl.exe @cflags $src "/Fe:$out" "/Fo:$Build\" /link /SUBSYSTEM:CONSOLE
if ($LASTEXITCODE -ne 0) { throw "cl.exe failed building triangle ($LASTEXITCODE)" }
} elseif ($tc.Kind -eq 'clangarm64') {
$cflags = if ($buildProfile -eq 'release') { @('-std=gnu89','-O3','-DNDEBUG','-w') } else { @('-std=gnu89','-O0','-g','-w') }
& (Join-Path $tc.Bin 'clang.exe') @cflags -o $out $src -lm
if ($LASTEXITCODE -ne 0) { throw "clang failed building triangle ($LASTEXITCODE)" }
} else {
$cflags = if ($buildProfile -eq 'release') { @('-std=gnu89','-O3','-DNDEBUG','-w') } else { @('-std=gnu89','-O0','-g','-w') }
& gcc.exe @cflags -o $out $src -lm
if ($LASTEXITCODE -ne 0) { throw "gcc failed building triangle ($LASTEXITCODE)" }
}
Write-Host "built: $out"

26
scripts/windows/debug.ps1 Normal file
View File

@ -0,0 +1,26 @@
#requires -Version 7
$ErrorActionPreference = 'Stop'
$Root = Resolve-Path (Join-Path $PSScriptRoot '..\..')
Set-Location $Root
if (-not $IsWindows) {
Write-Error "wrong platform: $($PSVersionTable.OS) - use cargo xtask debug"
exit 1
}
. (Join-Path $PSScriptRoot '_toolchain.ps1')
$tc = Get-Toolchain
Initialize-Toolchain $tc
$env:RUST_BACKTRACE = '1'
$cargoArgs = @('run','-p','femm-app')
if ($tc.CargoTarget) {
$cargoArgs += @('--target',$tc.CargoTarget)
}
$cargoArgs += '--'
$cargoArgs += $args
& cargo @cargoArgs
exit $LASTEXITCODE

4
scripts/windows/ffi.ps1 Normal file
View File

@ -0,0 +1,4 @@
#requires -Version 7
$ErrorActionPreference = 'Stop'
& (Join-Path $PSScriptRoot 'build_ffi.ps1') @args
exit $LASTEXITCODE

View File

@ -0,0 +1,29 @@
#requires -Version 7
$ErrorActionPreference = 'Stop'
$Root = Resolve-Path (Join-Path $PSScriptRoot '..\..')
Set-Location $Root
if (-not $IsWindows) {
Write-Error "wrong platform: $($PSVersionTable.OS) - use cargo xtask install"
exit 1
}
& (Join-Path $PSScriptRoot 'build.ps1')
if ($LASTEXITCODE -ne 0) { throw "build.ps1 failed ($LASTEXITCODE)" }
$Dest = if ($env:FEMM_INSTALL_DIR) { $env:FEMM_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA 'Programs\femm' }
$Src = Join-Path $Root 'build\bin\femm'
# Stop any running instance so the copy doesn't trip on a file lock.
Get-Process -Name 'femm' -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host "Stopping running femm.exe (pid $($_.Id))..."
try { $_ | Stop-Process -Force } catch {}
}
Start-Sleep -Milliseconds 200
if (Test-Path $Dest) { Remove-Item $Dest -Recurse -Force }
New-Item -ItemType Directory -Force -Path $Dest | Out-Null
Copy-Item -Path (Join-Path $Src '*') -Destination $Dest -Recurse -Force
Write-Host "Installed: $Dest"

View File

@ -346,6 +346,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#ifndef NO_TIMER #ifndef NO_TIMER
@ -831,7 +832,7 @@ struct behavior {
/* extracting an orientation (in the range 0 to 2) and a pointer to the */ /* extracting an orientation (in the range 0 to 2) and a pointer to the */
/* beginning of a triangle. The encode() routine compresses a pointer to a */ /* beginning of a triangle. The encode() routine compresses a pointer to a */
/* triangle and an orientation into a single pointer. My assumptions that */ /* triangle and an orientation into a single pointer. My assumptions that */
/* triangles are four-byte-aligned and that the `unsigned long' type is */ /* triangles are four-byte-aligned and that the `uintptr_t' type is */
/* long enough to hold a pointer are two of the few kludges in this program.*/ /* long enough to hold a pointer are two of the few kludges in this program.*/
/* */ /* */
/* Subsegments are manipulated similarly. A pointer to a subsegment */ /* Subsegments are manipulated similarly. A pointer to a subsegment */
@ -942,16 +943,16 @@ int minus1mod3[3] = {2, 0, 1};
/* extracted from the two least significant bits of the pointer. */ /* extracted from the two least significant bits of the pointer. */
#define decode(ptr, otri) \ #define decode(ptr, otri) \
(otri).orient = (int) ((unsigned long) (ptr) & (unsigned long) 3l); \ (otri).orient = (int) ((uintptr_t) (ptr) & (uintptr_t) 3l); \
(otri).tri = (triangle *) \ (otri).tri = (triangle *) \
((unsigned long) (ptr) ^ (unsigned long) (otri).orient) ((uintptr_t) (ptr) ^ (uintptr_t) (otri).orient)
/* encode() compresses an oriented triangle into a single pointer. It */ /* encode() compresses an oriented triangle into a single pointer. It */
/* relies on the assumption that all triangles are aligned to four-byte */ /* relies on the assumption that all triangles are aligned to four-byte */
/* boundaries, so the two least significant bits of (otri).tri are zero. */ /* boundaries, so the two least significant bits of (otri).tri are zero. */
#define encode(otri) \ #define encode(otri) \
(triangle) ((unsigned long) (otri).tri | (unsigned long) (otri).orient) (triangle) ((uintptr_t) (otri).tri | (uintptr_t) (otri).orient)
/* The following handle manipulation primitives are all described by Guibas */ /* The following handle manipulation primitives are all described by Guibas */
/* and Stolfi. However, Guibas and Stolfi use an edge-based data */ /* and Stolfi. However, Guibas and Stolfi use an edge-based data */
@ -1115,16 +1116,16 @@ int minus1mod3[3] = {2, 0, 1};
#define infect(otri) \ #define infect(otri) \
(otri).tri[6] = (triangle) \ (otri).tri[6] = (triangle) \
((unsigned long) (otri).tri[6] | (unsigned long) 2l) ((uintptr_t) (otri).tri[6] | (uintptr_t) 2l)
#define uninfect(otri) \ #define uninfect(otri) \
(otri).tri[6] = (triangle) \ (otri).tri[6] = (triangle) \
((unsigned long) (otri).tri[6] & ~ (unsigned long) 2l) ((uintptr_t) (otri).tri[6] & ~ (uintptr_t) 2l)
/* Test a triangle for viral infection. */ /* Test a triangle for viral infection. */
#define infected(otri) \ #define infected(otri) \
(((unsigned long) (otri).tri[6] & (unsigned long) 2l) != 0l) (((uintptr_t) (otri).tri[6] & (uintptr_t) 2l) != 0l)
/* Check or set a triangle's attributes. */ /* Check or set a triangle's attributes. */
@ -1162,16 +1163,16 @@ int minus1mod3[3] = {2, 0, 1};
/* are masked out to produce the real pointer. */ /* are masked out to produce the real pointer. */
#define sdecode(sptr, osub) \ #define sdecode(sptr, osub) \
(osub).ssorient = (int) ((unsigned long) (sptr) & (unsigned long) 1l); \ (osub).ssorient = (int) ((uintptr_t) (sptr) & (uintptr_t) 1l); \
(osub).ss = (subseg *) \ (osub).ss = (subseg *) \
((unsigned long) (sptr) & ~ (unsigned long) 3l) ((uintptr_t) (sptr) & ~ (uintptr_t) 3l)
/* sencode() compresses an oriented subsegment into a single pointer. It */ /* sencode() compresses an oriented subsegment into a single pointer. It */
/* relies on the assumption that all subsegments are aligned to two-byte */ /* relies on the assumption that all subsegments are aligned to two-byte */
/* boundaries, so the least significant bit of (osub).ss is zero. */ /* boundaries, so the least significant bit of (osub).ss is zero. */
#define sencode(osub) \ #define sencode(osub) \
(subseg) ((unsigned long) (osub).ss | (unsigned long) (osub).ssorient) (subseg) ((uintptr_t) (osub).ss | (uintptr_t) (osub).ssorient)
/* ssym() toggles the orientation of a subsegment. */ /* ssym() toggles the orientation of a subsegment. */
@ -1908,27 +1909,27 @@ struct otri *t;
struct osub printsh; struct osub printsh;
vertex printvertex; vertex printvertex;
printf("triangle x%lx with orientation %d:\n", (unsigned long) t->tri, printf("triangle x%lx with orientation %d:\n", (uintptr_t) t->tri,
t->orient); t->orient);
decode(t->tri[0], printtri); decode(t->tri[0], printtri);
if (printtri.tri == m->dummytri) { if (printtri.tri == m->dummytri) {
printf(" [0] = Outer space\n"); printf(" [0] = Outer space\n");
} else { } else {
printf(" [0] = x%lx %d\n", (unsigned long) printtri.tri, printf(" [0] = x%lx %d\n", (uintptr_t) printtri.tri,
printtri.orient); printtri.orient);
} }
decode(t->tri[1], printtri); decode(t->tri[1], printtri);
if (printtri.tri == m->dummytri) { if (printtri.tri == m->dummytri) {
printf(" [1] = Outer space\n"); printf(" [1] = Outer space\n");
} else { } else {
printf(" [1] = x%lx %d\n", (unsigned long) printtri.tri, printf(" [1] = x%lx %d\n", (uintptr_t) printtri.tri,
printtri.orient); printtri.orient);
} }
decode(t->tri[2], printtri); decode(t->tri[2], printtri);
if (printtri.tri == m->dummytri) { if (printtri.tri == m->dummytri) {
printf(" [2] = Outer space\n"); printf(" [2] = Outer space\n");
} else { } else {
printf(" [2] = x%lx %d\n", (unsigned long) printtri.tri, printf(" [2] = x%lx %d\n", (uintptr_t) printtri.tri,
printtri.orient); printtri.orient);
} }
@ -1937,37 +1938,37 @@ struct otri *t;
printf(" Origin[%d] = NULL\n", (t->orient + 1) % 3 + 3); printf(" Origin[%d] = NULL\n", (t->orient + 1) % 3 + 3);
else else
printf(" Origin[%d] = x%lx (%.12g, %.12g)\n", printf(" Origin[%d] = x%lx (%.12g, %.12g)\n",
(t->orient + 1) % 3 + 3, (unsigned long) printvertex, (t->orient + 1) % 3 + 3, (uintptr_t) printvertex,
printvertex[0], printvertex[1]); printvertex[0], printvertex[1]);
dest(*t, printvertex); dest(*t, printvertex);
if (printvertex == (vertex) NULL) if (printvertex == (vertex) NULL)
printf(" Dest [%d] = NULL\n", (t->orient + 2) % 3 + 3); printf(" Dest [%d] = NULL\n", (t->orient + 2) % 3 + 3);
else else
printf(" Dest [%d] = x%lx (%.12g, %.12g)\n", printf(" Dest [%d] = x%lx (%.12g, %.12g)\n",
(t->orient + 2) % 3 + 3, (unsigned long) printvertex, (t->orient + 2) % 3 + 3, (uintptr_t) printvertex,
printvertex[0], printvertex[1]); printvertex[0], printvertex[1]);
apex(*t, printvertex); apex(*t, printvertex);
if (printvertex == (vertex) NULL) if (printvertex == (vertex) NULL)
printf(" Apex [%d] = NULL\n", t->orient + 3); printf(" Apex [%d] = NULL\n", t->orient + 3);
else else
printf(" Apex [%d] = x%lx (%.12g, %.12g)\n", printf(" Apex [%d] = x%lx (%.12g, %.12g)\n",
t->orient + 3, (unsigned long) printvertex, t->orient + 3, (uintptr_t) printvertex,
printvertex[0], printvertex[1]); printvertex[0], printvertex[1]);
if (b->usesegments) { if (b->usesegments) {
sdecode(t->tri[6], printsh); sdecode(t->tri[6], printsh);
if (printsh.ss != m->dummysub) { if (printsh.ss != m->dummysub) {
printf(" [6] = x%lx %d\n", (unsigned long) printsh.ss, printf(" [6] = x%lx %d\n", (uintptr_t) printsh.ss,
printsh.ssorient); printsh.ssorient);
} }
sdecode(t->tri[7], printsh); sdecode(t->tri[7], printsh);
if (printsh.ss != m->dummysub) { if (printsh.ss != m->dummysub) {
printf(" [7] = x%lx %d\n", (unsigned long) printsh.ss, printf(" [7] = x%lx %d\n", (uintptr_t) printsh.ss,
printsh.ssorient); printsh.ssorient);
} }
sdecode(t->tri[8], printsh); sdecode(t->tri[8], printsh);
if (printsh.ss != m->dummysub) { if (printsh.ss != m->dummysub) {
printf(" [8] = x%lx %d\n", (unsigned long) printsh.ss, printf(" [8] = x%lx %d\n", (uintptr_t) printsh.ss,
printsh.ssorient); printsh.ssorient);
} }
} }
@ -2003,19 +2004,19 @@ struct osub *s;
vertex printvertex; vertex printvertex;
printf("subsegment x%lx with orientation %d and mark %d:\n", printf("subsegment x%lx with orientation %d and mark %d:\n",
(unsigned long) s->ss, s->ssorient, mark(*s)); (uintptr_t) s->ss, s->ssorient, mark(*s));
sdecode(s->ss[0], printsh); sdecode(s->ss[0], printsh);
if (printsh.ss == m->dummysub) { if (printsh.ss == m->dummysub) {
printf(" [0] = No subsegment\n"); printf(" [0] = No subsegment\n");
} else { } else {
printf(" [0] = x%lx %d\n", (unsigned long) printsh.ss, printf(" [0] = x%lx %d\n", (uintptr_t) printsh.ss,
printsh.ssorient); printsh.ssorient);
} }
sdecode(s->ss[1], printsh); sdecode(s->ss[1], printsh);
if (printsh.ss == m->dummysub) { if (printsh.ss == m->dummysub) {
printf(" [1] = No subsegment\n"); printf(" [1] = No subsegment\n");
} else { } else {
printf(" [1] = x%lx %d\n", (unsigned long) printsh.ss, printf(" [1] = x%lx %d\n", (uintptr_t) printsh.ss,
printsh.ssorient); printsh.ssorient);
} }
@ -2024,28 +2025,28 @@ struct osub *s;
printf(" Origin[%d] = NULL\n", 2 + s->ssorient); printf(" Origin[%d] = NULL\n", 2 + s->ssorient);
else else
printf(" Origin[%d] = x%lx (%.12g, %.12g)\n", printf(" Origin[%d] = x%lx (%.12g, %.12g)\n",
2 + s->ssorient, (unsigned long) printvertex, 2 + s->ssorient, (uintptr_t) printvertex,
printvertex[0], printvertex[1]); printvertex[0], printvertex[1]);
sdest(*s, printvertex); sdest(*s, printvertex);
if (printvertex == (vertex) NULL) if (printvertex == (vertex) NULL)
printf(" Dest [%d] = NULL\n", 3 - s->ssorient); printf(" Dest [%d] = NULL\n", 3 - s->ssorient);
else else
printf(" Dest [%d] = x%lx (%.12g, %.12g)\n", printf(" Dest [%d] = x%lx (%.12g, %.12g)\n",
3 - s->ssorient, (unsigned long) printvertex, 3 - s->ssorient, (uintptr_t) printvertex,
printvertex[0], printvertex[1]); printvertex[0], printvertex[1]);
decode(s->ss[6], printtri); decode(s->ss[6], printtri);
if (printtri.tri == m->dummytri) { if (printtri.tri == m->dummytri) {
printf(" [6] = Outer space\n"); printf(" [6] = Outer space\n");
} else { } else {
printf(" [6] = x%lx %d\n", (unsigned long) printtri.tri, printf(" [6] = x%lx %d\n", (uintptr_t) printtri.tri,
printtri.orient); printtri.orient);
} }
decode(s->ss[7], printtri); decode(s->ss[7], printtri);
if (printtri.tri == m->dummytri) { if (printtri.tri == m->dummytri) {
printf(" [7] = Outer space\n"); printf(" [7] = Outer space\n");
} else { } else {
printf(" [7] = x%lx %d\n", (unsigned long) printtri.tri, printf(" [7] = x%lx %d\n", (uintptr_t) printtri.tri,
printtri.orient); printtri.orient);
} }
@ -2054,14 +2055,14 @@ struct osub *s;
printf(" Segment origin[%d] = NULL\n", 4 + s->ssorient); printf(" Segment origin[%d] = NULL\n", 4 + s->ssorient);
else else
printf(" Segment origin[%d] = x%lx (%.12g, %.12g)\n", printf(" Segment origin[%d] = x%lx (%.12g, %.12g)\n",
4 + s->ssorient, (unsigned long) printvertex, 4 + s->ssorient, (uintptr_t) printvertex,
printvertex[0], printvertex[1]); printvertex[0], printvertex[1]);
segdest(*s, printvertex); segdest(*s, printvertex);
if (printvertex == (vertex) NULL) if (printvertex == (vertex) NULL)
printf(" Segment dest [%d] = NULL\n", 5 - s->ssorient); printf(" Segment dest [%d] = NULL\n", 5 - s->ssorient);
else else
printf(" Segment dest [%d] = x%lx (%.12g, %.12g)\n", printf(" Segment dest [%d] = x%lx (%.12g, %.12g)\n",
5 - s->ssorient, (unsigned long) printvertex, 5 - s->ssorient, (uintptr_t) printvertex,
printvertex[0], printvertex[1]); printvertex[0], printvertex[1]);
} }
@ -2124,7 +2125,7 @@ struct memorypool *pool;
#endif /* not ANSI_DECLARATORS */ #endif /* not ANSI_DECLARATORS */
{ {
unsigned long alignptr; uintptr_t alignptr;
pool->items = 0; pool->items = 0;
pool->maxitems = 0; pool->maxitems = 0;
@ -2132,11 +2133,11 @@ struct memorypool *pool;
/* Set the currently active block. */ /* Set the currently active block. */
pool->nowblock = pool->firstblock; pool->nowblock = pool->firstblock;
/* Find the first item in the pool. Increment by the size of (VOID *). */ /* Find the first item in the pool. Increment by the size of (VOID *). */
alignptr = (unsigned long) (pool->nowblock + 1); alignptr = (uintptr_t) (pool->nowblock + 1);
/* Align the item on an `alignbytes'-byte boundary. */ /* Align the item on an `alignbytes'-byte boundary. */
pool->nextitem = (VOID *) pool->nextitem = (VOID *)
(alignptr + (unsigned long) pool->alignbytes - (alignptr + (uintptr_t) pool->alignbytes -
(alignptr % (unsigned long) pool->alignbytes)); (alignptr % (uintptr_t) pool->alignbytes));
/* There are lots of unallocated items left in this block. */ /* There are lots of unallocated items left in this block. */
pool->unallocateditems = pool->itemsfirstblock; pool->unallocateditems = pool->itemsfirstblock;
/* The stack of deallocated items is empty. */ /* The stack of deallocated items is empty. */
@ -2241,7 +2242,7 @@ struct memorypool *pool;
{ {
VOID *newitem; VOID *newitem;
VOID **newblock; VOID **newblock;
unsigned long alignptr; uintptr_t alignptr;
/* First check the linked list of dead items. If the list is not */ /* First check the linked list of dead items. If the list is not */
/* empty, allocate an item from the list rather than a fresh one. */ /* empty, allocate an item from the list rather than a fresh one. */
@ -2266,11 +2267,11 @@ struct memorypool *pool;
pool->nowblock = (VOID **) *(pool->nowblock); pool->nowblock = (VOID **) *(pool->nowblock);
/* Find the first item in the block. */ /* Find the first item in the block. */
/* Increment by the size of (VOID *). */ /* Increment by the size of (VOID *). */
alignptr = (unsigned long) (pool->nowblock + 1); alignptr = (uintptr_t) (pool->nowblock + 1);
/* Align the item on an `alignbytes'-byte boundary. */ /* Align the item on an `alignbytes'-byte boundary. */
pool->nextitem = (VOID *) pool->nextitem = (VOID *)
(alignptr + (unsigned long) pool->alignbytes - (alignptr + (uintptr_t) pool->alignbytes -
(alignptr % (unsigned long) pool->alignbytes)); (alignptr % (uintptr_t) pool->alignbytes));
/* There are lots of unallocated items left in this block. */ /* There are lots of unallocated items left in this block. */
pool->unallocateditems = pool->itemsperblock; pool->unallocateditems = pool->itemsperblock;
} }
@ -2325,16 +2326,16 @@ struct memorypool *pool;
#endif /* not ANSI_DECLARATORS */ #endif /* not ANSI_DECLARATORS */
{ {
unsigned long alignptr; uintptr_t alignptr;
/* Begin the traversal in the first block. */ /* Begin the traversal in the first block. */
pool->pathblock = pool->firstblock; pool->pathblock = pool->firstblock;
/* Find the first item in the block. Increment by the size of (VOID *). */ /* Find the first item in the block. Increment by the size of (VOID *). */
alignptr = (unsigned long) (pool->pathblock + 1); alignptr = (uintptr_t) (pool->pathblock + 1);
/* Align with item on an `alignbytes'-byte boundary. */ /* Align with item on an `alignbytes'-byte boundary. */
pool->pathitem = (VOID *) pool->pathitem = (VOID *)
(alignptr + (unsigned long) pool->alignbytes - (alignptr + (uintptr_t) pool->alignbytes -
(alignptr % (unsigned long) pool->alignbytes)); (alignptr % (uintptr_t) pool->alignbytes));
/* Set the number of items left in the current block. */ /* Set the number of items left in the current block. */
pool->pathitemsleft = pool->itemsfirstblock; pool->pathitemsleft = pool->itemsfirstblock;
} }
@ -2362,7 +2363,7 @@ struct memorypool *pool;
{ {
VOID *newitem; VOID *newitem;
unsigned long alignptr; uintptr_t alignptr;
/* Stop upon exhausting the list of items. */ /* Stop upon exhausting the list of items. */
if (pool->pathitem == pool->nextitem) { if (pool->pathitem == pool->nextitem) {
@ -2374,11 +2375,11 @@ struct memorypool *pool;
/* Find the next block. */ /* Find the next block. */
pool->pathblock = (VOID **) *(pool->pathblock); pool->pathblock = (VOID **) *(pool->pathblock);
/* Find the first item in the block. Increment by the size of (VOID *). */ /* Find the first item in the block. Increment by the size of (VOID *). */
alignptr = (unsigned long) (pool->pathblock + 1); alignptr = (uintptr_t) (pool->pathblock + 1);
/* Align with item on an `alignbytes'-byte boundary. */ /* Align with item on an `alignbytes'-byte boundary. */
pool->pathitem = (VOID *) pool->pathitem = (VOID *)
(alignptr + (unsigned long) pool->alignbytes - (alignptr + (uintptr_t) pool->alignbytes -
(alignptr % (unsigned long) pool->alignbytes)); (alignptr % (uintptr_t) pool->alignbytes));
/* Set the number of items left in the current block. */ /* Set the number of items left in the current block. */
pool->pathitemsleft = pool->itemsperblock; pool->pathitemsleft = pool->itemsperblock;
} }
@ -2430,16 +2431,16 @@ int subsegbytes;
#endif /* not ANSI_DECLARATORS */ #endif /* not ANSI_DECLARATORS */
{ {
unsigned long alignptr; uintptr_t alignptr;
/* Set up `dummytri', the `triangle' that occupies "outer space." */ /* Set up `dummytri', the `triangle' that occupies "outer space." */
m->dummytribase = (triangle *) trimalloc(trianglebytes + m->dummytribase = (triangle *) trimalloc(trianglebytes +
m->triangles.alignbytes); m->triangles.alignbytes);
/* Align `dummytri' on a `triangles.alignbytes'-byte boundary. */ /* Align `dummytri' on a `triangles.alignbytes'-byte boundary. */
alignptr = (unsigned long) m->dummytribase; alignptr = (uintptr_t) m->dummytribase;
m->dummytri = (triangle *) m->dummytri = (triangle *)
(alignptr + (unsigned long) m->triangles.alignbytes - (alignptr + (uintptr_t) m->triangles.alignbytes -
(alignptr % (unsigned long) m->triangles.alignbytes)); (alignptr % (uintptr_t) m->triangles.alignbytes));
/* Initialize the three adjoining triangles to be "outer space." These */ /* Initialize the three adjoining triangles to be "outer space." These */
/* will eventually be changed by various bonding operations, but their */ /* will eventually be changed by various bonding operations, but their */
/* values don't really matter, as long as they can legally be */ /* values don't really matter, as long as they can legally be */
@ -2459,10 +2460,10 @@ int subsegbytes;
m->dummysubbase = (subseg *) trimalloc(subsegbytes + m->dummysubbase = (subseg *) trimalloc(subsegbytes +
m->subsegs.alignbytes); m->subsegs.alignbytes);
/* Align `dummysub' on a `subsegs.alignbytes'-byte boundary. */ /* Align `dummysub' on a `subsegs.alignbytes'-byte boundary. */
alignptr = (unsigned long) m->dummysubbase; alignptr = (uintptr_t) m->dummysubbase;
m->dummysub = (subseg *) m->dummysub = (subseg *)
(alignptr + (unsigned long) m->subsegs.alignbytes - (alignptr + (uintptr_t) m->subsegs.alignbytes -
(alignptr % (unsigned long) m->subsegs.alignbytes)); (alignptr % (uintptr_t) m->subsegs.alignbytes));
/* Initialize the two adjoining subsegments to be the omnipresent */ /* Initialize the two adjoining subsegments to be the omnipresent */
/* subsegment. These will eventually be changed by various bonding */ /* subsegment. These will eventually be changed by various bonding */
/* operations, but their values don't really matter, as long as they */ /* operations, but their values don't really matter, as long as they */
@ -2819,7 +2820,7 @@ int number;
{ {
VOID **getblock; VOID **getblock;
char *foundvertex; char *foundvertex;
unsigned long alignptr; uintptr_t alignptr;
int current; int current;
getblock = m->vertices.firstblock; getblock = m->vertices.firstblock;
@ -2836,9 +2837,9 @@ int number;
} }
/* Now find the right vertex. */ /* Now find the right vertex. */
alignptr = (unsigned long) (getblock + 1); alignptr = (uintptr_t) (getblock + 1);
foundvertex = (char *) (alignptr + (unsigned long) m->vertices.alignbytes - foundvertex = (char *) (alignptr + (uintptr_t) m->vertices.alignbytes -
(alignptr % (unsigned long) m->vertices.alignbytes)); (alignptr % (uintptr_t) m->vertices.alignbytes));
return (vertex) (foundvertex + m->vertices.itembytes * (number - current)); return (vertex) (foundvertex + m->vertices.itembytes * (number - current));
} }
@ -5882,7 +5883,7 @@ struct otri *searchtri;
char *firsttri; char *firsttri;
struct otri sampletri; struct otri sampletri;
vertex torg, tdest; vertex torg, tdest;
unsigned long alignptr; uintptr_t alignptr;
REAL searchdist, dist; REAL searchdist, dist;
REAL ahead; REAL ahead;
long samplesperblock, totalsamplesleft, samplesleft; long samplesperblock, totalsamplesleft, samplesleft;
@ -5954,11 +5955,11 @@ struct otri *searchtri;
population = totalpopulation; population = totalpopulation;
} }
/* Find a pointer to the first triangle in the block. */ /* Find a pointer to the first triangle in the block. */
alignptr = (unsigned long) (sampleblock + 1); alignptr = (uintptr_t) (sampleblock + 1);
firsttri = (char *) (alignptr + firsttri = (char *) (alignptr +
(unsigned long) m->triangles.alignbytes - (uintptr_t) m->triangles.alignbytes -
(alignptr % (alignptr %
(unsigned long) m->triangles.alignbytes)); (uintptr_t) m->triangles.alignbytes));
/* Choose `samplesleft' randomly sampled triangles in this block. */ /* Choose `samplesleft' randomly sampled triangles in this block. */
do { do {

View File

@ -26,7 +26,7 @@ fn main() -> ExitCode {
"windows" => ( "windows" => (
repo_root.join(format!("scripts/windows/{action}.ps1")), repo_root.join(format!("scripts/windows/{action}.ps1")),
vec![ vec![
"powershell", "pwsh",
"-NoProfile", "-NoProfile",
"-ExecutionPolicy", "-ExecutionPolicy",
"Bypass", "Bypass",