forked from 1b-agent/fieldcraft-upstream
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:
1 parent
f4b6d1dbff
commit
70c5c9b26f
7 files changed
+69
-41
No files matched your search
+2
-3
@@ -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.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.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.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.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.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.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.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 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?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">
|
||||
<title>mesh.hex.blocks</title>
|
||||
<title>mesh.cells</title>
|
||||
<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="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,6 +1,6 @@
|
||||
<?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">
|
||||
<title>mesh.hex.mapped</title>
|
||||
<title>mesh.hex.generate</title>
|
||||
<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="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 |
@@ -155,6 +155,9 @@ pub struct Capability {
|
||||
pub status: Status,
|
||||
/// Related commands in the entry's drop-down (edit, delete…).
|
||||
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 {
|
||||
@@ -194,7 +197,7 @@ impl 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(
|
||||
@@ -208,12 +211,23 @@ const fn ready_more(
|
||||
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 {
|
||||
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 {
|
||||
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::*;
|
||||
@@ -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",
|
||||
),
|
||||
soon("mesh.quad.faces", "Quad Faces", Mesh, "Structured Hex", "M5", "All-quad meshes of faces on shared edge nodes"),
|
||||
ready_more(
|
||||
"mesh.hex.mapped",
|
||||
"Mapped Hex",
|
||||
covering(
|
||||
"mesh.hex.generate",
|
||||
"Hex Mesh",
|
||||
Mesh,
|
||||
"Structured Hex",
|
||||
"Mapped hex mesh of a six-sided block (curved faces allowed)",
|
||||
&["mesh.hex.generate"],
|
||||
"Structured hex mesh of a body: automatic, or extruded, swept, revolved or mapped (the Method drop-down)",
|
||||
&["mesh.hex.sweep", "mesh.hex.mapped"],
|
||||
),
|
||||
ready_more(
|
||||
"mesh.hex.sweep",
|
||||
"Sweep Hex",
|
||||
covering(
|
||||
"mesh.cells",
|
||||
"Partitioned Mesh",
|
||||
Mesh,
|
||||
"Structured Hex",
|
||||
"Hex mesh swept along extrusions (several levels), around revolutions, or from a source face to a target",
|
||||
&["mesh.hex.generate"],
|
||||
),
|
||||
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"],
|
||||
"One mesh of a partitioned part, a method per cell (hex, tet, or all hex as conforming blocks); cells joined across partitions",
|
||||
&["mesh.hex.blocks"],
|
||||
),
|
||||
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"),
|
||||
@@ -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)",
|
||||
&["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",
|
||||
"Contact Pair",
|
||||
Interactions,
|
||||
"Contact",
|
||||
"Contact between two surfaces: with friction in explicit dynamics, mortar contact with friction in nonlinear static steps",
|
||||
&["interaction.contact.edit", "interaction.contact.delete"],
|
||||
),
|
||||
ready(
|
||||
"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
|
||||
/// 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> {
|
||||
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,
|
||||
@@ -611,7 +620,8 @@ pub fn features_page() -> String {
|
||||
let preset = c.preset();
|
||||
let main =
|
||||
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(", "));
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
fn the_ribbon_follows_each_command_to_its_stage() {
|
||||
assert_eq!(stage_tab("mesh.hex.sweep"), Some(Tab::Mesh));
|
||||
@@ -655,7 +684,7 @@ mod tests {
|
||||
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.
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -669,9 +698,9 @@ mod tests {
|
||||
assert!(find_command(command).is_some(), "{}: `{command}` is not a registered command", c.id);
|
||||
claimed.push(command);
|
||||
} 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);
|
||||
claimed.push(m);
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
));
|
||||
}
|
||||
for m in c.more {
|
||||
for m in c.more.iter().chain(c.covers) {
|
||||
let label = crate::command_label(m);
|
||||
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 }));
|
||||
@@ -267,7 +267,7 @@ mod tests {
|
||||
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_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.dedup();
|
||||
ids.len()
|
||||
|
||||
@@ -423,6 +423,7 @@ pub fn describe(app: &ModelApp) -> serde_json::Value {
|
||||
"panel": c.panel,
|
||||
"command": c.command(),
|
||||
"more": c.more,
|
||||
"covers": c.covers,
|
||||
"coming_soon": c.milestone(),
|
||||
"enabled": c.command().is_some() && missing.is_none(),
|
||||
"unavailable": missing.map(|u| u.to_json()),
|
||||
|
||||
+4
-5
@@ -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,
|
||||
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
|
||||
|
||||
@@ -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` |
|
||||
| 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 | Mapped Hex — Mapped hex mesh of a six-sided block (curved faces allowed) | ready | `mesh.hex.mapped`, `mesh.hex.generate` |
|
||||
| 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 | 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 | 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 | 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 | 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) | |
|
||||
| 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 | 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 | 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 | Rigid Wall — Rigid walls for explicit dynamics | coming soon (M10) | |
|
||||
| Connectors | Bolt Load — Bolt pretension | coming soon (M7) | |
|
||||
|
||||
Reference in new issue
Block a user