From 80da2d968be2f503efa0966d0b6250dc90308405 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Fri, 16 Oct 2026 07:38:51 -0400 Subject: [PATCH] mesh: applying a whole body's plan to a body partitions cut says to plan a cell (#473) After geometry.partition cuts a body, the body is its cells: a projection plan of the whole body could not open it and failed with "no body `Base` (open a design first)". It now names the cells and says to plan and apply a cell (mesh.plan {"body": "Base/a"}) or delete the partitions. Tests cover a design opened directly and a saved model opened again, partitioned after opening. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/engine/src/cmd/plan.rs | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/engine/src/cmd/plan.rs b/crates/engine/src/cmd/plan.rs index e1cf703..79932e3 100644 --- a/crates/engine/src/cmd/plan.rs +++ b/crates/engine/src/cmd/plan.rs @@ -1324,6 +1324,16 @@ fn apply_projection(s: &mut Session, name: &str, plan: &MeshPlan, tolerance: f64 Some(Err(why)) => return Err(EngineError::Params(format!("mesh plan `{name}`: no projection layout for body `{}`: {why}", plan.body))), None => return Err(EngineError::Params(format!("mesh plan `{name}`: projection blocking needs rings or blends; set the mesher to sweep"))), }; + // Partitions of the user's that cut the body leave cells, each meshed on its own. + if design.body(&plan.body).is_none() { + let cells: Vec<&str> = + design.bodies.iter().filter(|b| design.source_of(&b.name) == Some(plan.body.as_str())).map(|b| b.name.as_str()).collect(); + return Err(EngineError::Params(format!( + "mesh plan `{name}`: body `{}` is cut into the cells {cells:?} by partitions; plan and apply a cell (mesh.plan {{\"body\": \"{}\"}}), or delete the partitions", + plan.body, + cells.first().copied().unwrap_or_default() + ))); + } let body = super::project::open_body(s, &plan.body, Some(tolerance), &json!({}))?; let merge = super::project::merge_distance(None, body.diagonal())?; let opts = fieldcraft_mesh::project::ProjectOptions { size: plan.size, ..Default::default() }; @@ -1685,6 +1695,36 @@ mod tests { let r = s.execute("mesh.plan.apply", &json!({"plan": cell["name"]})).unwrap(); assert_eq!(r["all_hex"], true, "{r}"); assert!(r["min_scaled_jacobian"].as_f64().unwrap() >= 0.5, "{r}"); + // The whole body's plan cannot mesh a body partitions have cut: it says to plan a cell. + let e = s.execute("mesh.plan.apply", &json!({"plan": whole["name"]})).unwrap_err().to_string(); + assert!(e.contains("cut into the cells") && e.contains("Plate/a"), "{e}"); + } + + /// The same from a saved model opened again (the design it refers to rebuilt on opening). + #[test] + fn a_saved_model_partitioned_after_opening_plans_its_body_and_cells() { + let bytes = fieldcraft_geometry::script_design( + &json!({"commands": [ + {"command": "solid.box", "params": {"length": 60, "width": 40, "height": 6, "body_name": "Plate"}}, + {"command": "solid.hole", "params": {"position": [15, 20, 6], "diameter": 8, "depth": 6}}, + ]}) + .to_string(), + ) + .unwrap(); + let (_d, path) = crate::tests::design_file(&bytes); + let model = std::path::Path::new(&path).with_file_name("model.fieldcraft").to_string_lossy().into_owned(); + let mut s = Session::default(); + s.execute("geometry.open", &json!({"path": path})).unwrap(); + s.execute("file.save_as", &json!({"path": model})).unwrap(); + let mut s = Session::default(); + s.execute("file.open", &json!({"path": model})).unwrap(); + s.execute("geometry.datum", &json!({"name": "SymX", "by": {"type": "offset", "plane": "yz", "offset": 30}})).unwrap(); + s.execute("geometry.partition", &json!({"name": "HalfX", "datum": "SymX", "bodies": ["Plate"]})).unwrap(); + let cell = s.execute("mesh.plan", &json!({"body": "Plate/a"})).unwrap(); + assert_eq!(cell["rings"].as_array().map(Vec::len), Some(1), "{cell}"); + let whole = s.execute("mesh.plan", &json!({"body": "Plate"})).unwrap(); + assert_eq!(whole["body"], "Plate", "{whole}"); + s.execute("mesh.plan.apply", &json!({"plan": cell["name"]})).unwrap(); } /// The feature graph of every corpus design and the crank arm: every body lists the features