Engine: the half crank extrudes in blocks through mesh.hex.generate and mesh.cells

The extrusion's automatic cross-section blocks reach both commands; the
half crank arm, cut only on its symmetry plane, meshes all-structured
(sJ >= 0.37) with no seeds either way, and mesh.hex.generate's
description says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 01:26:30 -04:00
1 parent 02172f9a7e
commit 4c18322a34
2 files changed
+22 -1

No files matched your search

+21
View File
@@ -371,6 +371,27 @@ mod tests {
assert_eq!(s.model.ties().len(), 1);
}
/// The half crank arm (cut on its symmetry plane only) has no cell that maps or sweeps as
/// the design gives it; the extrusion decomposes its cross-section into blocks itself, alike
/// through mesh.hex.generate (auto) and mesh.cells, with no seeds.
#[test]
fn the_half_crank_extrudes_in_blocks_without_partitions() {
let bytes = fieldcraft_geometry::script_design(crate::sample::CRANK_ARM).unwrap();
let (_dir, path) = crate::tests::design_file(&bytes);
let mut s = Session::default();
s.execute("geometry.open", &json!({"path": path})).unwrap();
s.execute("geometry.datum", &json!({"name": "Sym", "by": {"type": "offset", "plane": "zx", "offset": 0}})).unwrap();
s.execute("geometry.partition", &json!({"name": "Half", "datum": "Sym"})).unwrap();
let r = s.execute("mesh.hex.generate", &json!({"body": "CrankArm/a", "size": 3})).unwrap();
let body = &r["bodies"][0];
assert_eq!(body["method"], "extrude", "{r}");
assert!(body["description"].as_str().unwrap().contains(" 0 free)"), "{r}");
assert!(body["quality"]["min_scaled_jacobian"].as_f64().unwrap() >= 0.37, "{r}");
let r = s.execute("mesh.cells", &json!({"bodies": ["CrankArm/a"], "default": "extrude", "size": 3})).unwrap();
assert_eq!(r["cells"][0]["method"], "extrude", "{r}");
assert_eq!(r["elements"], body["elements"], "{r}");
}
#[test]
fn auto_falls_back_to_tet_and_says_why() {
let (_dir, mut s) = cut_plate();
+1 -1
View File
@@ -16,7 +16,7 @@ use crate::{CommandKind, CommandSpec, EngineError, Result, Session};
pub const HEX_GENERATE: CommandSpec = CommandSpec {
id: "mesh.hex.generate",
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 meshes of the bodies it meshes (as mesh.tet.generate; other bodies keep theirs) 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"#,
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 meshes of the bodies it meshes (as mesh.tet.generate; other bodies keep theirs) with structured hexahedra: multi-level sweeps along a direction or around an axis (their cross-section cut into mappable blocks automatically: O-grids round holes, triangle primitives, four-sided blocks; nothing is added to the model), 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,
requires: &[Requirement::Geometry],