From f4b6d1dbff8dab4686189c3bb127c69ba7e6a2dd Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:30:24 -0400 Subject: [PATCH] mesh.cells: "default": "blocks" runs mesh.hex.blocks; labels Hex Mesh, Partitioned Mesh Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/engine/src/cmd/cells.rs | 10 ++++++++-- crates/engine/src/cmd/hex.rs | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/cells.rs b/crates/engine/src/cmd/cells.rs index 61de719..40cf6cb 100644 --- a/crates/engine/src/cmd/cells.rs +++ b/crates/engine/src/cmd/cells.rs @@ -29,8 +29,8 @@ use crate::{CommandKind, CommandSpec, EngineError, Result, Session}; pub const CELLS: CommandSpec = CommandSpec { id: "mesh.cells", - label: "Mesh Cells", - params: r#"{"methods": {"Body1/a": "tet", "Body1/b": "swept"} (per cell; default `default`), "default": "auto", "bodies": ["Body1/a", "Body1/b"] (default: every body of the open design), "size": 2.0 (element size, mm; default 1/25 of the bounding-box diagonal of all of them), "order": 1 | 2 (hex8 and tet4, or hex20 and tet10), "tolerance": 0.05 (chordal tolerance, mm)} — replaces the mesh with one mesh of the cells, each with its method: "auto" (structured hex where a method fits, else tet), "hex" (any structured hex method), "swept", "extrude", "sweep", "revolve", "mapped" or "tet". Cells on either side of a partition (faces `partition::a` and `:b`) are joined: where their element faces match node for node the nodes are merged (conforming); otherwise, as between a hex and a tet cell, a set on each side and a tie between them are made (`tie__`, sets `_main` and `_secondary`: the side with fewer nodes is the main surface), replacing ties of those names from an earlier run. Reports each cell's method (and why auto fell back to tet), and each interface: `merged` with the nodes merged, or `tied`"#, + label: "Partitioned Mesh", + params: r#"{"methods": {"Body1/a": "tet", "Body1/b": "swept"} (per cell; default `default`), "default": "auto", "bodies": ["Body1/a", "Body1/b"] (default: every body of the open design), "size": 2.0 (element size, mm; default 1/25 of the bounding-box diagonal of all of them), "order": 1 | 2 (hex8 and tet4, or hex20 and tet10), "tolerance": 0.05 (chordal tolerance, mm)} — replaces the mesh with one mesh of the cells, each with its method: "auto" (structured hex where a method fits, else tet), "hex" (any structured hex method), "swept", "extrude", "sweep", "revolve", "mapped" or "tet"; or "default": "blocks" (with no `methods`): every cell a mapped block, division counts assigned over all cells together, exactly mesh.hex.blocks with the same parameters. Cells on either side of a partition (faces `partition::a` and `:b`) are joined: where their element faces match node for node the nodes are merged (conforming); otherwise, as between a hex and a tet cell, a set on each side and a tie between them are made (`tie__`, sets `_main` and `_secondary`: the side with fewer nodes is the main surface), replacing ties of those names from an earlier run. Reports each cell's method (and why auto fell back to tet), and each interface: `merged` with the nodes merged, or `tied`"#, form: Some(super::forms::CELLS), kind: CommandKind::Model, requires: &[Requirement::Design], @@ -75,6 +75,12 @@ enum Meshed<'a> { } fn cells(s: &mut Session, p: &Value) -> Result { + if p.get("default").and_then(Value::as_str) == Some("blocks") { + if p.get("methods").is_some_and(|m| m.as_object().is_none_or(|o| !o.is_empty())) { + return Err(EngineError::param("methods", "must be empty with \"default\": \"blocks\" (every cell a block)")); + } + return super::hex::hex_blocks(s, p); + } let size = super::hex::number(p, "size")?; let quadratic = match p.get("order") { None => false, diff --git a/crates/engine/src/cmd/hex.rs b/crates/engine/src/cmd/hex.rs index ff88254..bd4801b 100644 --- a/crates/engine/src/cmd/hex.rs +++ b/crates/engine/src/cmd/hex.rs @@ -14,7 +14,7 @@ use crate::{CommandKind, CommandSpec, EngineError, Result, Session}; pub const HEX_GENERATE: CommandSpec = CommandSpec { id: "mesh.hex.generate", - label: "Generate Hex Mesh", + label: "Hex Mesh", params: r#"{"body": "Body1" (default: every body of the open design) or "surface": 0 (an imported surface), "size": 2.0 (element size, mm; default 1/25 of the bounding-box diagonal), "order": 1 (hex8) or 2 (hex20, mid-edge nodes on the geometry), "method": "auto" (default) | "swept" (any sweep) | "extrude" | "sweep" | "revolve" | "mapped", "tolerance": 0.05 (chordal tolerance of the tessellation, mm; default 1e-3 of the diagonal)} — replaces the mesh with structured hexahedra: multi-level sweeps along a direction or around an axis, one-to-one sweeps through linking faces, or mapped blocks. Boundary element faces are tagged with their B-rep faces (named as in geometry.bodies; by index when a face has no name; `weld`, `weld_distance` and `feature_angle` as in mesh.tet.generate), so sets by geometry resolve. Edge seeds (mesh.seed.edge) set the counts and spacing of their edges: exact seeds are held, and seeds that cannot all hold (opposite sides of a mapped face, the layers between two levels) are an error naming them. Reports per body the method, counts and quality; a body no structured method fits is an error that says why"#, form: Some(super::forms::HEX_GENERATE), kind: CommandKind::Model, @@ -148,7 +148,7 @@ pub const HEX_BLOCKS: CommandSpec = CommandSpec { run: hex_blocks, }; -fn hex_blocks(s: &mut Session, p: &Value) -> Result { +pub(super) fn hex_blocks(s: &mut Session, p: &Value) -> Result { let size = number(p, "size")?; let quadratic = order(p)?; let design = s.design.as_deref().ok_or_else(|| EngineError::Params("open a design first".into()))?; @@ -497,6 +497,17 @@ mod tests { assert_eq!(two["cells"], 2); } + /// The Partitioned Mesh form's "All hex" option is mesh.hex.blocks. + #[test] + fn mesh_cells_all_blocks_is_mesh_hex_blocks() { + let (_dir, mut s) = split_box(false); + let blocks = s.execute("mesh.hex.blocks", &json!({"size": 2.5})).unwrap(); + let cells = s.execute("mesh.cells", &json!({"default": "blocks", "size": 2.5})).unwrap(); + assert_eq!(cells, blocks); + let e = s.execute("mesh.cells", &json!({"default": "blocks", "methods": {"Box": "tet"}})).unwrap_err().to_string(); + assert!(e.contains("methods"), "{e}"); + } + #[test] fn cells_that_do_not_conform_and_hostile_params_are_errors() { let (_dir, mut s) = split_box(true);