Ribbon: Hex Mesh and Partitioned Mesh replace the duplicate hex entries (#270)

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-14 22:35:17 -04:00
1 parent f4b6d1dbff
commit 70c5c9b26f
7 files changed
+69 -41

No files matched your search

+2 -3
View File
@@ -57,11 +57,10 @@ is committed and listed here with its exports.
| assets/icons/mesh.element.add.svg, assets/icons/mesh.element.add.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.element.add.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.element.add.svg, assets/icons/mesh.element.add.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.element.add.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.hex.decompose.svg, assets/icons/mesh.hex.decompose.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.decompose.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.hex.decompose.svg, assets/icons/mesh.hex.decompose.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.decompose.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.hex.dominant.svg, assets/icons/mesh.hex.dominant.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.dominant.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.hex.dominant.svg, assets/icons/mesh.hex.dominant.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.dominant.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.hex.blocks.svg, assets/icons/mesh.hex.blocks.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.blocks.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.cells.svg, assets/icons/mesh.cells.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.cells.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.hex.mapped.svg, assets/icons/mesh.hex.mapped.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.mapped.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.hex.generate.svg, assets/icons/mesh.hex.generate.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.generate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.hex.octree.svg, assets/icons/mesh.hex.octree.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.octree.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.hex.octree.svg, assets/icons/mesh.hex.octree.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.octree.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.hex.polycube.svg, assets/icons/mesh.hex.polycube.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.polycube.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.hex.polycube.svg, assets/icons/mesh.hex.polycube.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.polycube.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.hex.sweep.svg, assets/icons/mesh.hex.sweep.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.hex.sweep.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.import.orphan.svg, assets/icons/mesh.import.orphan.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.import.orphan.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.import.orphan.svg, assets/icons/mesh.import.orphan.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.import.orphan.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.layers.svg, assets/icons/mesh.layers.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.layers.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.layers.svg, assets/icons/mesh.layers.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.layers.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/mesh.node.add.svg, assets/icons/mesh.node.add.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.node.add.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | | assets/icons/mesh.node.add.svg, assets/icons/mesh.node.add.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.node.add.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>mesh.hex.blocks</title> <title>mesh.cells</title>
<g id="Layer_1" data-name="Layer 1"> <g id="Layer_1" data-name="Layer 1">
<path d="M12 3 L20 7 L20 16 L12 20 L4 16 L4 7 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 3 L20 7 L20 16 L12 20 L4 16 L4 7 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7 L12 11 L20 7 M12 11 L12 20" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M4 7 L12 11 L20 7 M12 11 L12 20" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 1008 B

After

Width:  |  Height:  |  Size: 1003 B

+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>mesh.hex.mapped</title> <title>mesh.hex.generate</title>
<g id="Layer_1" data-name="Layer 1"> <g id="Layer_1" data-name="Layer 1">
<path d="M12 3 L20 7 L20 16 L12 20 L4 16 L4 7 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 3 L20 7 L20 16 L12 20 L4 16 L4 7 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7 L12 11 L20 7 M12 11 L12 20" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M4 7 L12 11 L20 7 M12 11 L12 20" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 884 B

+58 -29
View File
@@ -155,6 +155,9 @@ pub struct Capability {
pub status: Status, pub status: Status,
/// Related commands in the entry's drop-down (edit, delete…). /// Related commands in the entry's drop-down (edit, delete…).
pub more: &'static [&'static str], pub more: &'static [&'static str],
/// Commands the entry's own form covers (an option of it does the same): claimed by the entry
/// for the command search and the ribbon's stage, but not listed in the ribbon.
pub covers: &'static [&'static str],
} }
impl Capability { impl Capability {
@@ -194,7 +197,7 @@ impl Capability {
} }
const fn ready(id: &'static str, label: &'static str, tab: Tab, panel: &'static str, tip: &'static str) -> Capability { const fn ready(id: &'static str, label: &'static str, tab: Tab, panel: &'static str, tip: &'static str) -> Capability {
Capability { id, label, tab, panel, tip, status: Status::Ready, more: &[] } Capability { id, label, tab, panel, tip, status: Status::Ready, more: &[], covers: &[] }
} }
const fn ready_more( const fn ready_more(
@@ -208,12 +211,23 @@ const fn ready_more(
Capability { more, ..ready(id, label, tab, panel, tip) } Capability { more, ..ready(id, label, tab, panel, tip) }
} }
const fn covering(
id: &'static str,
label: &'static str,
tab: Tab,
panel: &'static str,
tip: &'static str,
covers: &'static [&'static str],
) -> Capability {
Capability { covers, ..ready(id, label, tab, panel, tip) }
}
const fn soon(id: &'static str, label: &'static str, tab: Tab, panel: &'static str, milestone: &'static str, tip: &'static str) -> Capability { const fn soon(id: &'static str, label: &'static str, tab: Tab, panel: &'static str, milestone: &'static str, tip: &'static str) -> Capability {
Capability { id, label, tab, panel, tip, status: Status::Planned { milestone }, more: &[] } Capability { id, label, tab, panel, tip, status: Status::Planned { milestone }, more: &[], covers: &[] }
} }
const fn deck(id: &'static str, label: &'static str, format: &'static str, tip: &'static str) -> Capability { const fn deck(id: &'static str, label: &'static str, format: &'static str, tip: &'static str) -> Capability {
Capability { id, label, tab: Tab::Solve, panel: "Decks", tip, status: Status::Deck { format, milestone: "M3" }, more: &[] } Capability { id, label, tab: Tab::Solve, panel: "Decks", tip, status: Status::Deck { format, milestone: "M3" }, more: &[], covers: &[] }
} }
use Tab::*; use Tab::*;
@@ -277,29 +291,21 @@ pub static CATALOGUE: &[Capability] = &[
"Quad or triangle mesh of a planar face or polygon in the XY plane for plane stress, plane strain and axisymmetric models; edges tagged", "Quad or triangle mesh of a planar face or polygon in the XY plane for plane stress, plane strain and axisymmetric models; edges tagged",
), ),
soon("mesh.quad.faces", "Quad Faces", Mesh, "Structured Hex", "M5", "All-quad meshes of faces on shared edge nodes"), soon("mesh.quad.faces", "Quad Faces", Mesh, "Structured Hex", "M5", "All-quad meshes of faces on shared edge nodes"),
ready_more( covering(
"mesh.hex.mapped", "mesh.hex.generate",
"Mapped Hex", "Hex Mesh",
Mesh, Mesh,
"Structured Hex", "Structured Hex",
"Mapped hex mesh of a six-sided block (curved faces allowed)", "Structured hex mesh of a body: automatic, or extruded, swept, revolved or mapped (the Method drop-down)",
&["mesh.hex.generate"], &["mesh.hex.sweep", "mesh.hex.mapped"],
), ),
ready_more( covering(
"mesh.hex.sweep", "mesh.cells",
"Sweep Hex", "Partitioned Mesh",
Mesh, Mesh,
"Structured Hex", "Structured Hex",
"Hex mesh swept along extrusions (several levels), around revolutions, or from a source face to a target", "One mesh of a partitioned part, a method per cell (hex, tet, or all hex as conforming blocks); cells joined across partitions",
&["mesh.hex.generate"], &["mesh.hex.blocks"],
),
ready_more(
"mesh.hex.blocks",
"Block Hex",
Mesh,
"Structured Hex",
"One conforming hex mesh of a part split into six-sided blocks: shared faces meshed once",
&["mesh.cells"],
), ),
soon("mesh.hex.decompose", "Decompose", Mesh, "Structured Hex", "M5", "Find sweepable and mappable regions and propose the splits"), soon("mesh.hex.decompose", "Decompose", Mesh, "Structured Hex", "M5", "Find sweepable and mappable regions and propose the splits"),
soon("mesh.transition", "Transition", Mesh, "Structured Hex", "M5", "Conforming tet–hex interfaces with pyramids"), soon("mesh.transition", "Transition", Mesh, "Structured Hex", "M5", "Conforming tet–hex interfaces with pyramids"),
@@ -452,13 +458,13 @@ pub static CATALOGUE: &[Capability] = &[
"Contact between everything that may touch, or self contact of a surface, with friction (explicit dynamics and nonlinear static steps)", "Contact between everything that may touch, or self contact of a surface, with friction (explicit dynamics and nonlinear static steps)",
&["interaction.contact.edit", "interaction.contact.delete"], &["interaction.contact.edit", "interaction.contact.delete"],
), ),
ready_more( // Its edit and delete rows are General Contact's: they act on contacts of either kind.
ready(
"interaction.contact.pair", "interaction.contact.pair",
"Contact Pair", "Contact Pair",
Interactions, Interactions,
"Contact", "Contact",
"Contact between two surfaces: with friction in explicit dynamics, mortar contact with friction in nonlinear static steps", "Contact between two surfaces: with friction in explicit dynamics, mortar contact with friction in nonlinear static steps",
&["interaction.contact.edit", "interaction.contact.delete"],
), ),
ready( ready(
"interaction.contact.find", "interaction.contact.find",
@@ -566,9 +572,12 @@ pub fn find(id: &str) -> Option<&'static Capability> {
} }
/// The entry that claims a command: the one running it, else the one listing it among its /// The entry that claims a command: the one running it, else the one listing it among its
/// related commands. Every registered command has one (a test checks it). /// related commands or covering it. Every registered command has one (a test checks it).
pub fn entry_of(command: &str) -> Option<&'static Capability> { pub fn entry_of(command: &str) -> Option<&'static Capability> {
CATALOGUE.iter().find(|c| c.command() == Some(command)).or_else(|| CATALOGUE.iter().find(|c| c.more.contains(&command))) CATALOGUE
.iter()
.find(|c| c.command() == Some(command))
.or_else(|| CATALOGUE.iter().find(|c| c.more.contains(&command) || c.covers.contains(&command)))
} }
/// The tab the ribbon follows to when `command` has run from anywhere but the ribbon (a script, /// The tab the ribbon follows to when `command` has run from anywhere but the ribbon (a script,
@@ -611,7 +620,8 @@ pub fn features_page() -> String {
let preset = c.preset(); let preset = c.preset();
let main = let main =
c.command().map(|id| if preset.as_object().is_some_and(|o| !o.is_empty()) { format!("`{id} {preset}`") } else { format!("`{id}`") }); c.command().map(|id| if preset.as_object().is_some_and(|o| !o.is_empty()) { format!("`{id} {preset}`") } else { format!("`{id}`") });
let commands: Vec<String> = main.into_iter().chain(c.more.iter().map(|id| format!("`{id}`"))).collect(); let commands: Vec<String> =
main.into_iter().chain(c.more.iter().map(|id| format!("`{id}`"))).chain(c.covers.iter().map(|id| format!("(`{id}`)"))).collect();
let _ = writeln!(md, "| {} | {} — {} | {status} | {} |", c.panel, c.label, c.tip, commands.join(", ")); let _ = writeln!(md, "| {} | {} — {} | {status} | {} |", c.panel, c.label, c.tip, commands.join(", "));
} }
} }
@@ -641,6 +651,25 @@ mod tests {
} }
} }
/// Each command has one place in the ribbon: no two menu rows (an entry or a drop-down row)
/// run the same command with the same preset, and a command an entry's form covers is no row.
#[test]
fn no_command_appears_twice_across_ribbon_menus() {
let mut rows: Vec<String> = Vec::new();
for c in CATALOGUE {
let entry = c.command().map(|id| format!("{id} {}", c.preset()));
for row in entry.into_iter().chain(c.more.iter().map(|id| format!("{id} {{}}"))) {
assert!(!rows.contains(&row), "{}: `{row}` is in the ribbon twice", c.id);
rows.push(row);
}
}
for c in CATALOGUE {
for m in c.covers {
assert!(!rows.iter().any(|r| r.split(' ').next() == Some(m)), "{}: `{m}` is covered by its form and also a ribbon row", c.id);
}
}
}
#[test] #[test]
fn the_ribbon_follows_each_command_to_its_stage() { fn the_ribbon_follows_each_command_to_its_stage() {
assert_eq!(stage_tab("mesh.hex.sweep"), Some(Tab::Mesh)); assert_eq!(stage_tab("mesh.hex.sweep"), Some(Tab::Mesh));
@@ -655,7 +684,7 @@ mod tests {
assert_eq!(stage_tab("no.such"), None); assert_eq!(stage_tab("no.such"), None);
// A ribbon click (an entry's command, or one of its drop-down's) stays on the tab shown. // A ribbon click (an entry's command, or one of its drop-down's) stays on the tab shown.
for c in CATALOGUE { for c in CATALOGUE {
for command in c.command().into_iter().chain(c.more.iter().copied()) { for command in c.command().into_iter().chain(c.more.iter().copied()).chain(c.covers.iter().copied()) {
assert!(stage_tab(command).is_none_or(|t| t == c.tab), "{}: `{command}` would move the ribbon from {:?}", c.id, c.tab); assert!(stage_tab(command).is_none_or(|t| t == c.tab), "{}: `{command}` would move the ribbon from {:?}", c.id, c.tab);
} }
} }
@@ -669,9 +698,9 @@ mod tests {
assert!(find_command(command).is_some(), "{}: `{command}` is not a registered command", c.id); assert!(find_command(command).is_some(), "{}: `{command}` is not a registered command", c.id);
claimed.push(command); claimed.push(command);
} else { } else {
assert!(c.more.is_empty(), "{}: a planned entry has no commands", c.id); assert!(c.more.is_empty() && c.covers.is_empty(), "{}: a planned entry has no commands", c.id);
} }
for m in c.more { for m in c.more.iter().chain(c.covers) {
assert!(find_command(m).is_some(), "{}: `{m}` is not a registered command", c.id); assert!(find_command(m).is_some(), "{}: `{m}` is not a registered command", c.id);
claimed.push(m); claimed.push(m);
} }
+2 -2
View File
@@ -58,7 +58,7 @@ pub fn matches(query: &str) -> Vec<Hit> {
Hit { id: c.id, label: c.label, place: place.clone(), icon: c.id, soon }, Hit { id: c.id, label: c.label, place: place.clone(), icon: c.id, soon },
)); ));
} }
for m in c.more { for m in c.more.iter().chain(c.covers) {
let label = crate::command_label(m); let label = crate::command_label(m);
if q.is_empty() || format!("{label} {m}").to_lowercase().contains(&q) { if q.is_empty() || format!("{label} {m}").to_lowercase().contains(&q) {
hits.push((rank(label, m), Hit { id: m, label, place: place.clone(), icon: c.id, soon: None })); hits.push((rank(label, m), Hit { id: m, label, place: place.clone(), icon: c.id, soon: None }));
@@ -267,7 +267,7 @@ mod tests {
let soon = hits.iter().position(|h| h.soon.is_some()).unwrap(); let soon = hits.iter().position(|h| h.soon.is_some()).unwrap();
assert!(hits.iter().skip(soon).all(|h| h.soon.is_some()), "coming soon last"); assert!(hits.iter().skip(soon).all(|h| h.soon.is_some()), "coming soon last");
assert_eq!(matches("").len(), { assert_eq!(matches("").len(), {
let mut ids: Vec<&str> = CATALOGUE.iter().flat_map(|c| std::iter::once(c.id).chain(c.more.iter().copied())).collect(); let mut ids: Vec<&str> = CATALOGUE.iter().flat_map(|c| std::iter::once(c.id).chain(c.more.iter().chain(c.covers).copied())).collect();
ids.sort(); ids.sort();
ids.dedup(); ids.dedup();
ids.len() ids.len()
+1
View File
@@ -423,6 +423,7 @@ pub fn describe(app: &ModelApp) -> serde_json::Value {
"panel": c.panel, "panel": c.panel,
"command": c.command(), "command": c.command(),
"more": c.more, "more": c.more,
"covers": c.covers,
"coming_soon": c.milestone(), "coming_soon": c.milestone(),
"enabled": c.command().is_some() && missing.is_none(), "enabled": c.command().is_some() && missing.is_none(),
"unavailable": missing.map(|u| u.to_json()), "unavailable": missing.map(|u| u.to_json()),
+4 -5
View File
@@ -6,7 +6,7 @@ Every entry is a toolbar button. **Ready** entries run their command; **coming s
shown greyed with the roadmap milestone that brings them (ROADMAP.md). Scripts, the command line, shown greyed with the roadmap milestone that brings them (ROADMAP.md). Scripts, the command line,
the control channel and AI agents over MCP (docs/mcp.md) run the same commands. the control channel and AI agents over MCP (docs/mcp.md) run the same commands.
63 ready, 41 coming soon. 62 ready, 41 coming soon.
## Geometry ## Geometry
@@ -33,9 +33,8 @@ the control channel and AI agents over MCP (docs/mcp.md) run the same commands.
| Tet | Delaunay Points — Delaunay tetrahedralization of a point list (debugging aid) | ready | `mesh.delaunay.points` | | Tet | Delaunay Points — Delaunay tetrahedralization of a point list (debugging aid) | ready | `mesh.delaunay.points` |
| 2D | 2D Mesh — Quad or triangle mesh of a planar face or polygon in the XY plane for plane stress, plane strain and axisymmetric models; edges tagged | ready | `mesh.planar` | | 2D | 2D Mesh — Quad or triangle mesh of a planar face or polygon in the XY plane for plane stress, plane strain and axisymmetric models; edges tagged | ready | `mesh.planar` |
| Structured Hex | Quad Faces — All-quad meshes of faces on shared edge nodes | coming soon (M5) | | | Structured Hex | Quad Faces — All-quad meshes of faces on shared edge nodes | coming soon (M5) | |
| Structured Hex | Mapped Hex — Mapped hex mesh of a six-sided block (curved faces allowed) | ready | `mesh.hex.mapped`, `mesh.hex.generate` | | Structured Hex | Hex Mesh — Structured hex mesh of a body: automatic, or extruded, swept, revolved or mapped (the Method drop-down) | ready | `mesh.hex.generate`, (`mesh.hex.sweep`), (`mesh.hex.mapped`) |
| Structured Hex | Sweep Hex — Hex mesh swept along extrusions (several levels), around revolutions, or from a source face to a target | ready | `mesh.hex.sweep`, `mesh.hex.generate` | | Structured Hex | Partitioned Mesh — One mesh of a partitioned part, a method per cell (hex, tet, or all hex as conforming blocks); cells joined across partitions | ready | `mesh.cells`, (`mesh.hex.blocks`) |
| Structured Hex | Block Hex — One conforming hex mesh of a part split into six-sided blocks: shared faces meshed once | ready | `mesh.hex.blocks`, `mesh.cells` |
| Structured Hex | Decompose — Find sweepable and mappable regions and propose the splits | coming soon (M5) | | | Structured Hex | Decompose — Find sweepable and mappable regions and propose the splits | coming soon (M5) | |
| Structured Hex | Transition — Conforming tet–hex interfaces with pyramids | coming soon (M5) | | | Structured Hex | Transition — Conforming tet–hex interfaces with pyramids | coming soon (M5) | |
| Automatic Hex | Octree Hex — Unstructured all-hex mesh of any part: adaptive grid, boundary layers on every sharp edge, untangled and proven valid | ready | `mesh.hex.octree` | | Automatic Hex | Octree Hex — Unstructured all-hex mesh of any part: adaptive grid, boundary layers on every sharp edge, untangled and proven valid | ready | `mesh.hex.octree` |
@@ -104,7 +103,7 @@ the control channel and AI agents over MCP (docs/mcp.md) run the same commands.
| Constraints | Coupling — Kinematic or distributing coupling of a set to a reference point | ready | `model.coupling.create`, `model.coupling.edit`, `model.coupling.delete` | | Constraints | Coupling — Kinematic or distributing coupling of a set to a reference point | ready | `model.coupling.create`, `model.coupling.edit`, `model.coupling.delete` |
| Constraints | Tie — Tie non-matching meshes together (dual mortar constraints) | ready | `interaction.tie`, `interaction.tie.edit`, `interaction.tie.delete` | | Constraints | Tie — Tie non-matching meshes together (dual mortar constraints) | ready | `interaction.tie`, `interaction.tie.edit`, `interaction.tie.delete` |
| Contact | General Contact — Contact between everything that may touch, or self contact of a surface, with friction (explicit dynamics and nonlinear static steps) | ready | `interaction.contact.general`, `interaction.contact.edit`, `interaction.contact.delete` | | Contact | General Contact — Contact between everything that may touch, or self contact of a surface, with friction (explicit dynamics and nonlinear static steps) | ready | `interaction.contact.general`, `interaction.contact.edit`, `interaction.contact.delete` |
| Contact | Contact Pair — Contact between two surfaces: with friction in explicit dynamics, mortar contact with friction in nonlinear static steps | ready | `interaction.contact.pair`, `interaction.contact.edit`, `interaction.contact.delete` | | Contact | Contact Pair — Contact between two surfaces: with friction in explicit dynamics, mortar contact with friction in nonlinear static steps | ready | `interaction.contact.pair` |
| Contact | Find Contacts — Find the parts of the mesh that touch, and make contact pairs of them | ready | `interaction.contact.find` | | Contact | Find Contacts — Find the parts of the mesh that touch, and make contact pairs of them | ready | `interaction.contact.find` |
| Contact | Rigid Wall — Rigid walls for explicit dynamics | coming soon (M10) | | | Contact | Rigid Wall — Rigid walls for explicit dynamics | coming soon (M10) | |
| Connectors | Bolt Load — Bolt pretension | coming soon (M7) | | | Connectors | Bolt Load — Bolt pretension | coming soon (M7) | |