From b2fcf2c5bccb0381449915ae0a22cec4d5103f91 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:06:15 -0400 Subject: [PATCH] geometry: partition cuts and the projection mesher report progress and stop when cancelled Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/engine/src/lib.rs | 1 + crates/geometry/src/lib.rs | 3 +++ crates/geometry/src/partition.rs | 8 +++++++- crates/mesh/src/project/mod.rs | 9 +++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 1e32a84..a54dbb8 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -87,6 +87,7 @@ impl EngineError { matches!( self, EngineError::Model(Cancelled) + | EngineError::Geometry(fieldcraft_geometry::Error::Model(Cancelled)) | EngineError::Mesh(fieldcraft_mesh::Error::Model(Cancelled)) | EngineError::Solver(fieldcraft_solver::Error::Model(Cancelled)) | EngineError::Explicit(fieldcraft_explicit::Error::Model(Cancelled)) diff --git a/crates/geometry/src/lib.rs b/crates/geometry/src/lib.rs index 5d50858..569e263 100644 --- a/crates/geometry/src/lib.rs +++ b/crates/geometry/src/lib.rs @@ -46,6 +46,9 @@ pub enum Error { Invalid(String), #[error("the geometry operation failed: {0}")] Failed(String), + /// From the model layer: a cancelled job ([`fieldcraft_model::progress::check`]). + #[error(transparent)] + Model(#[from] fieldcraft_model::Error), } pub type Result = std::result::Result; diff --git a/crates/geometry/src/partition.rs b/crates/geometry/src/partition.rs index 2402d21..f350236 100644 --- a/crates/geometry/src/partition.rs +++ b/crates/geometry/src/partition.rs @@ -103,7 +103,12 @@ impl Design { let mut bodies: Vec = self.source.as_ref().clone(); let mut errors: Vec = self.errors.iter().filter(|e| !crate::virtual_faces::derived(&e.feature)).cloned().collect(); let mut reports = Vec::with_capacity(cuts.len()); - for (cut, (plane, n)) in cuts.iter().zip(&planes) { + // Each cut is a kernel boolean per body it reaches, seconds each on a large part: the + // job's progress counts them, and a cancelled job stops between them. + fieldcraft_model::progress::stage("Partition cuts", Some(cuts.len() as f64), "cuts"); + for (k, (cut, (plane, n))) in cuts.iter().zip(&planes).enumerate() { + fieldcraft_model::progress::count(k as f64); + fieldcraft_model::progress::check()?; let feature = format!("partition {}", cut.name); // A listed name is the body of that name, or the cells earlier partitions cut it into. let names = |name: &str, b: &str| name == b || name.strip_prefix(b).is_some_and(|rest| rest.starts_with('/')); @@ -122,6 +127,7 @@ impl Design { next.push(body); continue; } + fieldcraft_model::progress::check()?; match split(&body, plane, *n, cut)? { Split::Cells(cells, faces, warnings) => { for &i in &listed { diff --git a/crates/mesh/src/project/mod.rs b/crates/mesh/src/project/mod.rs index 30d3faa..2ce1050 100644 --- a/crates/mesh/src/project/mod.rs +++ b/crates/mesh/src/project/mod.rs @@ -454,6 +454,8 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o let edge_c = |e: [u32; 2]| Constraint::of(&as_vec(edge_on.get(&e))); // 1. Vertices. + fieldcraft_model::progress::stage("Projection: block vertices", None, ""); + fieldcraft_model::progress::check()?; let mut vpos: Vec

= t.vertices.clone(); for (v, p) in vpos.iter_mut().enumerate() { *p = place(g, &targets, vertex_c(v as u32), *p, tol); @@ -697,6 +699,8 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o } } // 3. Edge nodes: even by arc length along their curve or surface. + fieldcraft_model::progress::stage("Projection: edge nodes", None, ""); + fieldcraft_model::progress::check()?; for (b, block) in t.blocks.iter().enumerate() { let nd = lat.divisions[b]; let bn = &lat.block_nodes[b]; @@ -740,6 +744,8 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o } } // 4. Faces (projected when outside), then 5. the interior. + fieldcraft_model::progress::stage("Projection: faces and interior", None, ""); + fieldcraft_model::progress::check()?; for (b, block) in t.blocks.iter().enumerate() { let nd = lat.divisions[b]; let bn = &lat.block_nodes[b]; @@ -798,6 +804,8 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o } // Nodes on a sheet slide along it among their neighbours on it, as boundary nodes do. let boundary_quads: Vec<[u32; 4]> = quads.iter().map(|q| q.0).chain(sheet_quads).collect(); + fieldcraft_model::progress::stage("Projection: smoothing", None, ""); + fieldcraft_model::progress::check()?; let smoothing = smooth::smooth( smooth::Mesh { nodes: &mut nodes, hexes: &lat.hexes, constraint: &constraint, held: &held, boundary: &boundary_quads }, g, @@ -807,6 +815,7 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o 1e-4 * opts.size, ); + fieldcraft_model::progress::check()?; // Certified quality, the gap to the geometry and the tags. let (mut min_sj, mut sum_sj, mut volume) = (f64::INFINITY, 0.0, 0.0); let mut invalid: Vec = Vec::new();