diff --git a/AGENTS.md b/AGENTS.md index 89dc394..4d063cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ -# ModelCraft — instructions for agents +# FieldCraft — instructions for agents -ModelCraft is a clean-room, open-source, pure-Rust CAE modeling application: it turns geometry +FieldCraft is a clean-room, open-source, pure-Rust CAE modeling application: it turns geometry into analysis-ready finite element models (volume meshes, materials, sections, boundary conditions, loads and steps), writes them as Abaqus input, Nastran bulk data and LS-DYNA keyword decks, and solves linear static and modal problems with its own solver. It targets the @@ -12,8 +12,8 @@ storytold Craft apps (`storytold/cadcraft`, `vectorcraft`, `photocraft`, …). C them (MIT OR Apache-2.0) is noted in ATTRIBUTION.md. ## Product name -ModelCraft everywhere: crates `modelcraft-*`, binaries `modelcraft` and `modelcraft-cli`, -`.modelcraft` models, `MODELCRAFT_*` environment variables, docs, CI and packaging. +FieldCraft everywhere: crates `fieldcraft-*`, binaries `fieldcraft` and `fieldcraft-cli`, +`.fieldcraft` models, `FIELDCRAFT_*` environment variables, docs, CI and packaging. ## Start every session here 1. Read `ROADMAP.md` (milestones, what is next) and your task. @@ -22,7 +22,7 @@ ModelCraft everywhere: crates `modelcraft-*`, binaries `modelcraft` and `modelcr 4. Meshing work: `docs/meshing/literature.md` (the papers each algorithm comes from). ## Never crash -People trust ModelCraft with their models; a crash loses their work. **This outranks feature work.** +People trust FieldCraft with their models; a crash loses their work. **This outranks feature work.** - **No panics in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!`; no `unsafe` (`unsafe_code = "forbid"`). - **Errors are `Result`** through the crate's error type and `?`. An unfinished feature @@ -32,7 +32,7 @@ People trust ModelCraft with their models; a crash loses their work. **This outr - **Bound every iterative algorithm.** Mesh refinement, smoothing, untangling and solver iterations have explicit iteration and element-count caps and report when they stop early. - **SolidCraft calls are guarded:** everything that calls `solidcraft-kernel` goes through - `solidcraft_kernel::guard` inside `modelcraft-geometry`, which turns a panic into an error. + `solidcraft_kernel::guard` inside `fieldcraft-geometry`, which turns a panic into an error. - **Last-resort guard:** `Session::execute` runs every command under `catch_unwind`; an error or an escaped panic restores the model and reports `EngineError`. - **Prove it:** every crash fix lands with a regression test (see `hostile_params_never_panic`). @@ -64,7 +64,7 @@ People trust ModelCraft with their models; a crash loses their work. **This outr Code_Aster, OpenRadioss, Elmer, deal.II, FreeFEM, Salome, OpenFOAM — don't even read their sources. Permissively licensed code (MIT, BSD, Apache-2.0, MPL-2.0) may be read for reference; anything adapted is noted in ATTRIBUTION.md with its licence. -- **Assets — absolutely essential.** ModelCraft contains **no iconography, images, fonts or +- **Assets — absolutely essential.** FieldCraft contains **no iconography, images, fonts or artwork from commercial CAE products — ever.** Icons are original artwork made with VectorCraft: commit the editable `.vectorcraft` source beside the exported SVG/PNG. Any file asset must be original or openly licensed and **must have a row in ATTRIBUTION.md** (`cargo xtask assets` @@ -85,7 +85,7 @@ People trust ModelCraft with their models; a crash loses their work. **This outr `crates/geometry` may use `solidcraft-*`.** - **Determinism.** The same input gives the same mesh, deck and CPU solver result, bit for bit, on every platform: no hash-order iteration into output, no thread-count-dependent results (threads - go through `modelcraft-parallel`: fixed blocks, sums in a fixed order, docs/adr/0001). GPU + go through `fieldcraft-parallel`: fixed blocks, sums in a fixed order, docs/adr/0001). GPU solver backends (wgpu, f32) are the one exception: deterministic per device, and checked against the CPU reference within a stated tolerance. - **Robust geometry.** Meshing decisions (orientation, in-sphere, intersection) use exact or @@ -104,7 +104,7 @@ People trust ModelCraft with their models; a crash loses their work. **This outr 2. When the change is done: review it once with `/simplify`, then `cargo fmt --all` and `cargo clippy -p --all-targets -- -D warnings` for each crate you touched, once. Regenerate what your change affects: `docs/features.md` when commands change - (`cargo run -p modelcraft-cli -- features > docs/features.md`), `docs/oracle.md` when + (`cargo run -p fieldcraft-cli -- features > docs/features.md`), `docs/oracle.md` when oracle cases or solver numerics change (`cargo xtask oracle`). 3. Fold every fix into the commit it fixes, never a separate "fmt", "clippy", "review fixes" or "regenerate features.md" commit: `git commit --fixup `, then @@ -128,21 +128,21 @@ People trust ModelCraft with their models; a crash loses their work. **This outr demand (`gh workflow run CI --ref `). Hostile-input tests run commands on sessions with small work caps (`Session::limits`) so refusals are instant; never lower the production caps for a test. -- **Publishing**: `origin` is github.com/jyviko/modelcraft. Changes land through pull requests +- **Publishing**: `origin` is github.com/jyviko/fieldcraft. Changes land through pull requests as the workflow above describes; the coordinator reads every merged diff and sends findings back as fix-forward work. Never force-push `main`. Never commit `plan/`, `.agent-files/`, `worktrees/` or `target*/`; check `git ls-files` before pushing. ## Running -- `cargo run -p modelcraft-cli -- commands` lists every command with its parameters. -- `cargo run -p modelcraft-cli -- run script.json` runs a script (a JSON array of +- `cargo run -p fieldcraft-cli -- commands` lists every command with its parameters. +- `cargo run -p fieldcraft-cli -- run script.json` runs a script (a JSON array of `{"command", "params"}`) and prints the model summary. -- `cargo run --release -p modelcraft -- --sample [cantilever|impact] --control PORT` opens the desktop app with a +- `cargo run --release -p fieldcraft -- --sample [cantilever|impact] --control PORT` opens the desktop app with a sample model and a control channel on `127.0.0.1:PORT` (docs/control-protocol.md). For UI work, look at the result: `ui.capture {path}` (the whole window drawn offscreen; works with the screen locked), `ui.screenshot {path}` (a presented frame) or `ui.render {path}` (the model only), then read the PNG. -- `cargo run -p modelcraft-cli -- mcp [--connect 127.0.0.1:PORT]` serves MCP on stdio (docs/mcp.md): +- `cargo run -p fieldcraft-cli -- mcp [--connect 127.0.0.1:PORT]` serves MCP on stdio (docs/mcp.md): the same commands as tools, headless or bridged to the running app. - Build with `CARGO_TARGET_DIR=/target`. Parallel agents: separate target dirs, one worktree each. diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index c9d145a..6b6a2cf 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -4,9 +4,9 @@ Every non-code asset in this repository (images, icons, fonts, example geometry) with its author, source and licence. `cargo xtask assets` (part of `cargo xtask ci`) fails if an asset file is missing from this table. -**Policy (mandatory):** ModelCraft contains **no iconography, images, artwork, fonts, templates, +**Policy (mandatory):** FieldCraft contains **no iconography, images, artwork, fonts, templates, example decks or help text from commercial CAE products.** Every asset is original work by -ModelCraft contributors or third-party material under an open licence. Screenshots of commercial +FieldCraft contributors or third-party material under an open licence. Screenshots of commercial software are never committed. Icons are made with VectorCraft; each icon's `.vectorcraft` source is committed and listed here with its exports. @@ -14,144 +14,144 @@ is committed and listed here with its exports. | File | Author | Source | Licence | |---|---|---|---| -| docs/media/ibeam-analysis.mp4, docs/media/ibeam-analysis.gif | ModelCraft contributors | Original: the ModelCraft window captured during an MCP session, generated by `tools/demo-video/make.py` | MIT OR Apache-2.0 | -| crates/decks/tests/golden/nastran/*.bdf | ModelCraft contributors | Written by our Nastran writer from our own test models (crates/decks/tests/nastran.rs) | MIT OR Apache-2.0 | -| assets/icons/assembly.instance.svg, assets/icons/assembly.instance.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/assembly.instance.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/deck.read.svg, assets/icons/deck.read.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.read.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/deck.write.abaqus.svg, assets/icons/deck.write.abaqus.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.write.abaqus.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/deck.write.lsdyna.svg, assets/icons/deck.write.lsdyna.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.write.lsdyna.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/deck.write.nastran.svg, assets/icons/deck.write.nastran.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.write.nastran.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/edit.redo.svg, assets/icons/edit.redo.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/edit.redo.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/edit.undo.svg, assets/icons/edit.undo.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/edit.undo.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/file.open.svg, assets/icons/file.open.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.open.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/file.recover.svg, assets/icons/file.recover.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.recover.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/file.save_as.svg, assets/icons/file.save_as.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.save_as.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/file.save.svg, assets/icons/file.save.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.save.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/geometry.bodies.svg, assets/icons/geometry.bodies.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.bodies.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/geometry.datum.svg, assets/icons/geometry.datum.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.datum.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/geometry.import.mesh.svg, assets/icons/geometry.import.mesh.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.import.mesh.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/geometry.midsurface.svg, assets/icons/geometry.midsurface.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.midsurface.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/geometry.open.svg, assets/icons/geometry.open.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.open.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/geometry.partition.svg, assets/icons/geometry.partition.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.partition.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/geometry.tessellate.svg, assets/icons/geometry.tessellate.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.tessellate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.bolt.svg, assets/icons/interaction.bolt.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.bolt.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.connector.svg, assets/icons/interaction.connector.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.connector.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.contact.find.svg, assets/icons/interaction.contact.find.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.contact.find.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.contact.general.svg, assets/icons/interaction.contact.general.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.contact.general.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.contact.pair.svg, assets/icons/interaction.contact.pair.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.contact.pair.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.fsi.svg, assets/icons/interaction.fsi.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.fsi.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.mass.create.svg, assets/icons/model.mass.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.mass.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.rigid_wall.svg, assets/icons/interaction.rigid_wall.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.rigid_wall.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.spring.create.svg, assets/icons/model.spring.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.spring.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/interaction.tie.svg, assets/icons/interaction.tie.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.tie.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.delaunay.points.svg, assets/icons/mesh.delaunay.points.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.delaunay.points.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.element.add.svg, assets/icons/mesh.element.add.vectorcraft | ModelCraft 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 | ModelCraft 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 | ModelCraft 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.mapped.svg, assets/icons/mesh.hex.mapped.vectorcraft | ModelCraft 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.octree.svg, assets/icons/mesh.hex.octree.vectorcraft | ModelCraft 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 | ModelCraft 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 | ModelCraft 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 | ModelCraft 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 | ModelCraft 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 | ModelCraft 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.merge.svg, assets/icons/mesh.node.merge.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.node.merge.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.order.quadratic.svg, assets/icons/mesh.order.quadratic.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.order.quadratic.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.quad.faces.svg, assets/icons/mesh.quad.faces.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.quad.faces.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.quality.svg, assets/icons/mesh.quality.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.quality.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.region.remesh.svg, assets/icons/mesh.region.remesh.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.region.remesh.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.size.adapt.svg, assets/icons/mesh.size.adapt.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.size.adapt.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.size.global.svg, assets/icons/mesh.size.global.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.size.global.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.size.seed.svg, assets/icons/mesh.size.seed.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.size.seed.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.smooth.svg, assets/icons/mesh.smooth.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.smooth.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.surface.check.svg, assets/icons/mesh.surface.check.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.surface.check.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.tag.svg, assets/icons/mesh.tag.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.tag.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.tet.generate.svg, assets/icons/mesh.tet.generate.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.tet.generate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.planar.svg, assets/icons/mesh.planar.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.planar.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.transition.svg, assets/icons/mesh.transition.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.transition.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.untangle.svg, assets/icons/mesh.untangle.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.untangle.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/mesh.verify.svg, assets/icons/mesh.verify.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.verify.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.activity.svg, assets/icons/model.activity.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.activity.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.amplitude.svg, assets/icons/model.amplitude.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.amplitude.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.boundary_condition.create.svg, assets/icons/model.boundary_condition.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.boundary_condition.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.check.svg, assets/icons/model.check.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.check.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.coupling.create.svg, assets/icons/model.coupling.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.coupling.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.fluid_domain.boundary.svg, assets/icons/model.fluid_domain.boundary.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.fluid_domain.boundary.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.fluid_domain.create.svg, assets/icons/model.fluid_domain.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.fluid_domain.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.info.svg, assets/icons/model.info.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.info.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.load.create.svg, assets/icons/model.load.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.load.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.material.create.svg, assets/icons/model.material.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.material.damage.svg, assets/icons/model.material.damage.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.damage.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.material.eos.svg, assets/icons/model.material.eos.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.eos.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.material.hyperelastic.svg, assets/icons/model.material.hyperelastic.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.hyperelastic.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.material.plasticity.svg, assets/icons/model.material.plasticity.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.plasticity.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.material.thermal.svg, assets/icons/model.material.thermal.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.thermal.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.orientation.svg, assets/icons/model.orientation.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.orientation.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.reference_point.create.svg, assets/icons/model.reference_point.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.reference_point.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.section.composite.svg, assets/icons/model.section.composite.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.section.composite.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.section.create.svg, assets/icons/model.section.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.section.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.set.create.svg, assets/icons/model.set.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.set.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.step.activate.svg, assets/icons/model.step.activate.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.activate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.step.create.svg, assets/icons/model.step.create.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.step.explicit.svg, assets/icons/model.step.explicit.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.explicit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.step.heat.svg, assets/icons/model.step.heat.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.heat.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.step.nonlinear.svg, assets/icons/model.step.nonlinear.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.nonlinear.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/model.units.svg, assets/icons/model.units.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.units.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.animate.svg, assets/icons/results.animate.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.animate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.contour.svg, assets/icons/results.contour.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.contour.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.convergence.svg, assets/icons/results.convergence.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.convergence.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.cut.svg, assets/icons/results.cut.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.cut.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.deformed.svg, assets/icons/results.deformed.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.deformed.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.display_group.svg, assets/icons/results.display_group.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.display_group.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.free_body.svg, assets/icons/results.free_body.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.free_body.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.modes.svg, assets/icons/results.modes.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.modes.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.path.svg, assets/icons/results.path.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.path.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.probe.svg, assets/icons/solve.probe.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.probe.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.reactions.svg, assets/icons/results.reactions.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.reactions.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.read.svg, assets/icons/results.read.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.read.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.streamlines.svg, assets/icons/results.streamlines.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.streamlines.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/results.xy.svg, assets/icons/results.xy.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.xy.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.blast.svg, assets/icons/solve.blast.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.blast.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.explicit.svg, assets/icons/solve.explicit.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.explicit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.fluid.svg, assets/icons/solve.fluid.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.fluid.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.frequency.svg, assets/icons/solve.frequency.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.frequency.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.fsi.svg, assets/icons/solve.fsi.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.fsi.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.static.svg, assets/icons/solve.static.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.static.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/solve.run.svg, assets/icons/solve.run.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.run.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/file.sample.svg, assets/icons/file.sample.vectorcraft | ModelCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.sample.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.eye.svg, assets/icons/ui.eye.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.eye.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.fit.svg, assets/icons/ui.fit.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.fit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.help.svg, assets/icons/ui.help.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.help.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.home.svg, assets/icons/ui.home.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.home.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.moon.svg, assets/icons/ui.moon.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.moon.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.orbit.svg, assets/icons/ui.orbit.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.orbit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.pan.svg, assets/icons/ui.pan.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.pan.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.perspective.svg, assets/icons/ui.perspective.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.perspective.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.search.svg, assets/icons/ui.search.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.search.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.style.svg, assets/icons/ui.style.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.style.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.sun.svg, assets/icons/ui.sun.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.sun.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.views.svg, assets/icons/ui.views.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.views.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/icons/ui.zoom.svg, assets/icons/ui.zoom.vectorcraft | ModelCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.zoom.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | -| assets/app-icon/icon.vectorcraft, assets/app-icon/icon.svg, assets/app-icon/icon.png | ModelCraft contributors | Original application icon (an L bracket with a bolt hole under a structured hexahedral mesh, on a rounded-square backdrop), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs); editable source, SVG and 1024 × 1024 PNG export | MIT OR Apache-2.0 | -| assets/app-icon/icon-small.vectorcraft, assets/app-icon/icon-small.svg, assets/app-icon/icon-small.png | ModelCraft contributors | Original simplified application icon for 16–32 px (no mesh or floor grid), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | -| assets/app-icon/layers/01-floor.vectorcraft, assets/app-icon/ModelCraft.icon/Assets/01-floor.svg | ModelCraft contributors | Original isometric floor grid with a radial fade (Default appearance), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | -| assets/app-icon/layers/01-floor-dark.vectorcraft, assets/app-icon/ModelCraft.icon/Assets/01-floor-dark.svg | ModelCraft contributors | Original isometric floor grid with a radial fade (Dark appearance), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | -| assets/app-icon/layers/02-part.vectorcraft, assets/app-icon/ModelCraft.icon/Assets/02-part.svg | ModelCraft contributors | Original bracket faces with transparent gaps at the shared edges, drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | -| assets/app-icon/layers/03-mesh.vectorcraft, assets/app-icon/ModelCraft.icon/Assets/03-mesh.svg | ModelCraft contributors | Original hexahedral mesh lines (O-grid around the hole) clipped to the bracket's faces, drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | -| assets/app-icon/ModelCraft.icon/icon.json | ModelCraft contributors | Icon Composer composition of the layers above (Default and Dark floor grid), written by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | -| assets/app-icon/Assets.car | ModelCraft contributors | Apple actool compilation of assets/app-icon/ModelCraft.icon; contains original artwork only | MIT OR Apache-2.0 | -| assets/app-icon/icon.icns | ModelCraft contributors | iconutil compilation of assets/app-icon/icon.iconset | MIT OR Apache-2.0 | -| assets/app-icon/icon.ico | ModelCraft contributors | ImageMagick compilation (16–256 px) of sips exports of assets/app-icon/icon-small.png (≤ 32 px) and assets/app-icon/icon.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_16x16.png | ModelCraft contributors | sips export (16 px) of assets/app-icon/icon-small.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_16x16@2x.png | ModelCraft contributors | sips export (32 px) of assets/app-icon/icon-small.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_32x32.png | ModelCraft contributors | sips export (32 px) of assets/app-icon/icon-small.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_32x32@2x.png | ModelCraft contributors | sips export (64 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_128x128.png | ModelCraft contributors | sips export (128 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_128x128@2x.png | ModelCraft contributors | sips export (256 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_256x256.png | ModelCraft contributors | sips export (256 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_256x256@2x.png | ModelCraft contributors | sips export (512 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_512x512.png | ModelCraft contributors | sips export (512 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | -| assets/app-icon/icon.iconset/icon_512x512@2x.png | ModelCraft contributors | sips export (1024 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | +| docs/media/ibeam-analysis.mp4, docs/media/ibeam-analysis.gif | FieldCraft contributors | Original: the FieldCraft window captured during an MCP session, generated by `tools/demo-video/make.py` | MIT OR Apache-2.0 | +| crates/decks/tests/golden/nastran/*.bdf | FieldCraft contributors | Written by our Nastran writer from our own test models (crates/decks/tests/nastran.rs) | MIT OR Apache-2.0 | +| assets/icons/assembly.instance.svg, assets/icons/assembly.instance.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/assembly.instance.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/deck.read.svg, assets/icons/deck.read.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.read.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/deck.write.abaqus.svg, assets/icons/deck.write.abaqus.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.write.abaqus.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/deck.write.lsdyna.svg, assets/icons/deck.write.lsdyna.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.write.lsdyna.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/deck.write.nastran.svg, assets/icons/deck.write.nastran.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.write.nastran.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/edit.redo.svg, assets/icons/edit.redo.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/edit.redo.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/edit.undo.svg, assets/icons/edit.undo.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/edit.undo.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/file.open.svg, assets/icons/file.open.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.open.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/file.recover.svg, assets/icons/file.recover.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.recover.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/file.save_as.svg, assets/icons/file.save_as.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.save_as.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/file.save.svg, assets/icons/file.save.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.save.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/geometry.bodies.svg, assets/icons/geometry.bodies.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.bodies.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/geometry.datum.svg, assets/icons/geometry.datum.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.datum.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/geometry.import.mesh.svg, assets/icons/geometry.import.mesh.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.import.mesh.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/geometry.midsurface.svg, assets/icons/geometry.midsurface.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.midsurface.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/geometry.open.svg, assets/icons/geometry.open.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.open.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/geometry.partition.svg, assets/icons/geometry.partition.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.partition.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/geometry.tessellate.svg, assets/icons/geometry.tessellate.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/geometry.tessellate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.bolt.svg, assets/icons/interaction.bolt.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.bolt.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.connector.svg, assets/icons/interaction.connector.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.connector.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.contact.find.svg, assets/icons/interaction.contact.find.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.contact.find.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.contact.general.svg, assets/icons/interaction.contact.general.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.contact.general.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.contact.pair.svg, assets/icons/interaction.contact.pair.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.contact.pair.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.fsi.svg, assets/icons/interaction.fsi.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.fsi.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.mass.create.svg, assets/icons/model.mass.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.mass.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.rigid_wall.svg, assets/icons/interaction.rigid_wall.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.rigid_wall.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.spring.create.svg, assets/icons/model.spring.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.spring.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/interaction.tie.svg, assets/icons/interaction.tie.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/interaction.tie.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.delaunay.points.svg, assets/icons/mesh.delaunay.points.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.delaunay.points.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.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.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.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 | +| assets/icons/mesh.node.merge.svg, assets/icons/mesh.node.merge.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.node.merge.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.order.quadratic.svg, assets/icons/mesh.order.quadratic.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.order.quadratic.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.quad.faces.svg, assets/icons/mesh.quad.faces.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.quad.faces.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.quality.svg, assets/icons/mesh.quality.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.quality.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.region.remesh.svg, assets/icons/mesh.region.remesh.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.region.remesh.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.size.adapt.svg, assets/icons/mesh.size.adapt.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.size.adapt.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.size.global.svg, assets/icons/mesh.size.global.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.size.global.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.size.seed.svg, assets/icons/mesh.size.seed.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.size.seed.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.smooth.svg, assets/icons/mesh.smooth.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.smooth.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.surface.check.svg, assets/icons/mesh.surface.check.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.surface.check.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.tag.svg, assets/icons/mesh.tag.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.tag.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.tet.generate.svg, assets/icons/mesh.tet.generate.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.tet.generate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.planar.svg, assets/icons/mesh.planar.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.planar.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.transition.svg, assets/icons/mesh.transition.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.transition.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.untangle.svg, assets/icons/mesh.untangle.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.untangle.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/mesh.verify.svg, assets/icons/mesh.verify.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/mesh.verify.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.activity.svg, assets/icons/model.activity.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.activity.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.amplitude.svg, assets/icons/model.amplitude.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.amplitude.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.boundary_condition.create.svg, assets/icons/model.boundary_condition.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.boundary_condition.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.check.svg, assets/icons/model.check.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.check.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.coupling.create.svg, assets/icons/model.coupling.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.coupling.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.fluid_domain.boundary.svg, assets/icons/model.fluid_domain.boundary.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.fluid_domain.boundary.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.fluid_domain.create.svg, assets/icons/model.fluid_domain.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.fluid_domain.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.info.svg, assets/icons/model.info.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.info.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.load.create.svg, assets/icons/model.load.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.load.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.material.create.svg, assets/icons/model.material.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.material.damage.svg, assets/icons/model.material.damage.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.damage.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.material.eos.svg, assets/icons/model.material.eos.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.eos.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.material.hyperelastic.svg, assets/icons/model.material.hyperelastic.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.hyperelastic.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.material.plasticity.svg, assets/icons/model.material.plasticity.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.plasticity.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.material.thermal.svg, assets/icons/model.material.thermal.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.material.thermal.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.orientation.svg, assets/icons/model.orientation.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.orientation.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.reference_point.create.svg, assets/icons/model.reference_point.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.reference_point.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.section.composite.svg, assets/icons/model.section.composite.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.section.composite.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.section.create.svg, assets/icons/model.section.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.section.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.set.create.svg, assets/icons/model.set.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.set.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.step.activate.svg, assets/icons/model.step.activate.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.activate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.step.create.svg, assets/icons/model.step.create.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.create.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.step.explicit.svg, assets/icons/model.step.explicit.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.explicit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.step.heat.svg, assets/icons/model.step.heat.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.heat.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.step.nonlinear.svg, assets/icons/model.step.nonlinear.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.step.nonlinear.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/model.units.svg, assets/icons/model.units.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/model.units.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.animate.svg, assets/icons/results.animate.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.animate.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.contour.svg, assets/icons/results.contour.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.contour.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.convergence.svg, assets/icons/results.convergence.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.convergence.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.cut.svg, assets/icons/results.cut.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.cut.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.deformed.svg, assets/icons/results.deformed.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.deformed.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.display_group.svg, assets/icons/results.display_group.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.display_group.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.free_body.svg, assets/icons/results.free_body.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.free_body.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.modes.svg, assets/icons/results.modes.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.modes.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.path.svg, assets/icons/results.path.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.path.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.probe.svg, assets/icons/solve.probe.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.probe.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.reactions.svg, assets/icons/results.reactions.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.reactions.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.read.svg, assets/icons/results.read.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.read.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.streamlines.svg, assets/icons/results.streamlines.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.streamlines.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/results.xy.svg, assets/icons/results.xy.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/results.xy.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.blast.svg, assets/icons/solve.blast.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.blast.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.explicit.svg, assets/icons/solve.explicit.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.explicit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.fluid.svg, assets/icons/solve.fluid.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.fluid.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.frequency.svg, assets/icons/solve.frequency.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.frequency.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.fsi.svg, assets/icons/solve.fsi.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.fsi.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.static.svg, assets/icons/solve.static.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.static.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/solve.run.svg, assets/icons/solve.run.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/solve.run.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/file.sample.svg, assets/icons/file.sample.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/file.sample.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.eye.svg, assets/icons/ui.eye.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.eye.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.fit.svg, assets/icons/ui.fit.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.fit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.help.svg, assets/icons/ui.help.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.help.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.home.svg, assets/icons/ui.home.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.home.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.moon.svg, assets/icons/ui.moon.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.moon.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.orbit.svg, assets/icons/ui.orbit.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.orbit.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.pan.svg, assets/icons/ui.pan.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.pan.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.perspective.svg, assets/icons/ui.perspective.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.perspective.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.search.svg, assets/icons/ui.search.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.search.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.style.svg, assets/icons/ui.style.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.style.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.sun.svg, assets/icons/ui.sun.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.sun.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.views.svg, assets/icons/ui.views.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.views.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/icons/ui.zoom.svg, assets/icons/ui.zoom.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.zoom.json` (`cargo xtask icons`) | MIT OR Apache-2.0 | +| assets/app-icon/icon.vectorcraft, assets/app-icon/icon.svg, assets/app-icon/icon.png | FieldCraft contributors | Original application icon (an L bracket with a bolt hole under a structured hexahedral mesh, on a rounded-square backdrop), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs); editable source, SVG and 1024 × 1024 PNG export | MIT OR Apache-2.0 | +| assets/app-icon/icon-small.vectorcraft, assets/app-icon/icon-small.svg, assets/app-icon/icon-small.png | FieldCraft contributors | Original simplified application icon for 16–32 px (no mesh or floor grid), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | +| assets/app-icon/layers/01-floor.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/01-floor.svg | FieldCraft contributors | Original isometric floor grid with a radial fade (Default appearance), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | +| assets/app-icon/layers/01-floor-dark.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg | FieldCraft contributors | Original isometric floor grid with a radial fade (Dark appearance), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | +| assets/app-icon/layers/02-part.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/02-part.svg | FieldCraft contributors | Original bracket faces with transparent gaps at the shared edges, drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | +| assets/app-icon/layers/03-mesh.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/03-mesh.svg | FieldCraft contributors | Original hexahedral mesh lines (O-grid around the hole) clipped to the bracket's faces, drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | +| assets/app-icon/FieldCraft.icon/icon.json | FieldCraft contributors | Icon Composer composition of the layers above (Default and Dark floor grid), written by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 | +| assets/app-icon/Assets.car | FieldCraft contributors | Apple actool compilation of assets/app-icon/FieldCraft.icon; contains original artwork only | MIT OR Apache-2.0 | +| assets/app-icon/icon.icns | FieldCraft contributors | iconutil compilation of assets/app-icon/icon.iconset | MIT OR Apache-2.0 | +| assets/app-icon/icon.ico | FieldCraft contributors | ImageMagick compilation (16–256 px) of sips exports of assets/app-icon/icon-small.png (≤ 32 px) and assets/app-icon/icon.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_16x16.png | FieldCraft contributors | sips export (16 px) of assets/app-icon/icon-small.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_16x16@2x.png | FieldCraft contributors | sips export (32 px) of assets/app-icon/icon-small.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_32x32.png | FieldCraft contributors | sips export (32 px) of assets/app-icon/icon-small.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_32x32@2x.png | FieldCraft contributors | sips export (64 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_128x128.png | FieldCraft contributors | sips export (128 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_128x128@2x.png | FieldCraft contributors | sips export (256 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_256x256.png | FieldCraft contributors | sips export (256 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_256x256@2x.png | FieldCraft contributors | sips export (512 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_512x512.png | FieldCraft contributors | sips export (512 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | +| assets/app-icon/icon.iconset/icon_512x512@2x.png | FieldCraft contributors | sips export (1024 px) of assets/app-icon/icon.png | MIT OR Apache-2.0 | | crates/ui-egui/fonts/Inter-SemiBold.ttf | The Inter Project Authors | https://github.com/rsms/inter release v4.1 (Inter-4.1.zip, extras/ttf/Inter-SemiBold.ttf), unmodified; licence text in crates/ui-egui/fonts/OFL.txt; the interface's caps labels (theme::LABEL_FAMILY) | SIL OFL 1.1 | ## Adapted code @@ -166,7 +166,7 @@ is committed and listed here with its exports. | crates/ui-egui/src/gpu.rs, crates/ui-egui/src/theme.rs (tokens and their values), crates/ui-egui/src/control.rs, crates/ui-egui/src/lib.rs (input injection, screenshots), crates/ui-egui/src/toolbar.rs (workspace switcher, tabs, panels with drop-downs, fitting), crates/ui-egui/src/appbar.rs (application bar; the mark is drawn from assets/app-icon/icon-small.svg's coordinates), crates/ui-egui/src/titlebar.rs (caption buttons, drag and resize zones; SolidCraft took it from VectorCraft), crates/ui-egui/src/frame.rs (windows, heading bands), crates/ui-egui/src/browser.rs (row drawing), crates/ui-egui/src/palette.rs (command line and search), crates/ui-egui/src/timeline.rs (timeline strip, transport), crates/ui-egui/src/viewport.rs (navigation bar, orientation gizmo), crates/ui-egui/src/files.rs (background opening, file pickers as host services) | SolidCraft `crates/ui-egui/src/` (titlebar.rs also VectorCraft `crates/ui-egui/src/titlebar.rs`) | MIT OR Apache-2.0 | | crates/io/src/fs.rs (atomic writes) | SolidCraft `crates/io/src/safe_write.rs` | MIT OR Apache-2.0 | | crates/io/src/recovery.rs (recovery folder, entries, locks) | SolidCraft `crates/engine/src/recovery.rs` | MIT OR Apache-2.0 | -| apps/modelcraft/src/control_server.rs | SolidCraft `apps/solidcraft/src/control_server.rs` | MIT OR Apache-2.0 | +| apps/fieldcraft/src/control_server.rs | SolidCraft `apps/solidcraft/src/control_server.rs` | MIT OR Apache-2.0 | | crates/mcp/src/ (JSON-RPC framing, MCP lifecycle, tool schema checks, the control-channel backend, base64) | SolidCraft `crates/mcp/src/` | MIT OR Apache-2.0 | | crates/io/src/roots.rs (the rules of read and write roots) | VectorCraft's automation roots (docs/mcp.md there) | MIT OR Apache-2.0 | diff --git a/Cargo.lock b/Cargo.lock index 255b5c6..f1a4ecd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,6 +1171,209 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693" +[[package]] +name = "fieldcraft" +version = "0.1.0" +dependencies = [ + "eframe", + "egui", + "fieldcraft-engine", + "fieldcraft-io", + "fieldcraft-ui-egui", + "log", + "rfd", + "serde_json", +] + +[[package]] +name = "fieldcraft-cli" +version = "0.1.0" +dependencies = [ + "fieldcraft-engine", + "fieldcraft-io", + "fieldcraft-mcp", + "serde_json", +] + +[[package]] +name = "fieldcraft-decks" +version = "0.1.0" +dependencies = [ + "fieldcraft-model", + "serde_json", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-engine" +version = "0.1.0" +dependencies = [ + "fieldcraft-decks", + "fieldcraft-explicit", + "fieldcraft-fem", + "fieldcraft-geometry", + "fieldcraft-io", + "fieldcraft-lbm", + "fieldcraft-mesh", + "fieldcraft-model", + "fieldcraft-parallel", + "fieldcraft-render", + "fieldcraft-solver", + "log", + "serde", + "serde_json", + "thiserror 2.0.21", + "zip", +] + +[[package]] +name = "fieldcraft-explicit" +version = "0.1.0" +dependencies = [ + "fieldcraft-fem", + "fieldcraft-model", + "fieldcraft-parallel", + "serde", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-fem" +version = "0.1.0" +dependencies = [ + "fieldcraft-model", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-geometry" +version = "0.1.0" +dependencies = [ + "fieldcraft-model", + "quick-xml", + "serde_json", + "solidcraft-doc", + "solidcraft-engine", + "solidcraft-geom", + "solidcraft-io", + "solidcraft-kernel", + "thiserror 2.0.21", + "zip", +] + +[[package]] +name = "fieldcraft-io" +version = "0.1.0" +dependencies = [ + "fieldcraft-model", + "log", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.21", + "zip", +] + +[[package]] +name = "fieldcraft-lbm" +version = "0.1.0" +dependencies = [ + "fieldcraft-mesh", + "fieldcraft-model", + "fieldcraft-parallel", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-mcp" +version = "0.1.0" +dependencies = [ + "fieldcraft-engine", + "fieldcraft-io", + "fieldcraft-render", + "fieldcraft-testkit", + "serde_json", +] + +[[package]] +name = "fieldcraft-mesh" +version = "0.1.0" +dependencies = [ + "fieldcraft-model", + "num-bigint", + "num-rational", + "num-traits", + "robust", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-model" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-parallel" +version = "0.1.0" +dependencies = [ + "rayon", +] + +[[package]] +name = "fieldcraft-render" +version = "0.1.0" +dependencies = [ + "fieldcraft-model", + "png", + "serde", + "serde_json", +] + +[[package]] +name = "fieldcraft-solver" +version = "0.1.0" +dependencies = [ + "fieldcraft-fem", + "fieldcraft-model", + "fieldcraft-parallel", + "serde", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-testkit" +version = "0.1.0" +dependencies = [ + "fieldcraft-engine", + "fieldcraft-geometry", + "fieldcraft-mesh", + "serde_json", + "thiserror 2.0.21", +] + +[[package]] +name = "fieldcraft-ui-egui" +version = "0.1.0" +dependencies = [ + "craft-ui", + "egui", + "egui-wgpu", + "fieldcraft-decks", + "fieldcraft-engine", + "fieldcraft-explicit", + "fieldcraft-geometry", + "fieldcraft-model", + "fieldcraft-render", + "log", + "resvg", + "serde", + "serde_json", +] + [[package]] name = "find-msvc-tools" version = "0.1.14" @@ -1855,209 +2058,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "modelcraft" -version = "0.1.0" -dependencies = [ - "eframe", - "egui", - "log", - "modelcraft-engine", - "modelcraft-io", - "modelcraft-ui-egui", - "rfd", - "serde_json", -] - -[[package]] -name = "modelcraft-cli" -version = "0.1.0" -dependencies = [ - "modelcraft-engine", - "modelcraft-io", - "modelcraft-mcp", - "serde_json", -] - -[[package]] -name = "modelcraft-decks" -version = "0.1.0" -dependencies = [ - "modelcraft-model", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-engine" -version = "0.1.0" -dependencies = [ - "log", - "modelcraft-decks", - "modelcraft-explicit", - "modelcraft-fem", - "modelcraft-geometry", - "modelcraft-io", - "modelcraft-lbm", - "modelcraft-mesh", - "modelcraft-model", - "modelcraft-parallel", - "modelcraft-render", - "modelcraft-solver", - "serde", - "serde_json", - "thiserror 2.0.21", - "zip", -] - -[[package]] -name = "modelcraft-explicit" -version = "0.1.0" -dependencies = [ - "modelcraft-fem", - "modelcraft-model", - "modelcraft-parallel", - "serde", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-fem" -version = "0.1.0" -dependencies = [ - "modelcraft-model", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-geometry" -version = "0.1.0" -dependencies = [ - "modelcraft-model", - "quick-xml", - "serde_json", - "solidcraft-doc", - "solidcraft-engine", - "solidcraft-geom", - "solidcraft-io", - "solidcraft-kernel", - "thiserror 2.0.21", - "zip", -] - -[[package]] -name = "modelcraft-io" -version = "0.1.0" -dependencies = [ - "log", - "modelcraft-model", - "serde", - "serde_json", - "sha2", - "thiserror 2.0.21", - "zip", -] - -[[package]] -name = "modelcraft-lbm" -version = "0.1.0" -dependencies = [ - "modelcraft-mesh", - "modelcraft-model", - "modelcraft-parallel", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-mcp" -version = "0.1.0" -dependencies = [ - "modelcraft-engine", - "modelcraft-io", - "modelcraft-render", - "modelcraft-testkit", - "serde_json", -] - -[[package]] -name = "modelcraft-mesh" -version = "0.1.0" -dependencies = [ - "modelcraft-model", - "num-bigint", - "num-rational", - "num-traits", - "robust", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-model" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-parallel" -version = "0.1.0" -dependencies = [ - "rayon", -] - -[[package]] -name = "modelcraft-render" -version = "0.1.0" -dependencies = [ - "modelcraft-model", - "png", - "serde", - "serde_json", -] - -[[package]] -name = "modelcraft-solver" -version = "0.1.0" -dependencies = [ - "modelcraft-fem", - "modelcraft-model", - "modelcraft-parallel", - "serde", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-testkit" -version = "0.1.0" -dependencies = [ - "modelcraft-engine", - "modelcraft-geometry", - "modelcraft-mesh", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "modelcraft-ui-egui" -version = "0.1.0" -dependencies = [ - "craft-ui", - "egui", - "egui-wgpu", - "log", - "modelcraft-decks", - "modelcraft-engine", - "modelcraft-explicit", - "modelcraft-geometry", - "modelcraft-model", - "modelcraft-render", - "resvg", - "serde", - "serde_json", -] - [[package]] name = "moxcms" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index ca8c485..9f42b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,26 +7,26 @@ version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.95" -authors = ["ModelCraft contributors"] -repository = "https://github.com/jyviko/modelcraft" +authors = ["FieldCraft contributors"] +repository = "https://github.com/jyviko/fieldcraft" [workspace.dependencies] # internal -modelcraft-model = { path = "crates/model" } -modelcraft-parallel = { path = "crates/parallel" } -modelcraft-geometry = { path = "crates/geometry" } -modelcraft-mesh = { path = "crates/mesh" } -modelcraft-decks = { path = "crates/decks" } -modelcraft-io = { path = "crates/io" } -modelcraft-fem = { path = "crates/fem" } -modelcraft-solver = { path = "crates/solver" } -modelcraft-explicit = { path = "crates/explicit" } -modelcraft-lbm = { path = "crates/lbm" } -modelcraft-engine = { path = "crates/engine" } -modelcraft-testkit = { path = "crates/testkit" } -modelcraft-render = { path = "crates/render" } -modelcraft-ui-egui = { path = "crates/ui-egui" } -modelcraft-mcp = { path = "crates/mcp" } +fieldcraft-model = { path = "crates/model" } +fieldcraft-parallel = { path = "crates/parallel" } +fieldcraft-geometry = { path = "crates/geometry" } +fieldcraft-mesh = { path = "crates/mesh" } +fieldcraft-decks = { path = "crates/decks" } +fieldcraft-io = { path = "crates/io" } +fieldcraft-fem = { path = "crates/fem" } +fieldcraft-solver = { path = "crates/solver" } +fieldcraft-explicit = { path = "crates/explicit" } +fieldcraft-lbm = { path = "crates/lbm" } +fieldcraft-engine = { path = "crates/engine" } +fieldcraft-testkit = { path = "crates/testkit" } +fieldcraft-render = { path = "crates/render" } +fieldcraft-ui-egui = { path = "crates/ui-egui" } +fieldcraft-mcp = { path = "crates/mcp" } # foundation thiserror = "2" @@ -38,7 +38,7 @@ log = "0.4" zip = { version = "3", default-features = false, features = ["deflate"] } quick-xml = "0.41" -# content hashes and part checksums of .modelcraft files (the version SolidCraft uses) +# content hashes and part checksums of .fieldcraft files (the version SolidCraft uses) sha2 = "0.10" # images (PNG snapshots and screenshots) @@ -62,7 +62,7 @@ solidcraft-geom = { git = "https://github.com/bherbruck/solidcraft", rev = "de14 solidcraft-kernel = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" } solidcraft-doc = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" } solidcraft-io = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" } -# Runs SolidCraft command scripts (the test corpus recipes); only behind modelcraft-geometry's +# Runs SolidCraft command scripts (the test corpus recipes); only behind fieldcraft-geometry's # `scripts` feature. solidcraft-engine = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" } @@ -85,7 +85,7 @@ opt-level = 1 # The lattice Boltzmann kernels are compute loops over millions of cells; their verification # runs (tests) need them optimised. -[profile.dev.package.modelcraft-lbm] +[profile.dev.package.fieldcraft-lbm] opt-level = 3 # Tests and lints (`cargo xtask ci`, GitHub CI). Debug assertions are off because truck's @@ -101,7 +101,7 @@ incremental = false [profile.ci.package."*"] opt-level = 2 -[profile.ci.package.modelcraft-lbm] +[profile.ci.package.fieldcraft-lbm] opt-level = 3 [profile.release] @@ -111,7 +111,7 @@ codegen-units = 16 debug = "line-tables-only" # SolidCraft builds truck and egui-wgpu with local fixes (its vendor/ directory). Patches don't -# reach a dependent workspace, so ModelCraft applies the same ones from the same pinned commit; +# reach a dependent workspace, so FieldCraft applies the same ones from the same pinned commit; # keep this list and the rev in step with SolidCraft's [patch.crates-io]. [patch.crates-io] truck-shapeops = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" } diff --git a/LICENSE-MIT b/LICENSE-MIT index 019bdae..39ab0d9 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 ModelCraft contributors +Copyright (c) 2026 FieldCraft contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NOTICE b/NOTICE index 815f97b..7f0a070 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ -ModelCraft -Copyright (c) 2026 ModelCraft contributors. +FieldCraft +Copyright (c) 2026 FieldCraft contributors. Licensed under the MIT License or the Apache License, Version 2.0, at your option (LICENSE-MIT, LICENSE-APACHE). @@ -13,7 +13,7 @@ Copyright (c) RICOS Co. Ltd.), some with SolidCraft's modifications. Some code is adapted from sibling projects (SolidCraft, CADCraft), licensed MIT OR Apache-2.0; ATTRIBUTION.md lists the files. -ModelCraft is an independent project and is not affiliated with, sponsored by or endorsed by +FieldCraft is an independent project and is not affiliated with, sponsored by or endorsed by Dassault Systèmes, MSC Software, Siemens Digital Industries Software or Ansys, Inc. Abaqus, Nastran, MSC Nastran, Simcenter Nastran and LS-DYNA are trademarks of their respective owners; they are used here only to describe file compatibility. diff --git a/README.md b/README.md index 6c3650b..5a210a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ModelCraft +# FieldCraft An open-source, clean-room CAE modeling application in pure Rust: geometry to analysis-ready finite element models, with robust tetrahedral and hexahedral meshing, solver decks for Abaqus, @@ -21,9 +21,9 @@ deformed shape (tip deflection 0.176 mm; beam theory with shear 0.177 mm). ```sh cargo xtask ci # fmt, assets, licences, layers, clippy, tests, wasm cargo xtask test --heavy # the heavy (ignored) tests and the doc tests -cargo run -p modelcraft-cli -- commands # every command and its parameters -cargo run -p modelcraft-cli -- run script.json # run a command script -cargo run -p modelcraft-cli -- mcp # MCP server on stdio for AI agents (docs/mcp.md) +cargo run -p fieldcraft-cli -- commands # every command and its parameters +cargo run -p fieldcraft-cli -- run script.json # run a command script +cargo run -p fieldcraft-cli -- mcp # MCP server on stdio for AI agents (docs/mcp.md) ``` Rust 1.95 or later, with the `wasm32-unknown-unknown` target for `cargo xtask ci`. @@ -32,7 +32,7 @@ Rust 1.95 or later, with the `wasm32-unknown-unknown` target for `cargo xtask ci - [Architecture](docs/architecture.md): crates, layers and the meshing pipeline - [Libraries and licences](docs/adr/0001-libraries.md) -- [AI agents (MCP)](docs/mcp.md): `claude mcp add modelcraft -- /path/to/modelcraft-cli mcp` +- [AI agents (MCP)](docs/mcp.md): `claude mcp add fieldcraft -- /path/to/fieldcraft-cli mcp` - [Control protocol](docs/control-protocol.md): driving the desktop app - [Contributing](CONTRIBUTING.md) and [agent instructions](AGENTS.md) @@ -41,6 +41,6 @@ Rust 1.95 or later, with the `wasm32-unknown-unknown` target for `cargo xtask ci Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option. Notices are in [NOTICE](NOTICE); third-party licences in [THIRD-PARTY-LICENSES.txt](THIRD-PARTY-LICENSES.txt). -ModelCraft is an independent project, not affiliated with, sponsored or endorsed by Dassault +FieldCraft is an independent project, not affiliated with, sponsored or endorsed by Dassault Systèmes, MSC Software, Siemens or Ansys. Abaqus, Nastran, MSC Nastran, Simcenter Nastran and LS-DYNA are trademarks of their owners and are named only to describe file compatibility. diff --git a/ROADMAP.md b/ROADMAP.md index 6bd860a..2d1a7cf 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # Roadmap -ModelCraft's goal is CAE modeling on par with Abaqus/CAE, Nastran pre-processors and LS-PrePost, +FieldCraft's goal is CAE modeling on par with Abaqus/CAE, Nastran pre-processors and LS-PrePost, with hexahedral meshing as the centre of gravity, plus its own linear solver. Abaqus/CAE is the reference for scope (its documentation is excellent and its solver strong); its application is where we aim to be clearly better: parametric geometry from SolidCraft, sets that survive @@ -19,18 +19,18 @@ hex meshes today and it gives the best elements where stresses are read. | Milestone | Scope | Status | |---|---|---| | M0 Foundation | Workspace, quality gates, FE model, command engine with rollback and panic guard, CLI, geometry boundary on SolidCraft | done | -| M1 Geometry in and surface preparation | Open `.solidcraft` designs with persistent face names, assemblies placed (done); face-tagged 3MF (done); test corpus, `cargo xtask corpus` (done); exact predicates (done); quality metrics and certified validity gate (done); weld, closed/manifold/orientation and self-intersection checks, feature edges (`modelcraft-mesh::surface`, `mesh.surface.check`, done) | in progress | -| M2 Tet meshing and size field | Size field (global, per face and edge seeds, curvature and proximity; the `modelcraft_mesh::size::SizeField` trait and edge-division helpers are in); Bowyer–Watson Delaunay (exact, symbolic perturbation, verified; `modelcraft-mesh::delaunay`, done); robust CDT boundary recovery (Diazzi et al. 2023: implicit LNC Steiner points on segments, gift-wrapping facet recovery, inside/outside labelling, B-rep face tags on boundary tet faces, few-ulp rounding repair checked by the validity gate; `modelcraft-mesh::cdt` and `mesh.tet.generate`, done: all 20 valid corpus parts); Delaunay refinement, ODT smoothing, sliver removal; tet4 and tet10 with mid-side nodes on the B-rep; mesh verify per set | in progress | -| M3 First deck | Sets and surfaces bound to SolidCraft face, edge, vertex and body names, resolved after meshing; materials (isotropic and orthotropic elastic, density) with a unit-system label and consistency check; sections (solid, shell, beam profiles) assigned by set; element formulation per region mapped to each solver; BCs (fixed, displacement, symmetry); loads (force and moment on reference points, pressure, gravity); reference points with kinematic and distributing coupling; steps (linear static, frequency) with explicit per-step load and BC tables and output presets; `model.check` naming every missing or bad item; `model.*` and `mesh.tag` commands (model definition: done); writers for Abaqus input (done: `deck.write`), Nastran SOL 101/103 (done: `deck.write`) and LS-DYNA implicit (done); the `.modelcraft` file (text definition, binary mesh, geometry references reopened on load, journal; atomic saves, autosave and recovery; undo/redo on the journal: done) | in progress | -| M4 Own solver and first results | Element library per docs/elements.md (1D and 2D first); Linear static and modal solver (tet4/10, wedge6, hex8, hex20; sparse direct, Lanczos), verified against analytic solutions (solver: done — `modelcraft-fem` kernels, `modelcraft-solver` with nested dissection, sparse Cholesky and block Lanczos, `solve.static`/`solve.frequency`/`solve.probe`, patch, cantilever, Lamé, Kirsch and beam-frequency tests; reference points with kinematic and distributing couplings and moments by master–slave constraint elimination, verified by rigid motion, clamping, Saint-Venant tension, bending and tube torsion; 1D elements I: rod2/rod3 trusses, point masses, rotary inertia, springs and dashpots in both solvers and all three decks, verified by the method of joints, the three-bar truss, spring–mass, two-mass chain and torsional pendulum frequencies, and damped decay; 2D elements: plane stress, plane strain and axisymmetric tri3, tri6, quad4 (incompatible modes QM6), quad8 with two degrees of freedom per node, centrifugal loads, `mesh.planar` 2D meshes of polygons and planar faces, verified by patch tests, Kirsch, Lamé (plane strain and axisymmetric), rotating discs and cylinders, and a plane stress cantilever; deck mapping to Abaqus CPS/CPE/CAX, Nastran membranes, LS-DYNA ELFORM 12/13); shells and beams (done: quad4 MITC4 and tri3 MITC3 flat shells with a Hughes–Brezzi drilling rotation, two-node Timoshenko beams with profile properties and orientations, six degrees of freedom at shell and beam nodes shared with solids and trusses, section forces and surface stresses, verified by shell patch tests, Navier's plate, cantilever and portal-frame beams, and the Scordelis–Lo and pinched-cylinder benchmarks); results display (done: `results.*` commands, deformed shape to scale, banded contours of displacement, von Mises, principal and component stresses with a legend, mode shapes with their frequencies and animation, probing a node by clicking it, reaction force and moment sums per set, stale results marked after an edit; explicit steps: field frames by slider or animation with contours per frame, energy histories plotted) | started | -| M5 Structured hex | 2D quad meshing of faces (`modelcraft-mesh::quad`: transfinite mapped meshes, free all-quad meshes of polygons with holes on prescribed boundary nodes, exact planar quad validity, shared `SizeField` and even edge divisions; done for planar faces); mapped hex of six-sided regions (trivariate TFI, curved faces) and sweep meshing (multi-level extrusions with imprinted cross-sections, revolutions, one-to-one sweeps through linking faces; interval assignment, hex8/hex20 on the geometry, B-rep face tags, `mesh.hex.generate`/`sweep`/`mapped`: done, 16 of 21 corpus parts all-hex); conforming interfaces and tet–hex transition; automatic decomposition into sweepable and mappable regions, proposed as SolidCraft splits | started | +| M1 Geometry in and surface preparation | Open `.solidcraft` designs with persistent face names, assemblies placed (done); face-tagged 3MF (done); test corpus, `cargo xtask corpus` (done); exact predicates (done); quality metrics and certified validity gate (done); weld, closed/manifold/orientation and self-intersection checks, feature edges (`fieldcraft-mesh::surface`, `mesh.surface.check`, done) | in progress | +| M2 Tet meshing and size field | Size field (global, per face and edge seeds, curvature and proximity; the `fieldcraft_mesh::size::SizeField` trait and edge-division helpers are in); Bowyer–Watson Delaunay (exact, symbolic perturbation, verified; `fieldcraft-mesh::delaunay`, done); robust CDT boundary recovery (Diazzi et al. 2023: implicit LNC Steiner points on segments, gift-wrapping facet recovery, inside/outside labelling, B-rep face tags on boundary tet faces, few-ulp rounding repair checked by the validity gate; `fieldcraft-mesh::cdt` and `mesh.tet.generate`, done: all 20 valid corpus parts); Delaunay refinement, ODT smoothing, sliver removal; tet4 and tet10 with mid-side nodes on the B-rep; mesh verify per set | in progress | +| M3 First deck | Sets and surfaces bound to SolidCraft face, edge, vertex and body names, resolved after meshing; materials (isotropic and orthotropic elastic, density) with a unit-system label and consistency check; sections (solid, shell, beam profiles) assigned by set; element formulation per region mapped to each solver; BCs (fixed, displacement, symmetry); loads (force and moment on reference points, pressure, gravity); reference points with kinematic and distributing coupling; steps (linear static, frequency) with explicit per-step load and BC tables and output presets; `model.check` naming every missing or bad item; `model.*` and `mesh.tag` commands (model definition: done); writers for Abaqus input (done: `deck.write`), Nastran SOL 101/103 (done: `deck.write`) and LS-DYNA implicit (done); the `.fieldcraft` file (text definition, binary mesh, geometry references reopened on load, journal; atomic saves, autosave and recovery; undo/redo on the journal: done) | in progress | +| M4 Own solver and first results | Element library per docs/elements.md (1D and 2D first); Linear static and modal solver (tet4/10, wedge6, hex8, hex20; sparse direct, Lanczos), verified against analytic solutions (solver: done — `fieldcraft-fem` kernels, `fieldcraft-solver` with nested dissection, sparse Cholesky and block Lanczos, `solve.static`/`solve.frequency`/`solve.probe`, patch, cantilever, Lamé, Kirsch and beam-frequency tests; reference points with kinematic and distributing couplings and moments by master–slave constraint elimination, verified by rigid motion, clamping, Saint-Venant tension, bending and tube torsion; 1D elements I: rod2/rod3 trusses, point masses, rotary inertia, springs and dashpots in both solvers and all three decks, verified by the method of joints, the three-bar truss, spring–mass, two-mass chain and torsional pendulum frequencies, and damped decay; 2D elements: plane stress, plane strain and axisymmetric tri3, tri6, quad4 (incompatible modes QM6), quad8 with two degrees of freedom per node, centrifugal loads, `mesh.planar` 2D meshes of polygons and planar faces, verified by patch tests, Kirsch, Lamé (plane strain and axisymmetric), rotating discs and cylinders, and a plane stress cantilever; deck mapping to Abaqus CPS/CPE/CAX, Nastran membranes, LS-DYNA ELFORM 12/13); shells and beams (done: quad4 MITC4 and tri3 MITC3 flat shells with a Hughes–Brezzi drilling rotation, two-node Timoshenko beams with profile properties and orientations, six degrees of freedom at shell and beam nodes shared with solids and trusses, section forces and surface stresses, verified by shell patch tests, Navier's plate, cantilever and portal-frame beams, and the Scordelis–Lo and pinched-cylinder benchmarks); results display (done: `results.*` commands, deformed shape to scale, banded contours of displacement, von Mises, principal and component stresses with a legend, mode shapes with their frequencies and animation, probing a node by clicking it, reaction force and moment sums per set, stale results marked after an edit; explicit steps: field frames by slider or animation with contours per frame, energy histories plotted) | started | +| M5 Structured hex | 2D quad meshing of faces (`fieldcraft-mesh::quad`: transfinite mapped meshes, free all-quad meshes of polygons with holes on prescribed boundary nodes, exact planar quad validity, shared `SizeField` and even edge divisions; done for planar faces); mapped hex of six-sided regions (trivariate TFI, curved faces) and sweep meshing (multi-level extrusions with imprinted cross-sections, revolutions, one-to-one sweeps through linking faces; interval assignment, hex8/hex20 on the geometry, B-rep face tags, `mesh.hex.generate`/`sweep`/`mapped`: done, 16 of 21 corpus parts all-hex); conforming interfaces and tet–hex transition; automatic decomposition into sweepable and mappable regions, proposed as SolidCraft splits | started | | M6 Automatic hex | Octree/grid-dual all-hex for any part; hex-dominant by tet recombination; polycube for blocky parts; smoothing, untangling, boundary padding, layers through thin walls; mesh editing and orphan-mesh import | planned | | M7 Assemblies and contact | Part instances from SolidCraft occurrences; tie between non-matching meshes; general and pairwise contact with automatic pair detection, friction; bolt pretension, connectors; amplitudes, multiple steps; nonlinear static steps in decks | planned | | M8 Materials, explicit, shells, composites | Plasticity, hyperelasticity, damage data per solver; explicit dynamics decks (Abaqus/Explicit, LS-DYNA); midsurface shells; composite layups and material orientations, continuum shells on swept hex; hybrid (mixed u–p) hex8/hex20/tet10 for nearly incompressible materials (done); heat transfer (solids done: steady and θ-method transient conduction with flux, convection and sources, thermal strain in static steps (solids and 2D) and explicit dynamics (with plasticity), coupled temperature–displacement, `solve.heat`; deck output, 2D/shell/beam conduction planned) | started | | M9 Post-processing and deck readers | Results readers for public formats (Nastran OP2, LS-DYNA d3plot, Abaqus `.fil`); XY and path plots, cuts, display groups, free-body forces; deck readers that keep unknown keywords; mesh convergence studies | planned | -| M10 Explicit dynamics | Shared element and material kernels with the implicit solver (`fem`); central-difference solver with lumped mass, stable time step, one-point hex8 with hourglass control and nodal-pressure tet4, bulk viscosity, energy accounting (X1 core: done — `modelcraft-explicit`, `explicit_dynamic` steps with initial velocities, prescribed motion and planar rigid walls, `solve.explicit`/`solve.history`, small-strain linear elastic; bar wave, rigid-wall impact, energy balance and cantilever-period tests); large-deformation materials (X2: done — updated Lagrangian elements, Jaumann/Hughes–Winget and Green–Naghdi/Flanagan–Taylor rates, J2 bilinear/table plasticity, Johnson–Cook strength with heating and damage, erosion; materials gain `plastic`, `damage`, `erosion` with `model.material.plasticity`/`damage`; Dienes shear, rigid rotation, J2 uniaxial and Taylor-bar tests); contact (global search, penalty with friction, rigid walls); runs on hex and tet meshes | started | +| M10 Explicit dynamics | Shared element and material kernels with the implicit solver (`fem`); central-difference solver with lumped mass, stable time step, one-point hex8 with hourglass control and nodal-pressure tet4, bulk viscosity, energy accounting (X1 core: done — `fieldcraft-explicit`, `explicit_dynamic` steps with initial velocities, prescribed motion and planar rigid walls, `solve.explicit`/`solve.history`, small-strain linear elastic; bar wave, rigid-wall impact, energy balance and cantilever-period tests); large-deformation materials (X2: done — updated Lagrangian elements, Jaumann/Hughes–Winget and Green–Naghdi/Flanagan–Taylor rates, J2 bilinear/table plasticity, Johnson–Cook strength with heating and damage, erosion; materials gain `plastic`, `damage`, `erosion` with `model.material.plasticity`/`damage`; Dienes shear, rigid rotation, J2 uniaxial and Taylor-bar tests); contact (global search, penalty with friction, rigid walls); runs on hex and tet meshes | started | | M11 Equations of state and shells | Polynomial, ideal-gas, Mie–Grüneisen, JWL with programmed burn, tabulated; energy coupling; shock verification (Rankine–Hugoniot, Sod, Noh, Sedov); Belytschko–Lin–Tsay shells with self-contact on quad meshes | planned | -| M12 CFD: lattice Boltzmann | D3Q19/D3Q27 on grids voxelised from the face-tagged surface; TRT, regularised and cumulant collision; interpolated walls, inlets and outlets; LES; octree refinement shared with the hex mesher; wgpu backend (deterministic per device, checked against the CPU) (CPU core: done — `modelcraft-lbm` with D3Q19 TRT and Guo forcing, halfway bounce-back and moving walls, slip and periodic faces, Zou–He velocity and pressure faces, exact voxelisation of obstacle bodies, momentum-exchange forces per face; fluid materials, fluid domains and fluid steps in the model; `model.fluid_domain.*`, `solve.fluid`; verified by Poiseuille channel and pipe, Couette, Taylor–Green decay, sphere-array Stokes drag and mass conservation; Schäfer–Turek 2D-1 as a benchmark) | started | +| M12 CFD: lattice Boltzmann | D3Q19/D3Q27 on grids voxelised from the face-tagged surface; TRT, regularised and cumulant collision; interpolated walls, inlets and outlets; LES; octree refinement shared with the hex mesher; wgpu backend (deterministic per device, checked against the CPU) (CPU core: done — `fieldcraft-lbm` with D3Q19 TRT and Guo forcing, halfway bounce-back and moving walls, slip and periodic faces, Zou–He velocity and pressure faces, exact voxelisation of obstacle bodies, momentum-exchange forces per face; fluid materials, fluid domains and fluid steps in the model; `model.fluid_domain.*`, `solve.fluid`; verified by Poiseuille channel and pipe, Couette, Taylor–Green decay, sphere-array Stokes drag and mass conservation; Schäfer–Turek 2D-1 as a benchmark) | started | | M13 Fluid–structure interaction | Immersed-boundary coupling of the lattice Boltzmann fluid with the explicit or implicit structure; interface force transfer; sub-cycling; Aitken then interface quasi-Newton coupling; free-surface flow | planned | | M14 Blast and impact fluids | EOS-driven fluids in the explicit solver: ALE multi-material or SPH, with fluid–structure contact | planned | | Continuous: verification and validation | Every capability checked against theory (closed forms), experiments (published measurements) and published benchmarks (NAFEMS, vendor benchmark and verification manuals such as Abaqus's), each case our own model in `tests/oracle/` with its cited reference and tolerance; `cargo xtask oracle` runs them and writes docs/oracle.md; once a case matches, our result is frozen as a fixture so fidelity cannot regress (docs/verification.md). Runner, fixtures and CI job: done; 26 theory cases (patch tests of every solid element, cantilevers, Lamé in 3D, plane strain and axisymmetric, Kirsch in 3D and plane stress, rotating disc and cylinder, beam frequencies, couplings, explicit waves, impact and J2 plasticity, CDT invariants), 9 published benchmarks (MacNeal–Harder solid tests, Scordelis–Lo with solids, NAFEMS LE1 in 3D and 2D, LE10, FV32) and 9 pending cases with citations (shells, thermal, static plasticity, Taylor bar, CFD); `mesh.block` mapped blocks and 2D patches build their meshes | started | diff --git a/SECURITY.md b/SECURITY.md index 7d466f5..a715c3a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,9 @@ # Security policy -ModelCraft is young; only `main` and the latest release get fixes. +FieldCraft is young; only `main` and the latest release get fixes. Please **don't open a public issue** for a security problem. Report it privately through the -repository's [Security tab](https://github.com/jyviko/modelcraft/security/advisories/new) with +repository's [Security tab](https://github.com/jyviko/fieldcraft/security/advisories/new) with what you found, how to reproduce it (a model, deck or script), the version and platform, and the impact you expect. diff --git a/THIRD-PARTY-LICENSES.txt b/THIRD-PARTY-LICENSES.txt index 866ed9c..cafec14 100644 --- a/THIRD-PARTY-LICENSES.txt +++ b/THIRD-PARTY-LICENSES.txt @@ -1,8 +1,8 @@ -Third-party software in ModelCraft +Third-party software in FieldCraft -ModelCraft (the desktop app, modelcraft-cli and the web app) is built from the 444 crates below. +FieldCraft (the desktop app, fieldcraft-cli and the web app) is built from the 444 crates below. Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files. -ModelCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE. +FieldCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE. accesskit 0.24.1 (MIT OR Apache-2.0) accesskit_atspi_common 0.18.1 (MIT OR Apache-2.0) @@ -13024,7 +13024,7 @@ freely, subject to the following restrictions: ================================================================================ Crates whose package carries no licence file (their licence is named in Cargo.toml; the MIT and Apache-2.0 -texts are as in ModelCraft's own LICENSE-MIT and LICENSE-APACHE) +texts are as in FieldCraft's own LICENSE-MIT and LICENSE-APACHE) ================================================================================ accesskit 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit) diff --git a/apps/fieldcraft-cli/Cargo.toml b/apps/fieldcraft-cli/Cargo.toml new file mode 100644 index 0000000..cfb9b71 --- /dev/null +++ b/apps/fieldcraft-cli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "fieldcraft-cli" +description = "FieldCraft from the command line: list commands, run scripts, serve MCP" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] +fieldcraft-engine = { workspace = true } +fieldcraft-io = { workspace = true } +fieldcraft-mcp = { workspace = true } +serde_json = { workspace = true } diff --git a/apps/modelcraft-cli/src/main.rs b/apps/fieldcraft-cli/src/main.rs similarity index 83% rename from apps/modelcraft-cli/src/main.rs rename to apps/fieldcraft-cli/src/main.rs index 24159f5..5c7c228 100644 --- a/apps/modelcraft-cli/src/main.rs +++ b/apps/fieldcraft-cli/src/main.rs @@ -1,27 +1,27 @@ -//! modelcraft-cli commands -//! modelcraft-cli run -//! modelcraft-cli features -//! modelcraft-cli mcp [--connect ADDR | --headless] [--automation-read-root DIR] [--automation-write-root DIR] +//! fieldcraft-cli commands +//! fieldcraft-cli run +//! fieldcraft-cli features +//! fieldcraft-cli mcp [--connect ADDR | --headless] [--automation-read-root DIR] [--automation-write-root DIR] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] #![forbid(unsafe_code)] use std::path::Path; use std::process::ExitCode; -use modelcraft_engine::Session; +use fieldcraft_engine::Session; use serde_json::json; const USAGE: &str = "\ usage: - modelcraft-cli commands list every command with its parameters - modelcraft-cli run run a script (a JSON array of {\"command\", \"params\"}) + fieldcraft-cli commands list every command with its parameters + fieldcraft-cli run run a script (a JSON array of {\"command\", \"params\"}) and print the model summary - modelcraft-cli features print docs/features.md (every capability, ready or + fieldcraft-cli features print docs/features.md (every capability, ready or coming soon) from the capability catalogue - modelcraft-cli mcp [options] serve the Model Context Protocol on stdin/stdout + fieldcraft-cli mcp [options] serve the Model Context Protocol on stdin/stdout (docs/mcp.md): --headless an in-process session (the default) - --connect ADDR drive the app at ADDR (`modelcraft --control PORT`); + --connect ADDR drive the app at ADDR (`fieldcraft --control PORT`); headless when it cannot be reached --automation-read-root DIR files may be read only inside DIR --automation-write-root DIR files may be written only inside DIR (with either root, @@ -35,13 +35,13 @@ fn main() -> ExitCode { let args: Vec = std::env::args().skip(1).collect(); let result = match args.first().map(String::as_str) { Some("commands") => { - for c in modelcraft_engine::commands() { + for c in fieldcraft_engine::commands() { println!("{:<24} {:<18} {}", c.id, c.label, c.params); } Ok(()) } Some("features") => { - print!("{}", modelcraft_engine::catalogue::features_page()); + print!("{}", fieldcraft_engine::catalogue::features_page()); Ok(()) } Some("mcp") => mcp(args.get(1..).unwrap_or(&[])), @@ -62,7 +62,7 @@ fn main() -> ExitCode { } /// The `mcp` options: each given once, as `--flag value` or `--flag=value`. -fn mcp_options(args: &[String]) -> Result { +fn mcp_options(args: &[String]) -> Result { let mut connect = None; let mut headless = false; let mut read = None; @@ -94,18 +94,18 @@ fn mcp_options(args: &[String]) -> Result { return Err("--connect and --headless exclude each other".into()); } let roots = match (&read, &write) { - (None, None) => modelcraft_io::roots::Roots::default(), - _ => modelcraft_io::roots::Roots::new(read.as_deref().map(Path::new), write.as_deref().map(Path::new)).map_err(|e| e.to_string())?, + (None, None) => fieldcraft_io::roots::Roots::default(), + _ => fieldcraft_io::roots::Roots::new(read.as_deref().map(Path::new), write.as_deref().map(Path::new)).map_err(|e| e.to_string())?, }; - Ok(modelcraft_mcp::Options { connect, roots }) + Ok(fieldcraft_mcp::Options { connect, roots }) } fn mcp(args: &[String]) -> Result<(), String> { - let (backend, note) = modelcraft_mcp::backend(mcp_options(args)?)?; + let (backend, note) = fieldcraft_mcp::backend(mcp_options(args)?)?; if let Some(note) = note { - eprintln!("modelcraft-cli mcp: {note}"); + eprintln!("fieldcraft-cli mcp: {note}"); } - let mut server = modelcraft_mcp::Server::new(backend); + let mut server = fieldcraft_mcp::Server::new(backend); server.serve(std::io::stdin().lock(), std::io::stdout().lock()).map_err(|e| format!("mcp: {e}")) } @@ -127,7 +127,7 @@ fn run(path: &str) -> Result<(), String> { mod tests { use super::*; - fn opts(args: &[&str]) -> Result { + fn opts(args: &[&str]) -> Result { mcp_options(&args.iter().map(|s| s.to_string()).collect::>()) } diff --git a/apps/modelcraft/Cargo.toml b/apps/fieldcraft/Cargo.toml similarity index 66% rename from apps/modelcraft/Cargo.toml rename to apps/fieldcraft/Cargo.toml index e3fc01a..5fa82c5 100644 --- a/apps/modelcraft/Cargo.toml +++ b/apps/fieldcraft/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft" -description = "ModelCraft desktop app" +name = "fieldcraft" +description = "FieldCraft desktop app" version.workspace = true edition.workspace = true license.workspace = true @@ -10,9 +10,9 @@ publish = false workspace = true [dependencies] -modelcraft-engine = { workspace = true } -modelcraft-ui-egui = { workspace = true } -modelcraft-io = { workspace = true } +fieldcraft-engine = { workspace = true } +fieldcraft-ui-egui = { workspace = true } +fieldcraft-io = { workspace = true } rfd = { workspace = true } eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11"] } egui = { workspace = true } diff --git a/apps/modelcraft/src/control_server.rs b/apps/fieldcraft/src/control_server.rs similarity index 96% rename from apps/modelcraft/src/control_server.rs rename to apps/fieldcraft/src/control_server.rs index d4b2568..d7fb35f 100644 --- a/apps/modelcraft/src/control_server.rs +++ b/apps/fieldcraft/src/control_server.rs @@ -1,6 +1,6 @@ //! Loopback JSON-lines control server: one request per line, one reply per line. Agents and //! tests drive the app through it; the UI thread answers each request between frames -//! (`modelcraft_ui_egui::control`). +//! (`fieldcraft_ui_egui::control`). //! //! Adapted from SolidCraft's `apps/solidcraft/src/control_server.rs` (MIT OR Apache-2.0), see //! ATTRIBUTION.md. @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{Receiver, Sender, channel}; use std::time::Duration; -use modelcraft_ui_egui::ControlRequest; +use fieldcraft_ui_egui::ControlRequest; use serde_json::{Value, json}; /// Longest request line (bytes). @@ -31,11 +31,11 @@ pub fn start(port: u16, ctx: egui::Context) -> Receiver { let listener = match TcpListener::bind(("127.0.0.1", port)) { Ok(l) => l, Err(e) => { - eprintln!("modelcraft: control server failed to bind 127.0.0.1:{port}: {e}"); + eprintln!("fieldcraft: control server failed to bind 127.0.0.1:{port}: {e}"); return rx; } }; - eprintln!("modelcraft: control server listening on 127.0.0.1:{port}"); + eprintln!("fieldcraft: control server listening on 127.0.0.1:{port}"); std::thread::spawn(move || { for stream in listener.incoming().flatten() { if OPEN.fetch_add(1, Ordering::Relaxed) >= MAX_CONNECTIONS { diff --git a/apps/modelcraft/src/main.rs b/apps/fieldcraft/src/main.rs similarity index 87% rename from apps/modelcraft/src/main.rs rename to apps/fieldcraft/src/main.rs index 3c30fb5..cacfe2e 100644 --- a/apps/modelcraft/src/main.rs +++ b/apps/fieldcraft/src/main.rs @@ -1,23 +1,23 @@ -//! ModelCraft desktop app. +//! FieldCraft desktop app. //! -//! Usage: `modelcraft [--sample [NAME]] [--control PORT] [FILE…]` (files open as if dropped on the -//! window; NAME is a sample of `modelcraft_engine::sample`, `parts` by default) +//! Usage: `fieldcraft [--sample [NAME]] [--control PORT] [FILE…]` (files open as if dropped on the +//! window; NAME is a sample of `fieldcraft_engine::sample`, `parts` by default) //! -//! `--sample` opens a sample model (`modelcraft_engine::sample`). `--control PORT` (or -//! `MODELCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server: +//! `--sample` opens a sample model (`fieldcraft_engine::sample`). `--control PORT` (or +//! `FIELDCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server: //! `{"id":1,"method":"engine.execute","params":{"command":"model.info","params":{}}}`. See -//! `modelcraft_ui_egui::control` and docs/control-protocol.md. +//! `fieldcraft_ui_egui::control` and docs/control-protocol.md. #![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] #![forbid(unsafe_code)] mod control_server; -use modelcraft_engine::Session; -use modelcraft_engine::autosave::{self, Autosaver}; -use modelcraft_ui_egui::{Filter, ModelApp, Services}; +use fieldcraft_engine::Session; +use fieldcraft_engine::autosave::{self, Autosaver}; +use fieldcraft_ui_egui::{Filter, ModelApp, Services}; -const USAGE: &str = "usage: modelcraft [--sample [parts|cantilever|impact]] [--control PORT] [FILE…] (FILE: .modelcraft, .solidcraft or .3mf)"; +const USAGE: &str = "usage: fieldcraft [--sample [parts|cantilever|impact]] [--control PORT] [FILE…] (FILE: .fieldcraft, .solidcraft or .3mf)"; const DEPTH_BITS: u8 = 24; /// The window draws its own caption buttons where the OS ones would sit apart from the app bar. const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos"); @@ -90,7 +90,7 @@ fn services() -> Services { dialog(filters).set_file_name(name).save_file().map(|p| p.to_string_lossy().to_string()) })), // Beside the recovery folder in the per-user data folder. - recent_path: modelcraft_io::recovery::default_dir().and_then(|d| d.parent().map(|p| p.join("recent.json"))), + recent_path: fieldcraft_io::recovery::default_dir().and_then(|d| d.parent().map(|p| p.join("recent.json"))), } } @@ -111,7 +111,7 @@ fn parse_args(args: impl IntoIterator, env_port: Option) match a.as_str() { "--sample" => { // An optional sample name follows. - let name = args.peek().filter(|a| modelcraft_engine::sample::find(a).is_some()).cloned(); + let name = args.peek().filter(|a| fieldcraft_engine::sample::find(a).is_some()).cloned(); if name.is_some() { args.next(); } @@ -122,7 +122,7 @@ fn parse_args(args: impl IntoIterator, env_port: Option) out.control = Some(port.parse().map_err(|_| format!("--control: `{port}` is not a port number"))?); } "--version" | "-V" => { - println!("modelcraft {}", modelcraft_ui_egui::VERSION); + println!("fieldcraft {}", fieldcraft_ui_egui::VERSION); return Ok(None); } "--help" | "-h" => { @@ -137,17 +137,17 @@ fn parse_args(args: impl IntoIterator, env_port: Option) } fn main() -> eframe::Result { - let args = match parse_args(std::env::args().skip(1), std::env::var("MODELCRAFT_CONTROL_PORT").ok()) { + let args = match parse_args(std::env::args().skip(1), std::env::var("FIELDCRAFT_CONTROL_PORT").ok()) { Ok(Some(a)) => a, Ok(None) => return Ok(()), Err(e) => { - eprintln!("modelcraft: {e}"); + eprintln!("fieldcraft: {e}"); std::process::exit(2); } }; let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() - .with_title("ModelCraft") + .with_title("FieldCraft") .with_icon(app_icon()) .with_inner_size([1400.0, 900.0]) .with_min_inner_size([960.0, 600.0]) @@ -163,7 +163,7 @@ fn main() -> eframe::Result { ..Default::default() }; eframe::run_native( - "ModelCraft", + "FieldCraft", options, Box::new(move |cc| { let mut app = ModelApp::new(Session::default()).with_services(services()); @@ -171,7 +171,7 @@ fn main() -> eframe::Result { app.integrated_titlebar = cfg!(target_os = "macos"); if let Some(rs) = &cc.wgpu_render_state { let info = rs.adapter.get_info(); - eprintln!("modelcraft: graphics {:?} {}, MSAA {}x (requested {MSAA}x)", info.backend, info.name, rs.msaa_samples); + eprintln!("fieldcraft: graphics {:?} {}, MSAA {}x (requested {MSAA}x)", info.backend, info.name, rs.msaa_samples); app.set_wgpu(rs, DEPTH_BITS, rs.msaa_samples, &cc.egui_ctx); } if let Some(port) = args.control { @@ -224,9 +224,9 @@ mod tests { assert!(parse(&["--control", "99999"], None).is_err()); assert!(parse(&["--frobnicate"], None).is_err()); assert_eq!(parse(&["--sample", "impact"], None).map(|a| a.and_then(|a| a.sample)), Ok(Some("impact".into()))); - let a = parse(&["--sample", "x.modelcraft"], None).unwrap().unwrap(); - assert_eq!((a.sample.as_deref(), a.files), (Some("parts"), vec!["x.modelcraft".to_string()])); - assert_eq!(parse(&["a.modelcraft", "--sample"], None).map(|a| a.map(|a| a.files)), Ok(Some(vec!["a.modelcraft".to_string()]))); + let a = parse(&["--sample", "x.fieldcraft"], None).unwrap().unwrap(); + assert_eq!((a.sample.as_deref(), a.files), (Some("parts"), vec!["x.fieldcraft".to_string()])); + assert_eq!(parse(&["a.fieldcraft", "--sample"], None).map(|a| a.map(|a| a.files)), Ok(Some(vec!["a.fieldcraft".to_string()]))); assert_eq!(parse(&["--help"], None), Ok(None)); } } diff --git a/apps/modelcraft-cli/Cargo.toml b/apps/modelcraft-cli/Cargo.toml deleted file mode 100644 index d27aa98..0000000 --- a/apps/modelcraft-cli/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "modelcraft-cli" -description = "ModelCraft from the command line: list commands, run scripts, serve MCP" -version.workspace = true -edition.workspace = true -license.workspace = true - -[lints] -workspace = true - -[dependencies] -modelcraft-engine = { workspace = true } -modelcraft-io = { workspace = true } -modelcraft-mcp = { workspace = true } -serde_json = { workspace = true } diff --git a/assets/app-icon/Assets.car b/assets/app-icon/Assets.car index 9977d42..54d0d0d 100644 Binary files a/assets/app-icon/Assets.car and b/assets/app-icon/Assets.car differ diff --git a/assets/app-icon/ModelCraft.icon/Assets/01-floor-dark.svg b/assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg similarity index 96% rename from assets/app-icon/ModelCraft.icon/Assets/01-floor-dark.svg rename to assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg index 492536c..efae283 100644 --- a/assets/app-icon/ModelCraft.icon/Assets/01-floor-dark.svg +++ b/assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg @@ -11,7 +11,7 @@ - ModelCraft icon floor grid (Dark) + FieldCraft icon floor grid (Dark) diff --git a/assets/app-icon/ModelCraft.icon/Assets/01-floor.svg b/assets/app-icon/FieldCraft.icon/Assets/01-floor.svg similarity index 97% rename from assets/app-icon/ModelCraft.icon/Assets/01-floor.svg rename to assets/app-icon/FieldCraft.icon/Assets/01-floor.svg index 4228703..89ca0e2 100644 --- a/assets/app-icon/ModelCraft.icon/Assets/01-floor.svg +++ b/assets/app-icon/FieldCraft.icon/Assets/01-floor.svg @@ -11,7 +11,7 @@ - ModelCraft icon floor grid + FieldCraft icon floor grid diff --git a/assets/app-icon/ModelCraft.icon/Assets/02-part.svg b/assets/app-icon/FieldCraft.icon/Assets/02-part.svg similarity index 98% rename from assets/app-icon/ModelCraft.icon/Assets/02-part.svg rename to assets/app-icon/FieldCraft.icon/Assets/02-part.svg index b9f0514..d612eb4 100644 --- a/assets/app-icon/ModelCraft.icon/Assets/02-part.svg +++ b/assets/app-icon/FieldCraft.icon/Assets/02-part.svg @@ -26,7 +26,7 @@ - ModelCraft icon part + FieldCraft icon part diff --git a/assets/app-icon/ModelCraft.icon/Assets/03-mesh.svg b/assets/app-icon/FieldCraft.icon/Assets/03-mesh.svg similarity index 99% rename from assets/app-icon/ModelCraft.icon/Assets/03-mesh.svg rename to assets/app-icon/FieldCraft.icon/Assets/03-mesh.svg index ba3dd49..859db3d 100644 --- a/assets/app-icon/ModelCraft.icon/Assets/03-mesh.svg +++ b/assets/app-icon/FieldCraft.icon/Assets/03-mesh.svg @@ -5,7 +5,7 @@ - ModelCraft icon mesh + FieldCraft icon mesh diff --git a/assets/app-icon/ModelCraft.icon/icon.json b/assets/app-icon/FieldCraft.icon/icon.json similarity index 100% rename from assets/app-icon/ModelCraft.icon/icon.json rename to assets/app-icon/FieldCraft.icon/icon.json diff --git a/assets/app-icon/README.md b/assets/app-icon/README.md index 0475c1a..015fe22 100644 --- a/assets/app-icon/README.md +++ b/assets/app-icon/README.md @@ -1,6 +1,6 @@ -# ModelCraft application icon +# FieldCraft application icon -ModelCraft's mark is an L-shaped bracket with a bolt hole, drawn in isometric view and covered +FieldCraft's mark is an L-shaped bracket with a bolt hole, drawn in isometric view and covered by a structured hexahedral mesh with an O-grid around the hole. Its faces are separated by transparent gaps rather than painted edge lines, so the backdrop shows through them in every appearance. It is original artwork, attributed in `ATTRIBUTION.md`, and shares the Craft @@ -17,11 +17,11 @@ hand. It runs on macOS (sips, iconutil, `xcrun actool`) with ImageMagick (`magic | `icon-small.vectorcraft`, `icon-small.svg`, `icon-small.png` | Small-size artwork used at 16–32 px: no mesh or floor grid, faces edge to edge | | `icon.iconset/`, `icon.icns` | macOS iconset (sips) and its iconutil compilation | | `icon.ico` | Windows icon, 16–256 px | -| `ModelCraft.icon/` | Icon Composer document: floor grid (Default and Dark), part and mesh layers, each scaled to 90% | +| `FieldCraft.icon/` | Icon Composer document: floor grid (Default and Dark), part and mesh layers, each scaled to 90% | | `layers/*.vectorcraft` | Editable sources of the Icon Composer layers | -| `Assets.car` | `actool` compilation of `ModelCraft.icon` for macOS 26 and later (needs Xcode 26 or later; the task skips it with a note otherwise) | +| `Assets.car` | `actool` compilation of `FieldCraft.icon` for macOS 26 and later (needs Xcode 26 or later; the task skips it with a note otherwise) | The desktop app embeds `icon.iconset/icon_128x128@2x.png` as its window and Dock icon -(`apps/modelcraft/src/main.rs`). There is no macOS bundle yet; packaging will copy -`Assets.car` and `icon.icns` into `Contents/Resources` and name `ModelCraft` in +(`apps/fieldcraft/src/main.rs`). There is no macOS bundle yet; packaging will copy +`Assets.car` and `icon.icns` into `Contents/Resources` and name `FieldCraft` in `CFBundleIconName` / `CFBundleIconFile`. diff --git a/assets/app-icon/icon-small.png b/assets/app-icon/icon-small.png index 1465277..d7b2470 100644 Binary files a/assets/app-icon/icon-small.png and b/assets/app-icon/icon-small.png differ diff --git a/assets/app-icon/icon-small.svg b/assets/app-icon/icon-small.svg index a54e434..1c308e0 100644 --- a/assets/app-icon/icon-small.svg +++ b/assets/app-icon/icon-small.svg @@ -31,7 +31,7 @@ - ModelCraft application icon, small sizes + FieldCraft application icon, small sizes diff --git a/assets/app-icon/icon-small.vectorcraft b/assets/app-icon/icon-small.vectorcraft index 05771a8..c2c0166 100644 Binary files a/assets/app-icon/icon-small.vectorcraft and b/assets/app-icon/icon-small.vectorcraft differ diff --git a/assets/app-icon/icon.iconset/icon_128x128.png b/assets/app-icon/icon.iconset/icon_128x128.png index 8f82f11..80fec70 100644 Binary files a/assets/app-icon/icon.iconset/icon_128x128.png and b/assets/app-icon/icon.iconset/icon_128x128.png differ diff --git a/assets/app-icon/icon.iconset/icon_128x128@2x.png b/assets/app-icon/icon.iconset/icon_128x128@2x.png index a4cfed6..adccb6a 100644 Binary files a/assets/app-icon/icon.iconset/icon_128x128@2x.png and b/assets/app-icon/icon.iconset/icon_128x128@2x.png differ diff --git a/assets/app-icon/icon.iconset/icon_16x16.png b/assets/app-icon/icon.iconset/icon_16x16.png index bde5131..198efe5 100644 Binary files a/assets/app-icon/icon.iconset/icon_16x16.png and b/assets/app-icon/icon.iconset/icon_16x16.png differ diff --git a/assets/app-icon/icon.iconset/icon_16x16@2x.png b/assets/app-icon/icon.iconset/icon_16x16@2x.png index d85b5ec..e5faeba 100644 Binary files a/assets/app-icon/icon.iconset/icon_16x16@2x.png and b/assets/app-icon/icon.iconset/icon_16x16@2x.png differ diff --git a/assets/app-icon/icon.iconset/icon_256x256.png b/assets/app-icon/icon.iconset/icon_256x256.png index a4cfed6..adccb6a 100644 Binary files a/assets/app-icon/icon.iconset/icon_256x256.png and b/assets/app-icon/icon.iconset/icon_256x256.png differ diff --git a/assets/app-icon/icon.iconset/icon_256x256@2x.png b/assets/app-icon/icon.iconset/icon_256x256@2x.png index cf32c37..6070d79 100644 Binary files a/assets/app-icon/icon.iconset/icon_256x256@2x.png and b/assets/app-icon/icon.iconset/icon_256x256@2x.png differ diff --git a/assets/app-icon/icon.iconset/icon_32x32.png b/assets/app-icon/icon.iconset/icon_32x32.png index d85b5ec..e5faeba 100644 Binary files a/assets/app-icon/icon.iconset/icon_32x32.png and b/assets/app-icon/icon.iconset/icon_32x32.png differ diff --git a/assets/app-icon/icon.iconset/icon_32x32@2x.png b/assets/app-icon/icon.iconset/icon_32x32@2x.png index 8517996..693c4ec 100644 Binary files a/assets/app-icon/icon.iconset/icon_32x32@2x.png and b/assets/app-icon/icon.iconset/icon_32x32@2x.png differ diff --git a/assets/app-icon/icon.iconset/icon_512x512.png b/assets/app-icon/icon.iconset/icon_512x512.png index cf32c37..6070d79 100644 Binary files a/assets/app-icon/icon.iconset/icon_512x512.png and b/assets/app-icon/icon.iconset/icon_512x512.png differ diff --git a/assets/app-icon/icon.iconset/icon_512x512@2x.png b/assets/app-icon/icon.iconset/icon_512x512@2x.png index 74f0f45..825cea7 100644 Binary files a/assets/app-icon/icon.iconset/icon_512x512@2x.png and b/assets/app-icon/icon.iconset/icon_512x512@2x.png differ diff --git a/assets/app-icon/icon.png b/assets/app-icon/icon.png index 74f0f45..825cea7 100644 Binary files a/assets/app-icon/icon.png and b/assets/app-icon/icon.png differ diff --git a/assets/app-icon/icon.svg b/assets/app-icon/icon.svg index d568459..762fa56 100644 --- a/assets/app-icon/icon.svg +++ b/assets/app-icon/icon.svg @@ -43,7 +43,7 @@ - ModelCraft application icon + FieldCraft application icon diff --git a/assets/app-icon/icon.vectorcraft b/assets/app-icon/icon.vectorcraft index 8b5da44..01e56c4 100644 Binary files a/assets/app-icon/icon.vectorcraft and b/assets/app-icon/icon.vectorcraft differ diff --git a/assets/app-icon/layers/01-floor-dark.vectorcraft b/assets/app-icon/layers/01-floor-dark.vectorcraft index e089aa1..a3f74a7 100644 Binary files a/assets/app-icon/layers/01-floor-dark.vectorcraft and b/assets/app-icon/layers/01-floor-dark.vectorcraft differ diff --git a/assets/app-icon/layers/01-floor.vectorcraft b/assets/app-icon/layers/01-floor.vectorcraft index 5e78798..5a07c5e 100644 Binary files a/assets/app-icon/layers/01-floor.vectorcraft and b/assets/app-icon/layers/01-floor.vectorcraft differ diff --git a/assets/app-icon/layers/02-part.vectorcraft b/assets/app-icon/layers/02-part.vectorcraft index 7929913..fbff97f 100644 Binary files a/assets/app-icon/layers/02-part.vectorcraft and b/assets/app-icon/layers/02-part.vectorcraft differ diff --git a/assets/app-icon/layers/03-mesh.vectorcraft b/assets/app-icon/layers/03-mesh.vectorcraft index 698cf9b..21e4270 100644 Binary files a/assets/app-icon/layers/03-mesh.vectorcraft and b/assets/app-icon/layers/03-mesh.vectorcraft differ diff --git a/crates/decks/Cargo.toml b/crates/decks/Cargo.toml index cc0d16e..e325a64 100644 --- a/crates/decks/Cargo.toml +++ b/crates/decks/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-decks" -description = "ModelCraft's solver decks: Abaqus input, Nastran bulk data and LS-DYNA keyword writers (and later readers)" +name = "fieldcraft-decks" +description = "FieldCraft's solver decks: Abaqus input, Nastran bulk data and LS-DYNA keyword writers (and later readers)" version.workspace = true edition.workspace = true license.workspace = true @@ -9,7 +9,7 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } +fieldcraft-model = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/crates/decks/src/abaqus.rs b/crates/decks/src/abaqus.rs index 77ba472..ac4e0e1 100644 --- a/crates/decks/src/abaqus.rs +++ b/crates/decks/src/abaqus.rs @@ -6,7 +6,7 @@ //! # Layout //! //! The deck is *flat*: nodes, elements, sets, materials, sections and couplings sit at model level, -//! with no parts, assembly or instances. ModelCraft has one mesh with model-wide labels and no +//! with no parts, assembly or instances. FieldCraft has one mesh with model-wide labels and no //! instance transforms, so a part/assembly wrapper would only prefix every set name with an //! instance name (`Part-1-1.root`) and bring the 80-character limit on the joined name closer. When //! the model gains several parts or instances the writer switches to the part/assembly form. @@ -53,7 +53,7 @@ //! //! # Element types //! -//! | ModelCraft | Solid section | Shell section | Beam | Rod | +//! | FieldCraft | Solid section | Shell section | Beam | Rod | //! |---|---|---|---|---| //! | tet4 | C3D4 (H) | | | | //! | tet10 | C3D10 (H) | | | | @@ -76,7 +76,7 @@ //! differs by direction and a spring along a skew fixed vector are reported (they need a local //! orientation, not written yet). //! -//! | ModelCraft | Plane stress | Plane strain | Axisymmetric | +//! | FieldCraft | Plane stress | Plane strain | Axisymmetric | //! |---|---|---|---| //! | tri3 | CPS3 | CPE3 | CAX3 | //! | tri6 | CPS6 | CPE6 | CAX6 | @@ -115,7 +115,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write as _; -use modelcraft_model::{ +use fieldcraft_model::{ BeamProfile, BoundaryCondition, CouplingKind, Elastic, Element, ElementKind, Hardening, Integration, Load, Model, OutputVariable, Plastic, Procedure, Resolved, Section, SectionKind, Step, Symmetry, Target, }; @@ -330,10 +330,10 @@ fn discrete_groups<'r>(model: &Model, r: &'r Resolved) -> Result Result { let l = next; - if l > modelcraft_model::MAX_LABEL { + if l > fieldcraft_model::MAX_LABEL { return Err(Error::Limit(format!( "no element label left below {} for point masses, springs and dashpots", - modelcraft_model::MAX_LABEL as u64 + 1 + fieldcraft_model::MAX_LABEL as u64 + 1 ))); } next += 1; @@ -371,7 +371,7 @@ fn discrete_groups<'r>(model: &Model, r: &'r Resolved) -> Result (if s.dashpot { "DASHPOTA" } else { "SPRINGA" }, String::new(), vec![s.first, b]), (Some(b), Some(d), false) => (if s.dashpot { "DASHPOT2" } else { "SPRING2" }, format!("{d}, {d}"), vec![s.first, b]), @@ -556,7 +556,7 @@ impl<'a> StepLoads<'a> { /// `everything` is every element and the section sets that hold them (gravity on the whole /// model). fn new(model: &'a Model, r: &'a Resolved, step: &'a Step, everything: &'a (Vec<&'a str>, Vec)) -> StepLoads<'a> { - let axisymmetric = r.extent() == Some(modelcraft_model::Extent::Revolved); + let axisymmetric = r.extent() == Some(fieldcraft_model::Extent::Revolved); let mut s = StepLoads { concentrated: crate::concentrated_loads(model, r, &step.loads), axisymmetric, ..StepLoads::default() }; let (mut gravity, mut body) = (Vec::new(), Vec::new()); for name in &step.loads { @@ -637,8 +637,8 @@ impl Deck { None => "units not specified".to_string(), }; self.line("*HEADING"); - self.line(&format!("ModelCraft model, {units}")); - self.comment("Written by ModelCraft. The numbers are in one consistent unit system and are not converted."); + self.line(&format!("FieldCraft model, {units}")); + self.comment("Written by FieldCraft. The numbers are in one consistent unit system and are not converted."); } fn nodes(&mut self, model: &Model) -> Result<()> { diff --git a/crates/decks/src/lib.rs b/crates/decks/src/lib.rs index 35ba863..f157f7b 100644 --- a/crates/decks/src/lib.rs +++ b/crates/decks/src/lib.rs @@ -1,7 +1,7 @@ //! Solver decks: the solver-neutral model written as Abaqus input, Nastran bulk data and LS-DYNA //! keyword files. Each format is its own module (`abaqus`, `nastran`, `lsdyna`), written from the //! vendor's public reference manual in our own words; examples and expected files are our own. -//! Writers read [`modelcraft_model::Model`] and its `resolve()` result, never another format. +//! Writers read [`fieldcraft_model::Model`] and its `resolve()` result, never another format. //! //! Writers produce text; saving it is the caller's business (the engine's `deck.write`), so the //! crate stays free of the file system. [`FORMATS`] lists every writer by the name `deck.write` @@ -15,8 +15,8 @@ pub mod nastran; use std::collections::{BTreeMap, BTreeSet}; -use modelcraft_model::nodal::edge_pressure; -use modelcraft_model::{Diagnostic, Load, Model, Resolved, ResolvedDirection}; +use fieldcraft_model::nodal::edge_pressure; +use fieldcraft_model::{Diagnostic, Load, Model, Resolved, ResolvedDirection}; #[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum Error { @@ -25,7 +25,7 @@ pub enum Error { Unsupported { format: &'static str, what: String }, /// The model is not ready to write (its check failed). #[error(transparent)] - Model(#[from] modelcraft_model::Error), + Model(#[from] fieldcraft_model::Error), #[error("limit reached: {0}")] Limit(String), } @@ -70,11 +70,11 @@ impl Format { /// stay (the check warns about the ones no step uses). pub fn with_steps(model: &Model, names: &[String]) -> Result { if names.is_empty() { - return Err(Error::Model(modelcraft_model::Error::Invalid("choose at least one step to write".into()))); + return Err(Error::Model(fieldcraft_model::Error::Invalid("choose at least one step to write".into()))); } if let Some(n) = names.iter().find(|n| model.step(n).is_none()) { - let shown: String = n.chars().take(modelcraft_model::MAX_NAME_LEN).collect(); - return Err(Error::Model(modelcraft_model::Error::NotFound(format!("step `{shown}`")))); + let shown: String = n.chars().take(fieldcraft_model::MAX_NAME_LEN).collect(); + return Err(Error::Model(fieldcraft_model::Error::NotFound(format!("step `{shown}`")))); } let mut out = model.clone(); let dropped: Vec = model.steps().iter().map(|s| s.name.clone()).filter(|s| !names.contains(s)).collect(); @@ -84,7 +84,7 @@ pub fn with_steps(model: &Model, names: &[String]) -> Result { Ok(out) } -/// A fluid step: solved by ModelCraft's lattice Boltzmann solver, never written to a deck. +/// A fluid step: solved by FieldCraft's lattice Boltzmann solver, never written to a deck. pub(crate) fn fluid_step(format: &'static str, step: &str) -> Error { Error::Unsupported { format, what: format!("fluid step `{step}` (solve it with solve.fluid; write the structural steps only)") } } @@ -155,7 +155,7 @@ pub fn concentrated_loads<'a>(model: &Model, r: &Resolved, active: impl IntoIter /// The consistent nodal forces (degrees of freedom 1 and 2) of the active pressures on a 2D /// model's element edges, for formats that write them as concentrated loads: the model's edge -/// integration ([`modelcraft_model::nodal::edge_pressure`]), a positive pressure pushing +/// integration ([`fieldcraft_model::nodal::edge_pressure`]), a positive pressure pushing /// into the element, summed per node and direction; zero sums are left out. pub fn edge_pressure_loads<'a>(model: &Model, r: &Resolved, active: impl IntoIterator) -> Result> { let mut out: BTreeMap<(u32, u8), f64> = BTreeMap::new(); diff --git a/crates/decks/src/lsdyna/elements.rs b/crates/decks/src/lsdyna/elements.rs index 855edef..7795ebf 100644 --- a/crates/decks/src/lsdyna/elements.rs +++ b/crates/decks/src/lsdyna/elements.rs @@ -17,7 +17,7 @@ //! //! Every mapping is checked by the sign of the element Jacobian in the tests. -use modelcraft_model::{ElementKind, Formulation, Integration, SectionKind}; +use fieldcraft_model::{ElementKind, Formulation, Integration, SectionKind}; /// How an element is written: its keyword and its node list in LS-DYNA order. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/crates/decks/src/lsdyna/mod.rs b/crates/decks/src/lsdyna/mod.rs index 28610ae..5741c9c 100644 --- a/crates/decks/src/lsdyna/mod.rs +++ b/crates/decks/src/lsdyna/mod.rs @@ -61,7 +61,7 @@ mod elements; use std::collections::{BTreeMap, BTreeSet}; -use modelcraft_model::{ +use fieldcraft_model::{ BoundaryCondition, CouplingKind, Diagnostic, Dofs, Elastic, Element, ElementKind, Hardening, Load, Material, Model, OutputVariable, Plastic, Procedure, Resolved, ResolvedDirection, Section, SectionKind, Step, Target, }; @@ -102,7 +102,7 @@ const MAX_SHELL_POINTS: u32 = 10; /// A resolved model that breaks its own invariants (unreachable after `resolve`). fn broken(what: String) -> Error { - Error::Model(modelcraft_model::Error::Invalid(format!("inconsistent resolved model: {what}"))) + Error::Model(fieldcraft_model::Error::Invalid(format!("inconsistent resolved model: {what}"))) } /// One `*PART`: a section's elements of one kind. @@ -159,7 +159,7 @@ fn write_with(model: &Model, r: &Resolved) -> Result { let cases = if steps.len() > 1 { steps.len() } else { 0 }; d.line("*KEYWORD long=y"); - d.comment("Written by ModelCraft: implicit linear analysis, long format (20-column fields)."); + d.comment("Written by FieldCraft: implicit linear analysis, long format (20-column fields)."); d.comment(&format!("Units: {} (consistent; nothing is converted).", model.units().label())); if cases > 0 { d.comment(&format!("{cases} steps are written as *CASE analyses: run LS-DYNA with CASE on its command line.")); @@ -676,7 +676,7 @@ fn discrete(d: &mut Deck, model: &Model, r: &Resolved, plan: &Plan) -> Result<() let limit = |what: &str| Error::Limit(format!("no {what} id left for point masses, springs and dashpots")); let mut eid = model.elements().keys().next_back().copied().unwrap_or(0); let mut next_eid = || -> Result { - eid = eid.checked_add(1).filter(|e| *e <= modelcraft_model::MAX_LABEL).ok_or_else(|| limit("element"))?; + eid = eid.checked_add(1).filter(|e| *e <= fieldcraft_model::MAX_LABEL).ok_or_else(|| limit("element"))?; Ok(eid) }; d.comment("Point masses"); @@ -754,7 +754,7 @@ fn discrete(d: &mut Deck, model: &Model, r: &Resolved, plan: &Plan) -> Result<() while taken.contains(&node) { node = node.checked_add(1).ok_or_else(|| limit("node"))?; } - if node > modelcraft_model::MAX_LABEL { + if node > fieldcraft_model::MAX_LABEL { return Err(limit("node")); } ground.push((node, model.label_xyz(s.first).ok_or_else(|| broken(format!("node {}", s.first)))?)); diff --git a/crates/decks/src/nastran/mod.rs b/crates/decks/src/nastran/mod.rs index 96c0a51..a1ecbe1 100644 --- a/crates/decks/src/nastran/mod.rs +++ b/crates/decks/src/nastran/mod.rs @@ -52,7 +52,7 @@ mod card; use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write as _; -use modelcraft_model::{ +use fieldcraft_model::{ BeamProfile, Coupling, CouplingKind, Dofs, Elastic, ElementKind, Formulation, Hardening, Integration, Load, MAX_LABEL, Model, OutputVariable, Plastic, Procedure, Resolved, SectionKind, Step, }; @@ -688,9 +688,9 @@ impl<'m> Deck<'m> { fn emit(&self) -> Result { let mut out = String::new(); - out.push_str("$ Nastran bulk data written by ModelCraft\n"); + out.push_str("$ Nastran bulk data written by FieldCraft\n"); let _ = writeln!(out, "$ units: {}", self.model.units().label()); - let _ = writeln!(out, "SOL {}\nCEND\nTITLE = ModelCraft model\nECHO = NONE", self.sol); + let _ = writeln!(out, "SOL {}\nCEND\nTITLE = FieldCraft model\nECHO = NONE", self.sol); for (i, sub) in self.subcases.iter().enumerate() { let _ = writeln!(out, "SUBCASE {}", i + 1); let title: String = format!(" SUBTITLE = {}", sub.step.name).chars().take(72).collect(); @@ -945,7 +945,7 @@ impl<'m> Deck<'m> { } /// A `PLOAD4` for one element face, positive pushing into the element. - fn pressure(&self, sid: u32, f: modelcraft_model::ElementFace, magnitude: f64) -> Option { + fn pressure(&self, sid: u32, f: fieldcraft_model::ElementFace, magnitude: f64) -> Option { let el = self.model.element(f.element)?; let card = Card::new("PLOAD4").int(sid).int(f.element); if el.kind.dimension() == 2 { @@ -1010,7 +1010,7 @@ fn plasticity(out: &mut String, name: &str, mid: u32, young: f64, plastic: &Plas write_named(out, &format!("stress-strain curve of {name}"), tid, Card::new("TABLES1").int(tid).end_line().reals(curve).text("ENDT")) } (Plastic::Table { .. }, None) | (Plastic::JohnsonCook { .. }, _) => { - Err(Error::Model(modelcraft_model::Error::Invalid(format!("material `{name}`: its plasticity was not planned for MATS1")))) + Err(Error::Model(fieldcraft_model::Error::Invalid(format!("material `{name}`: its plasticity was not planned for MATS1")))) } } } diff --git a/crates/decks/tests/abaqus.rs b/crates/decks/tests/abaqus.rs index 2cc6ad0..e21f552 100644 --- a/crates/decks/tests/abaqus.rs +++ b/crates/decks/tests/abaqus.rs @@ -5,8 +5,8 @@ use std::collections::{BTreeMap, BTreeSet}; use std::path::PathBuf; -use modelcraft_decks::{Error, abaqus}; -use modelcraft_model::{ +use fieldcraft_decks::{Error, abaqus}; +use fieldcraft_model::{ BeamProfile, BoundaryCondition, Coupling, CouplingKind, Distribution, Dofs, Elastic, ElementFace, ElementKind, Formulation, Integration, Load, Material, Model, OutputVariable, Procedure, ReferencePoint, Section, SectionKind, Set, Step, Symmetry, Target, UnitSystem, }; @@ -185,12 +185,12 @@ fn read(deck: &str) -> Parsed { fn golden(name: &str, deck: &str) { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/abaqus").join(format!("{name}.inp")); - if std::env::var_os("MODELCRAFT_BLESS").is_some() { + if std::env::var_os("FIELDCRAFT_BLESS").is_some() { std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::write(&path, deck).unwrap(); } - let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with MODELCRAFT_BLESS=1 to write it)", path.display())); - assert!(expected == deck, "{name}.inp differs from the written deck (MODELCRAFT_BLESS=1 rewrites it):\n{deck}"); + let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with FIELDCRAFT_BLESS=1 to write it)", path.display())); + assert!(expected == deck, "{name}.inp differs from the written deck (FIELDCRAFT_BLESS=1 rewrites it):\n{deck}"); } fn assert_lines_fit(deck: &str) { @@ -513,16 +513,16 @@ fn numbers_read_back_exactly() { #[test] fn largest_labels_longest_numbers_and_names_fit_a_line() { let mut m = Model::default(); - let base = modelcraft_model::MAX_LABEL - 30; + let base = fieldcraft_model::MAX_LABEL - 30; let ugly = -1.234_567_890_123_456_7e-300; let mut xyz = one_element(ElementKind::Hex20).nodes().values().map(|n| n.xyz).collect::>(); xyz[0] = [ugly, ugly, ugly]; for (i, p) in xyz.iter().enumerate() { m.set_node(base + i as u32, *p).unwrap(); } - m.set_element(modelcraft_model::MAX_LABEL, ElementKind::Hex20, (0..20).map(|i| base + i).collect()).unwrap(); - let long = format!("A{}", "x".repeat(modelcraft_model::MAX_NAME_LEN - 1)); - m.add_set(&long, Set { elements: vec![modelcraft_model::MAX_LABEL], nodes: (0..20).map(|i| base + i).collect(), ..Set::default() }).unwrap(); + m.set_element(fieldcraft_model::MAX_LABEL, ElementKind::Hex20, (0..20).map(|i| base + i).collect()).unwrap(); + let long = format!("A{}", "x".repeat(fieldcraft_model::MAX_NAME_LEN - 1)); + m.add_set(&long, Set { elements: vec![fieldcraft_model::MAX_LABEL], nodes: (0..20).map(|i| base + i).collect(), ..Set::default() }).unwrap(); m.add_material( &long, Material { elastic: Some(Elastic::Isotropic { young: ugly.abs(), poisson: 0.3 }), density: Some(1.0), ..Material::default() }, @@ -537,7 +537,7 @@ fn largest_labels_longest_numbers_and_names_fit_a_line() { let deck = abaqus::write(&m).unwrap(); assert_lines_fit(&deck); let p = read(&deck); - assert_eq!(p.elements[&modelcraft_model::MAX_LABEL].1.len(), 20, "a hex20 continues onto a second line"); + assert_eq!(p.elements[&fieldcraft_model::MAX_LABEL].1.len(), 20, "a hex20 continues onto a second line"); assert_eq!(p.nodes[&base], [ugly; 3]); } @@ -704,7 +704,7 @@ fn a_model_that_is_not_ready_is_refused() { } /// The cantilever with its steel given each plasticity in turn. -fn plastic_cantilever(p: modelcraft_model::Plastic) -> Model { +fn plastic_cantilever(p: fieldcraft_model::Plastic) -> Model { let mut m = cantilever(); m.replace_material("Steel", Material { plastic: Some(p), ..steel() }).unwrap(); m @@ -714,7 +714,7 @@ fn plastic_cantilever(p: modelcraft_model::Plastic) -> Model { /// erosion, failure models of Abaqus/Explicit, are reported. #[test] fn plasticity_is_written_and_failure_reported() { - use modelcraft_model::{Damage, Erosion, Hardening, HardeningPoint, Plastic}; + use fieldcraft_model::{Damage, Erosion, Hardening, HardeningPoint, Plastic}; let section = |deck: &str| deck.split("*MATERIAL, NAME=Steel").nth(1).and_then(|s| s.split("*SOLID SECTION").next()).unwrap().to_string(); let deck = abaqus::write(&plastic_cantilever(Plastic::Bilinear { yield_stress: 250.0, hardening_modulus: 1000.0, hardening: Hardening::Isotropic })) diff --git a/crates/decks/tests/abaqus/cantilever.inp b/crates/decks/tests/abaqus/cantilever.inp index 2cff3f8..1476fcc 100644 --- a/crates/decks/tests/abaqus/cantilever.inp +++ b/crates/decks/tests/abaqus/cantilever.inp @@ -1,6 +1,6 @@ *HEADING -ModelCraft model, units mm-t-s-N-MPa -** Written by ModelCraft. The numbers are in one consistent unit system and are not converted. +FieldCraft model, units mm-t-s-N-MPa +** Written by FieldCraft. The numbers are in one consistent unit system and are not converted. ** Nodes *NODE 1, 0.0, 0.0, 0.0 diff --git a/crates/decks/tests/abaqus/frequency.inp b/crates/decks/tests/abaqus/frequency.inp index 079dea7..3ad0f40 100644 --- a/crates/decks/tests/abaqus/frequency.inp +++ b/crates/decks/tests/abaqus/frequency.inp @@ -1,6 +1,6 @@ *HEADING -ModelCraft model, units mm-t-s-N-MPa -** Written by ModelCraft. The numbers are in one consistent unit system and are not converted. +FieldCraft model, units mm-t-s-N-MPa +** Written by FieldCraft. The numbers are in one consistent unit system and are not converted. ** Nodes *NODE 1, 0.0, 0.0, 0.0 diff --git a/crates/decks/tests/abaqus/plate.inp b/crates/decks/tests/abaqus/plate.inp index 6c8e201..2041fa7 100644 --- a/crates/decks/tests/abaqus/plate.inp +++ b/crates/decks/tests/abaqus/plate.inp @@ -1,6 +1,6 @@ *HEADING -ModelCraft model, units mm-t-s-N-MPa -** Written by ModelCraft. The numbers are in one consistent unit system and are not converted. +FieldCraft model, units mm-t-s-N-MPa +** Written by FieldCraft. The numbers are in one consistent unit system and are not converted. ** Nodes *NODE 1, 0.0, 0.0, 0.0 diff --git a/crates/decks/tests/discrete.rs b/crates/decks/tests/discrete.rs index 1c2aa3e..fb7e52d 100644 --- a/crates/decks/tests/discrete.rs +++ b/crates/decks/tests/discrete.rs @@ -1,8 +1,8 @@ //! Trusses (rod3), point masses, rotary inertia, springs and dashpots in every deck format: //! what each writes, and what each reports as unsupported. -use modelcraft_decks::{Error, abaqus, lsdyna, nastran}; -use modelcraft_model::{ +use fieldcraft_decks::{Error, abaqus, lsdyna, nastran}; +use fieldcraft_model::{ BoundaryCondition, Direction, Elastic, ElementKind, Formulation, Mass, Material, Model, PointMass, Procedure, ReferencePoint, Section, SectionKind, Set, Spring, Step, Target, UnitSystem, }; diff --git a/crates/decks/tests/golden/nastran/cantilever.bdf b/crates/decks/tests/golden/nastran/cantilever.bdf index 6815d11..19abb00 100644 --- a/crates/decks/tests/golden/nastran/cantilever.bdf +++ b/crates/decks/tests/golden/nastran/cantilever.bdf @@ -1,8 +1,8 @@ -$ Nastran bulk data written by ModelCraft +$ Nastran bulk data written by FieldCraft $ units: mm-t-s-N-MPa SOL 101 CEND -TITLE = ModelCraft model +TITLE = FieldCraft model ECHO = NONE SUBCASE 1 SUBTITLE = Bend diff --git a/crates/decks/tests/golden/nastran/modes.bdf b/crates/decks/tests/golden/nastran/modes.bdf index a9bd25f..b57826a 100644 --- a/crates/decks/tests/golden/nastran/modes.bdf +++ b/crates/decks/tests/golden/nastran/modes.bdf @@ -1,8 +1,8 @@ -$ Nastran bulk data written by ModelCraft +$ Nastran bulk data written by FieldCraft $ units: unspecified SOL 103 CEND -TITLE = ModelCraft model +TITLE = FieldCraft model ECHO = NONE SUBCASE 1 SUBTITLE = Modes diff --git a/crates/decks/tests/golden/nastran/plate.bdf b/crates/decks/tests/golden/nastran/plate.bdf index 35cc679..bd3c2b8 100644 --- a/crates/decks/tests/golden/nastran/plate.bdf +++ b/crates/decks/tests/golden/nastran/plate.bdf @@ -1,8 +1,8 @@ -$ Nastran bulk data written by ModelCraft +$ Nastran bulk data written by FieldCraft $ units: mm-t-s-N-MPa SOL 101 CEND -TITLE = ModelCraft model +TITLE = FieldCraft model ECHO = NONE SUBCASE 1 SUBTITLE = Sag diff --git a/crates/decks/tests/lsdyna.rs b/crates/decks/tests/lsdyna.rs index eb4eae0..99eb830 100644 --- a/crates/decks/tests/lsdyna.rs +++ b/crates/decks/tests/lsdyna.rs @@ -1,13 +1,13 @@ //! LS-DYNA deck writer: golden files of our own small models, node-order mappings checked by the //! sign of the element Jacobian, a re-read of our own output, and what the format cannot take. //! -//! Golden files live in `tests/lsdyna/`; `MODELCRAFT_BLESS=1 cargo test -p modelcraft-decks` +//! Golden files live in `tests/lsdyna/`; `FIELDCRAFT_BLESS=1 cargo test -p fieldcraft-decks` //! rewrites them after an intended change (review the diff). use std::collections::{BTreeMap, BTreeSet}; -use modelcraft_decks::{Error, lsdyna}; -use modelcraft_model::*; +use fieldcraft_decks::{Error, lsdyna}; +use fieldcraft_model::*; // --------------------------------------------------------------------------------------------- // Our own models. @@ -200,11 +200,11 @@ fn golden(name: &str, model: &Model) -> lsdyna::Written { let again = lsdyna::write(model).unwrap(); assert_eq!(written, again, "deterministic"); let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/lsdyna").join(format!("{name}.k")); - if std::env::var_os("MODELCRAFT_BLESS").is_some() { + if std::env::var_os("FIELDCRAFT_BLESS").is_some() { std::fs::write(&path, &written.text).unwrap(); } - let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with MODELCRAFT_BLESS=1)", path.display())); - assert!(written.text == expected, "{name}.k differs from the golden file (MODELCRAFT_BLESS=1 rewrites it)"); + let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with FIELDCRAFT_BLESS=1)", path.display())); + assert!(written.text == expected, "{name}.k differs from the golden file (FIELDCRAFT_BLESS=1 rewrites it)"); assert!(written.text.is_ascii()); for line in written.text.lines() { assert!(line.len() <= 200 && line == line.trim_end(), "{line:?}"); @@ -688,7 +688,7 @@ fn hostile_numbers_stay_within_their_fields() { fn explicit_steps_are_refused_by_every_format() { let mut m = cantilever(); m.add_step(step("Impact", Procedure::ExplicitDynamic(ExplicitDynamic::new(1e-3)), &["clamp"], &[]), None).unwrap(); - for f in modelcraft_decks::FORMATS { + for f in fieldcraft_decks::FORMATS { let e = f.write(&m).unwrap_err(); assert!(matches!(e, Error::Unsupported { .. }), "{}: {e}", f.name); assert!(e.to_string().contains("step `Impact`: explicit dynamic"), "{}: {e}", f.name); diff --git a/crates/decks/tests/lsdyna/cantilever.k b/crates/decks/tests/lsdyna/cantilever.k index 3265912..e1ee619 100644 --- a/crates/decks/tests/lsdyna/cantilever.k +++ b/crates/decks/tests/lsdyna/cantilever.k @@ -1,5 +1,5 @@ *KEYWORD long=y -$ Written by ModelCraft: implicit linear analysis, long format (20-column fields). +$ Written by FieldCraft: implicit linear analysis, long format (20-column fields). $ Units: mm-t-s-N-MPa (consistent; nothing is converted). *NODE $ nid x y z diff --git a/crates/decks/tests/lsdyna/cantilever_modes.k b/crates/decks/tests/lsdyna/cantilever_modes.k index fa92d9c..1dea6f5 100644 --- a/crates/decks/tests/lsdyna/cantilever_modes.k +++ b/crates/decks/tests/lsdyna/cantilever_modes.k @@ -1,5 +1,5 @@ *KEYWORD long=y -$ Written by ModelCraft: implicit linear analysis, long format (20-column fields). +$ Written by FieldCraft: implicit linear analysis, long format (20-column fields). $ Units: mm-t-s-N-MPa (consistent; nothing is converted). $ 2 steps are written as *CASE analyses: run LS-DYNA with CASE on its command line. *NODE diff --git a/crates/decks/tests/lsdyna/plate.k b/crates/decks/tests/lsdyna/plate.k index 2aea021..8f86815 100644 --- a/crates/decks/tests/lsdyna/plate.k +++ b/crates/decks/tests/lsdyna/plate.k @@ -1,5 +1,5 @@ *KEYWORD long=y -$ Written by ModelCraft: implicit linear analysis, long format (20-column fields). +$ Written by FieldCraft: implicit linear analysis, long format (20-column fields). $ Units: mm-t-s-N-MPa (consistent; nothing is converted). *NODE $ nid x y z diff --git a/crates/decks/tests/lsdyna/solids.k b/crates/decks/tests/lsdyna/solids.k index bc2cae8..43d2add 100644 --- a/crates/decks/tests/lsdyna/solids.k +++ b/crates/decks/tests/lsdyna/solids.k @@ -1,5 +1,5 @@ *KEYWORD long=y -$ Written by ModelCraft: implicit linear analysis, long format (20-column fields). +$ Written by FieldCraft: implicit linear analysis, long format (20-column fields). $ Units: m-kg-s-N-Pa (consistent; nothing is converted). *NODE $ nid x y z diff --git a/crates/decks/tests/nastran.rs b/crates/decks/tests/nastran.rs index ca79a66..31785a2 100644 --- a/crates/decks/tests/nastran.rs +++ b/crates/decks/tests/nastran.rs @@ -2,13 +2,13 @@ //! output (grids, elements, constraint sets, case control), node order checked against Nastran's //! own element conventions by Jacobian sign and mid-side positions, and what is unsupported. //! -//! Golden files are rewritten with `MODELCRAFT_BLESS=1 cargo test -p modelcraft-decks`. +//! Golden files are rewritten with `FIELDCRAFT_BLESS=1 cargo test -p fieldcraft-decks`. use std::collections::BTreeMap; -use modelcraft_decks::nastran; -use modelcraft_decks::{Error, with_steps}; -use modelcraft_model::{ +use fieldcraft_decks::nastran; +use fieldcraft_decks::{Error, with_steps}; +use fieldcraft_model::{ BeamProfile, BoundaryCondition, Coupling, CouplingKind, Distribution, Dofs, Elastic, ElementFace, ElementKind, Formulation, Integration, Load, Material, Model, Procedure, ReferencePoint, Section, SectionKind, Set, Step, Target, UnitSystem, }; @@ -266,12 +266,12 @@ fn modes() -> Model { fn golden(name: &str, deck: &str) { let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden/nastran").join(format!("{name}.bdf")); - if std::env::var_os("MODELCRAFT_BLESS").is_some() { + if std::env::var_os("FIELDCRAFT_BLESS").is_some() { std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::write(&path, deck).unwrap(); } - let want = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (bless with MODELCRAFT_BLESS=1)", path.display())); - assert_eq!(deck, want, "{name}: the deck changed (bless with MODELCRAFT_BLESS=1 after checking it)"); + let want = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (bless with FIELDCRAFT_BLESS=1)", path.display())); + assert_eq!(deck, want, "{name}: the deck changed (bless with FIELDCRAFT_BLESS=1 after checking it)"); } fn unsupported(m: &Model) -> String { @@ -640,7 +640,7 @@ fn steps_are_chosen_and_mixed_solutions_are_refused() { // A modes step holds the clamp but carries no loads. assert!(r.cards("FORCE").next().is_none()); assert_eq!(read(&nastran::write(&with_steps(&m, &only(&["Bend"])).unwrap()).unwrap()).sol, 101); - let nastran = modelcraft_decks::format("nastran").unwrap(); + let nastran = fieldcraft_decks::format("nastran").unwrap(); assert_eq!(nastran.write_steps(&m, Some(&only(&["Bend"]))).unwrap().text, nastran::write(&with_steps(&m, &only(&["Bend"])).unwrap()).unwrap()); for bad in [only(&[]), only(&["Nope"]), only(&[&"x".repeat(100_000)]), only(&["", "\u{0}"])] { @@ -740,7 +740,7 @@ fn conflicting_values_and_ids_at_the_top_of_the_range() { /// damage and erosion are reported. #[test] fn plasticity_is_written_as_mats1() { - use modelcraft_model::{Erosion, Hardening, HardeningPoint, Plastic}; + use fieldcraft_model::{Erosion, Hardening, HardeningPoint, Plastic}; let with = |p: Plastic| { let mut m = cantilever(); m.replace_material("Steel", Material { plastic: Some(p), ..steel() }).unwrap(); diff --git a/crates/decks/tests/planar.rs b/crates/decks/tests/planar.rs index 3da995c..c6f4d57 100644 --- a/crates/decks/tests/planar.rs +++ b/crates/decks/tests/planar.rs @@ -1,8 +1,8 @@ //! 2D models (plane stress, plane strain, axisymmetric) in every deck format: element names, //! section data, edge surfaces and pressures, centrifugal loads, and what a format cannot write. -use modelcraft_decks::{Error, abaqus, edge_pressure_loads, lsdyna, nastran}; -use modelcraft_model::{ +use fieldcraft_decks::{Error, abaqus, edge_pressure_loads, lsdyna, nastran}; +use fieldcraft_model::{ BoundaryCondition, Elastic, ElementEdge, ElementKind, Formulation, Integration, Load, Material, Model, Procedure, Section, SectionKind, Set, Step, Target, UnitSystem, }; diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 2eee7f6..16a2a87 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-engine" -description = "ModelCraft's command engine: every user-visible action as a command with a JSON form" +name = "fieldcraft-engine" +description = "FieldCraft's command engine: every user-visible action as a command with a JSON form" version.workspace = true edition.workspace = true license.workspace = true @@ -9,17 +9,17 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } -modelcraft-geometry = { workspace = true } -modelcraft-mesh = { workspace = true } -modelcraft-io = { workspace = true } -modelcraft-decks = { workspace = true } -modelcraft-solver = { workspace = true } -modelcraft-explicit = { workspace = true } -modelcraft-fem = { workspace = true } -modelcraft-lbm = { workspace = true } -modelcraft-parallel = { workspace = true } -modelcraft-render = { workspace = true } +fieldcraft-model = { workspace = true } +fieldcraft-geometry = { workspace = true } +fieldcraft-mesh = { workspace = true } +fieldcraft-io = { workspace = true } +fieldcraft-decks = { workspace = true } +fieldcraft-solver = { workspace = true } +fieldcraft-explicit = { workspace = true } +fieldcraft-fem = { workspace = true } +fieldcraft-lbm = { workspace = true } +fieldcraft-parallel = { workspace = true } +fieldcraft-render = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/engine/src/autosave.rs b/crates/engine/src/autosave.rs index 49184b0..d4f7a32 100644 --- a/crates/engine/src/autosave.rs +++ b/crates/engine/src/autosave.rs @@ -2,16 +2,16 @@ //! //! The desktop app keeps an [`Autosaver`] and calls [`Autosaver::save`] on a timer: while the //! session has unsaved changes not yet autosaved, the whole model is written to the app's entry in -//! the recovery folder ([`modelcraft_io::recovery`]); once nothing is unsaved the entry goes. After +//! the recovery folder ([`fieldcraft_io::recovery`]); once nothing is unsaved the entry goes. After //! a crash the next launch lists the entry (`file.recovery.list`) and opens it (`file.recover`). use std::path::Path; -use modelcraft_io::recovery::Slot; +use fieldcraft_io::recovery::Slot; use crate::{Result, Session}; -pub use modelcraft_io::recovery::default_dir; +pub use fieldcraft_io::recovery::default_dir; /// One app's autosaver. #[derive(Debug)] @@ -67,7 +67,7 @@ mod tests { #[test] fn autosaves_unsaved_work_and_recovers_it_after_a_crash() { - let dir = std::env::temp_dir().join(format!("modelcraft-autosave-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-autosave-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); let mut s = Session::default(); let mut a = Autosaver::new(&dir).unwrap(); diff --git a/crates/engine/src/catalogue.rs b/crates/engine/src/catalogue.rs index e02b5e0..3ac0515 100644 --- a/crates/engine/src/catalogue.rs +++ b/crates/engine/src/catalogue.rs @@ -1,4 +1,4 @@ -//! The capability catalogue: everything ModelCraft does or will do, in one table. +//! The capability catalogue: everything FieldCraft does or will do, in one table. //! //! Each [`Capability`] is a toolbar entry: its tab and panel, its label and one-line tip, and //! either the command of the same id it runs ([`Status::Ready`]) or the milestone that brings it @@ -138,7 +138,7 @@ pub enum Status { Ready, /// Coming in this roadmap milestone (`M2`…). Planned { milestone: &'static str }, - /// Runs `deck.write` with this `format` once `modelcraft_decks::FORMATS` has a writer for it; + /// Runs `deck.write` with this `format` once `fieldcraft_decks::FORMATS` has a writer for it; /// until then coming in `milestone`. Deck { format: &'static str, milestone: &'static str }, } @@ -163,7 +163,7 @@ impl Capability { match self.status { Status::Ready => Some(self.id), Status::Planned { .. } => None, - Status::Deck { format, .. } => modelcraft_decks::format(format).map(|_| crate::cmd::deck::WRITE.id), + Status::Deck { format, .. } => fieldcraft_decks::format(format).map(|_| crate::cmd::deck::WRITE.id), } } @@ -431,13 +431,13 @@ pub static CATALOGUE: &[Capability] = &[ deck("deck.write.abaqus", "Abaqus Input", "abaqus", "Write an Abaqus input file (.inp)"), deck("deck.write.nastran", "Nastran Deck", "nastran", "Write Nastran bulk data (SOL 101, 103)"), deck("deck.write.lsdyna", "LS-DYNA Deck", "lsdyna", "Write an LS-DYNA keyword file"), - soon("deck.read", "Read Deck", Solve, "Decks", "M9", "Read a deck, keeping keywords ModelCraft does not know"), + soon("deck.read", "Read Deck", Solve, "Decks", "M9", "Read a deck, keeping keywords FieldCraft does not know"), ready_more( "solve.static", "Static", Solve, "Implicit", - "Linear static (or coupled temperature–displacement) solve of a step with ModelCraft's own solver; heat transfer in the drop-down", + "Linear static (or coupled temperature–displacement) solve of a step with FieldCraft's own solver; heat transfer in the drop-down", &["solve.heat"], ), ready("solve.frequency", "Frequency", Solve, "Implicit", "Natural frequencies and mode shapes of a frequency step"), @@ -482,10 +482,10 @@ pub static CATALOGUE: &[Capability] = &[ soon("results.convergence", "Convergence", Results, "Files", "M9", "Mesh convergence studies"), // ---- Tools ---- ready("model.info", "Model Info", Tools, "Model", "Node and element counts and the model summary"), - ready("file.open", "Open Model", Tools, "File", "Open a .modelcraft model"), + ready("file.open", "Open Model", Tools, "File", "Open a .fieldcraft model"), ready("file.sample", "Samples", Tools, "File", "Open a sample model, ready to solve"), - ready("file.save", "Save", Tools, "File", "Save the model to its .modelcraft file"), - ready("file.save_as", "Save As", Tools, "File", "Save the model to a new .modelcraft file"), + ready("file.save", "Save", Tools, "File", "Save the model to its .fieldcraft file"), + ready("file.save_as", "Save As", Tools, "File", "Save the model to a new .fieldcraft file"), ready_more( "file.recover", "Recover", @@ -527,7 +527,7 @@ pub fn features_page() -> String { let mut md = String::from( "# Features\n\n\ Generated from the capability catalogue (`crates/engine/src/catalogue.rs`) by\n\ - `cargo run -p modelcraft-cli -- features > docs/features.md`; a test fails when it is out of date.\n\ + `cargo run -p fieldcraft-cli -- features > docs/features.md`; a test fails when it is out of date.\n\ Every entry is a toolbar button. **Ready** entries run their command; **coming soon** entries are\n\ shown greyed with the roadmap milestone that brings them (ROADMAP.md). Scripts, the command line,\n\ the control channel and AI agents over MCP (docs/mcp.md) run the same commands.\n\n", @@ -592,16 +592,16 @@ mod tests { } /// Deck entries follow the writers: one entry per format, ready exactly when its writer is in - /// `modelcraft_decks::FORMATS`, running `deck.write` with its format. + /// `fieldcraft_decks::FORMATS`, running `deck.write` with its format. #[test] fn deck_entries_follow_the_writers() { let decks: Vec<(&Capability, &str)> = CATALOGUE.iter().filter_map(|c| if let Status::Deck { format, .. } = c.status { Some((c, format)) } else { None }).collect(); - for f in modelcraft_decks::FORMATS { + for f in fieldcraft_decks::FORMATS { assert!(decks.iter().any(|(_, name)| *name == f.name), "deck format `{}` has no catalogue entry", f.name); } for (c, format) in decks { - let written = modelcraft_decks::format(format).is_some(); + let written = fieldcraft_decks::format(format).is_some(); assert_eq!(c.command(), written.then_some("deck.write"), "{}", c.id); assert_eq!(c.milestone().is_none(), written, "{}", c.id); assert_eq!(c.preset(), serde_json::json!({"format": format})); @@ -640,6 +640,6 @@ mod tests { fn features_page_is_current() { let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/features.md"); let committed = std::fs::read_to_string(&path).unwrap_or_default().replace("\r\n", "\n"); - assert!(committed == features_page(), "docs/features.md is out of date: run `cargo run -p modelcraft-cli -- features > docs/features.md`"); + assert!(committed == features_page(), "docs/features.md is out of date: run `cargo run -p fieldcraft-cli -- features > docs/features.md`"); } } diff --git a/crates/engine/src/cmd/block.rs b/crates/engine/src/cmd/block.rs index 95baeea..854ab27 100644 --- a/crates/engine/src/cmd/block.rs +++ b/crates/engine/src/cmd/block.rs @@ -1,13 +1,13 @@ //! `mesh.block`: a structured mesh of one six-sided block (solid elements) or four-sided planar -//! patch (2D elements) from its corners and curved edges ([`modelcraft_mesh::block`]), joined to +//! patch (2D elements) from its corners and curved edges ([`fieldcraft_mesh::block`]), joined to //! the mesh already there and tagged as a body (a patch: a face of the body) with named faces, //! edges and vertices, so sets by geometry select it. use std::collections::{BTreeMap, BTreeSet}; -use modelcraft_mesh::block::{Block, BlockMesh, Curve, Patch, block, patch}; -use modelcraft_mesh::join::{check_conforming, weld}; -use modelcraft_model::{ElementKind, Entity, GeometryRef, Model}; +use fieldcraft_mesh::block::{Block, BlockMesh, Curve, Patch, block, patch}; +use fieldcraft_mesh::join::{check_conforming, weld}; +use fieldcraft_model::{ElementKind, Entity, GeometryRef, Model}; use serde::Deserialize; use serde_json::{Value, json}; @@ -178,7 +178,7 @@ fn block_mesh(s: &mut Session, p: &Value) -> Result { s.model.tag_element(label, body_tag)?; } for (refs, pieces, tag) in [ - (&face_refs, &mesh.faces, Model::tag_element_face as fn(&mut Model, u32, u8, u32) -> modelcraft_model::Result<()>), + (&face_refs, &mesh.faces, Model::tag_element_face as fn(&mut Model, u32, u8, u32) -> fieldcraft_model::Result<()>), (&edge_refs, &mesh.edges, Model::tag_element_edge), ] { for (k, r) in refs { diff --git a/crates/engine/src/cmd/deck.rs b/crates/engine/src/cmd/deck.rs index 134980c..6067c16 100644 --- a/crates/engine/src/cmd/deck.rs +++ b/crates/engine/src/cmd/deck.rs @@ -2,7 +2,7 @@ use std::path::Path; -use modelcraft_io::roots::Access; +use fieldcraft_io::roots::Access; use serde_json::{Value, json}; use super::req; @@ -11,22 +11,22 @@ use crate::{CommandKind, CommandSpec, EngineError, Result, Session}; pub const WRITE: CommandSpec = CommandSpec { id: "deck.write", label: "Write Solver Deck", - params: r#"{"format": "abaqus|nastran|lsdyna", "path": "part.inp", "steps": ["Load"]} — writes the model as a solver deck (formats: modelcraft_decks::FORMATS); `steps` (optional) writes only those steps, as a Nastran run needs (static or frequency steps, not both); LS-DYNA writes several steps as *CASE analyses (run it with CASE); fails naming every error of the model check and everything the format cannot express"#, + params: r#"{"format": "abaqus|nastran|lsdyna", "path": "part.inp", "steps": ["Load"]} — writes the model as a solver deck (formats: fieldcraft_decks::FORMATS); `steps` (optional) writes only those steps, as a Nastran run needs (static or frequency steps, not both); LS-DYNA writes several steps as *CASE analyses (run it with CASE); fails naming every error of the model check and everything the format cannot express"#, kind: CommandKind::Session, run: write, }; fn write(s: &mut Session, p: &Value) -> Result { let name: &str = req(p, "format")?; - let format = modelcraft_decks::format(name).ok_or_else(|| { + let format = fieldcraft_decks::format(name).ok_or_else(|| { let shown: String = name.chars().take(32).collect(); - let known: Vec<&str> = modelcraft_decks::FORMATS.iter().map(|f| f.name).collect(); + let known: Vec<&str> = fieldcraft_decks::FORMATS.iter().map(|f| f.name).collect(); EngineError::Params(format!("unknown deck format `{shown}`; known: {}", known.join(", "))) })?; let path = super::file_param(s, p, "path", Access::Write)?; let steps: Option> = super::opt(p, "steps")?; let deck = format.write_steps(&s.model, steps.as_deref())?; - modelcraft_io::fs::write_atomic(Path::new(&path), deck.text.as_bytes(), false)?; + fieldcraft_io::fs::write_atomic(Path::new(&path), deck.text.as_bytes(), false)?; Ok(json!({"format": format.name, "path": path, "bytes": deck.text.len(), "warnings": deck.warnings})) } @@ -36,7 +36,7 @@ mod tests { #[test] fn writes_the_defined_model_and_refuses_what_it_cannot() { - let dir = std::env::temp_dir().join(format!("modelcraft-deck-write-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-deck-write-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("bar.inp"); let path_str = path.to_string_lossy().into_owned(); @@ -46,7 +46,7 @@ mod tests { let journal = s.journal.len(); let out = s.execute("deck.write", &json!({"format": "abaqus", "path": path_str})).unwrap(); let written = std::fs::read_to_string(&path).unwrap(); - assert_eq!(written, modelcraft_decks::abaqus::write(&s.model).unwrap()); + assert_eq!(written, fieldcraft_decks::abaqus::write(&s.model).unwrap()); assert_eq!(out["bytes"], written.len()); assert_eq!(out["warnings"].as_array().map(Vec::len), Some(1), "`press` is in no step: {out}"); assert!(written.contains("*ELEMENT, TYPE=C3D8I"), "{written}"); @@ -81,14 +81,14 @@ mod tests { #[test] fn writes_lsdyna_keyword_decks() { - let dir = std::env::temp_dir().join(format!("modelcraft-deck-lsdyna-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-deck-lsdyna-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("bar.k"); let mut s = Session::default(); crate::cmd::define::tests::defined_bar(&mut s); let out = s.execute("deck.write", &json!({"format": "lsdyna", "path": path.to_string_lossy()})).unwrap(); let written = std::fs::read_to_string(&path).unwrap(); - assert_eq!(written, modelcraft_decks::lsdyna::write(&s.model).unwrap().text); + assert_eq!(written, fieldcraft_decks::lsdyna::write(&s.model).unwrap().text); assert_eq!(out["format"], "lsdyna"); assert!(written.starts_with("*KEYWORD long=y\n") && written.contains("*CASE_BEGIN_2"), "two steps, two cases"); // One chosen step: a single analysis, no cases. diff --git a/crates/engine/src/cmd/define.rs b/crates/engine/src/cmd/define.rs index a5c913b..ff86bee 100644 --- a/crates/engine/src/cmd/define.rs +++ b/crates/engine/src/cmd/define.rs @@ -2,11 +2,11 @@ //! points, couplings, point masses, springs and dashpots, boundary conditions, loads, fluid //! domains and steps, and the step activity table. //! -//! Each definition's parameters are its JSON form in the model (the same form a `.modelcraft` +//! Each definition's parameters are its JSON form in the model (the same form a `.fieldcraft` //! file stores), plus its `name`. `create` fails when the name is taken, `edit` replaces an //! existing definition, `delete` refuses while another definition refers to it. -use modelcraft_model::{ +use fieldcraft_model::{ BoundaryCondition, Coupling, FaceCondition, FluidBoundary, FluidDomain, Load, Material, Model, PointMass, ReferencePoint, Section, Set, Spring, Step, }; diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index 6696c7e..545ead2 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -1,4 +1,4 @@ -//! Files: open and save `.modelcraft` models, and recover autosaved ones. +//! Files: open and save `.fieldcraft` models, and recover autosaved ones. //! //! A saved model refers to its design and imported meshes by path and content hash; opening it //! opens them again. Paths inside the model file's folder are stored relative to it, so a folder @@ -9,12 +9,12 @@ use std::path::{Component, Path, PathBuf}; use std::sync::Arc; -use modelcraft_geometry::tagged_mesh; -use modelcraft_geometry::{Design, MAX_DESIGN_BYTES, open_design}; -use modelcraft_io::{Geometry, ModelFile, SourceFile}; +use fieldcraft_geometry::tagged_mesh; +use fieldcraft_geometry::{Design, MAX_DESIGN_BYTES, open_design}; +use fieldcraft_io::{Geometry, ModelFile, SourceFile}; use serde_json::{Value, json}; -use modelcraft_io::roots::{Access, Roots}; +use fieldcraft_io::roots::{Access, Roots}; use super::{file_param, req}; use crate::{CommandKind, CommandSpec, EngineError, Result, Session, next_revision}; @@ -22,7 +22,7 @@ use crate::{CommandKind, CommandSpec, EngineError, Result, Session, next_revisio pub const OPEN: CommandSpec = CommandSpec { id: "file.open", label: "Open Model", - params: r#"{"path": "bracket.modelcraft"} — opens the model, its mesh and its journal, and opens its design and imported meshes again from their files (one that is missing or changed is listed in `warnings`; the model still opens); unsaved changes of the current model are discarded and the undo history starts empty"#, + params: r#"{"path": "bracket.fieldcraft"} — opens the model, its mesh and its journal, and opens its design and imported meshes again from their files (one that is missing or changed is listed in `warnings`; the model still opens); unsaved changes of the current model are discarded and the undo history starts empty"#, kind: CommandKind::Session, run: open, }; @@ -30,7 +30,7 @@ pub const OPEN: CommandSpec = CommandSpec { pub const SAVE: CommandSpec = CommandSpec { id: "file.save", label: "Save Model", - params: r#"{"path"?: "bracket.modelcraft"} — saves to `path`, else to the file the model was opened from or last saved to; written atomically, the previous version kept as `.bak`"#, + params: r#"{"path"?: "bracket.fieldcraft"} — saves to `path`, else to the file the model was opened from or last saved to; written atomically, the previous version kept as `.bak`"#, kind: CommandKind::Session, run: save, }; @@ -38,7 +38,7 @@ pub const SAVE: CommandSpec = CommandSpec { pub const SAVE_AS: CommandSpec = CommandSpec { id: "file.save_as", label: "Save Model As", - params: r#"{"path": "bracket.modelcraft"} — saves to a new file, which becomes the model's file"#, + params: r#"{"path": "bracket.fieldcraft"} — saves to a new file, which becomes the model's file"#, kind: CommandKind::Session, run: save_as, }; @@ -46,7 +46,7 @@ pub const SAVE_AS: CommandSpec = CommandSpec { pub const RECOVERY_LIST: CommandSpec = CommandSpec { id: "file.recovery.list", label: "Recoverable Models", - params: r#"{"dir"?: "…"} — models autosaved by apps that crashed or closed with unsaved changes, newest first (default folder: MODELCRAFT_RECOVERY_DIR or the per-user data folder)"#, + params: r#"{"dir"?: "…"} — models autosaved by apps that crashed or closed with unsaved changes, newest first (default folder: FIELDCRAFT_RECOVERY_DIR or the per-user data folder)"#, kind: CommandKind::Session, run: recovery_list, }; @@ -70,7 +70,7 @@ pub const RECOVERY_DISCARD: CommandSpec = CommandSpec { pub const SAMPLE: CommandSpec = CommandSpec { id: "file.sample", label: "Open Sample", - params: r#"{"name": "parts|cantilever|impact"} — opens a sample model (modelcraft_engine::sample) in place of the current one, unsaved changes discarded; `cantilever` and `impact` are ready to solve (solve.run)"#, + params: r#"{"name": "parts|cantilever|impact"} — opens a sample model (fieldcraft_engine::sample) in place of the current one, unsaved changes discarded; `cantilever` and `impact` are ready to solve (solve.run)"#, kind: CommandKind::Session, run: open_sample, }; @@ -120,7 +120,7 @@ fn reopen( base: Option<&Path>, roots: &Roots, max: usize, - open: impl FnOnce(&[u8]) -> modelcraft_geometry::Result, + open: impl FnOnce(&[u8]) -> fieldcraft_geometry::Result, warnings: &mut Vec, ) -> (Option, SourceFile) { let path = resolved_path(stored.path(), base); @@ -132,7 +132,7 @@ fn reopen( return (None, kept); } }; - let bytes = match modelcraft_geometry::read_file(&checked, max) { + let bytes = match fieldcraft_geometry::read_file(&checked, max) { Ok(b) => b, Err(e) => { warnings.push(format!("{what} `{path}` was not opened ({e}); the model opens without it")); @@ -154,15 +154,15 @@ fn reopen( } impl Session { - /// The bytes of the `.modelcraft` file holding this session, with geometry paths inside + /// The bytes of the `.fieldcraft` file holding this session, with geometry paths inside /// `base` (the folder the file goes to) stored relative to it. pub fn file_bytes(&self, base: Option<&Path>) -> Result> { let stored = |s: &SourceFile| s.with_path(stored_path(s.path(), base)); let geometry = Geometry { design: self.sources.design.as_ref().map(stored).transpose()?, - meshes: self.sources.meshes.iter().map(stored).collect::>()?, + meshes: self.sources.meshes.iter().map(stored).collect::>()?, }; - Ok(modelcraft_io::write(&self.model, &geometry, &self.journal)?) + Ok(fieldcraft_io::write(&self.model, &geometry, &self.journal)?) } /// A session holding what a file holds, its geometry opened again from the paths it names @@ -211,10 +211,10 @@ fn summary(s: &Session, warnings: &[String]) -> Value { }) } -/// Replace the session with a `.modelcraft` file's content; `path` is its own file (geometry +/// Replace the session with a `.fieldcraft` file's content; `path` is its own file (geometry /// paths are relative to its folder), `saved` whether that file holds this content. fn load(s: &mut Session, bytes: &[u8], path: Option, saved: bool) -> Result { - let file = modelcraft_io::read(bytes)?; + let file = fieldcraft_io::read(bytes)?; let (mut opened, warnings) = Session::from_file(file, path.as_deref().and_then(|p| Path::new(p).parent()), &s.file_access); opened.path = path; if saved { @@ -226,8 +226,8 @@ fn load(s: &mut Session, bytes: &[u8], path: Option, saved: bool) -> Res fn open(s: &mut Session, p: &Value) -> Result { let path = absolute(&file_param(s, p, "path", Access::Read)?); - let max = usize::try_from(modelcraft_io::MAX_FILE_BYTES).unwrap_or(usize::MAX); - let bytes = modelcraft_geometry::read_file(&path, max)?; + let max = usize::try_from(fieldcraft_io::MAX_FILE_BYTES).unwrap_or(usize::MAX); + let bytes = fieldcraft_geometry::read_file(&path, max)?; load(s, &bytes, Some(path.clone()), true).map_err(|e| EngineError::Params(format!("{path}: {e}"))) } @@ -238,7 +238,7 @@ fn save_to(s: &mut Session, path: &str) -> Result { return Err(EngineError::Params(format!("`{target}` exists and is not a regular file"))); } let bytes = s.file_bytes(Path::new(&target).parent())?; - modelcraft_io::fs::write_atomic(Path::new(&target), &bytes, true)?; + fieldcraft_io::fs::write_atomic(Path::new(&target), &bytes, true)?; s.mark_saved(target.clone()); Ok(json!({"path": target, "bytes": bytes.len()})) } @@ -265,19 +265,19 @@ fn recovery_dir(s: &Session, p: &Value, access: Access) -> Result { match p.get("dir") { Some(_) => Ok(PathBuf::from(file_param(s, p, "dir", access)?)), None => { - crate::autosave::default_dir().ok_or_else(|| EngineError::Params("no recovery folder: set MODELCRAFT_RECOVERY_DIR or give `dir`".into())) + crate::autosave::default_dir().ok_or_else(|| EngineError::Params("no recovery folder: set FIELDCRAFT_RECOVERY_DIR or give `dir`".into())) } } } fn recovery_list(s: &mut Session, p: &Value) -> Result { - let entries = modelcraft_io::recovery::orphans(&recovery_dir(s, p, Access::Read)?); + let entries = fieldcraft_io::recovery::orphans(&recovery_dir(s, p, Access::Read)?); serde_json::to_value(entries).map_err(|e| EngineError::Params(e.to_string())) } fn recover(s: &mut Session, p: &Value) -> Result { let dir = recovery_dir(s, p, Access::Read)?; - let (bytes, entry) = modelcraft_io::recovery::load(&dir, req::<&str>(p, "id")?)?; + let (bytes, entry) = fieldcraft_io::recovery::load(&dir, req::<&str>(p, "id")?)?; // Recovered work is unsaved until it is saved. Autosaves keep geometry paths absolute. load(s, &bytes, entry.path, false) } @@ -285,26 +285,26 @@ fn recover(s: &mut Session, p: &Value) -> Result { fn recovery_discard(s: &mut Session, p: &Value) -> Result { let dir = recovery_dir(s, p, Access::Write)?; if p.get("all").and_then(Value::as_bool) == Some(true) { - let ids: Vec = modelcraft_io::recovery::orphans(&dir).into_iter().map(|e| e.id).collect(); + let ids: Vec = fieldcraft_io::recovery::orphans(&dir).into_iter().map(|e| e.id).collect(); for id in &ids { - modelcraft_io::recovery::remove(&dir, id)?; + fieldcraft_io::recovery::remove(&dir, id)?; } return Ok(json!({"discarded": ids})); } let id = req::<&str>(p, "id")?; - modelcraft_io::recovery::remove(&dir, id)?; + fieldcraft_io::recovery::remove(&dir, id)?; Ok(json!({"discarded": [id]})) } #[cfg(test)] mod tests { - use modelcraft_geometry::sample::{Plate, plate_design}; + use fieldcraft_geometry::sample::{Plate, plate_design}; use serde_json::json; use super::*; fn dir(tag: &str) -> PathBuf { - let d = std::env::temp_dir().join(format!("modelcraft-file-{tag}-{}", std::process::id())); + let d = std::env::temp_dir().join(format!("fieldcraft-file-{tag}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&d); std::fs::create_dir_all(&d).unwrap(); d @@ -327,17 +327,17 @@ mod tests { s.execute("geometry.open", &json!({"path": design})).unwrap(); defined(&mut s); assert!(s.execute("file.save", &json!({})).is_err(), "no file yet, and no dialog"); - let path = d.join("bracket.modelcraft"); + let path = d.join("bracket.fieldcraft"); s.execute("file.save_as", &json!({"path": path})).unwrap(); assert!(!s.is_dirty()); let bytes = std::fs::read(&path).unwrap(); - let file = modelcraft_io::read(&bytes).unwrap(); + let file = fieldcraft_io::read(&bytes).unwrap(); assert_eq!(file.geometry.design.as_ref().unwrap().path(), "plate.solidcraft", "stored next to the model, relative"); // Saving again keeps the previous version and gives the same bytes. s.execute("file.save", &json!({})).unwrap(); assert_eq!(std::fs::read(&path).unwrap(), bytes); - assert_eq!(std::fs::read(modelcraft_io::fs::backup_path(&path)).unwrap(), bytes); + assert_eq!(std::fs::read(fieldcraft_io::fs::backup_path(&path)).unwrap(), bytes); let mut t = Session::default(); t.execute("mesh.node.add", &json!({"label": 9, "xyz": [0, 0, 0]})).unwrap(); @@ -383,17 +383,17 @@ mod tests { }; refused(s.execute("geometry.open", &json!({"path": outside.join("plate.solidcraft")}))); refused(s.execute("geometry.import.mesh", &json!({"path": outside.join("x.3mf")}))); - refused(s.execute("file.open", &json!({"path": outside.join("x.modelcraft")}))); + refused(s.execute("file.open", &json!({"path": outside.join("x.fieldcraft")}))); refused(s.execute("file.recovery.list", &json!({"dir": outside}))); refused(s.execute("file.recovery.discard", &json!({"all": true, "dir": input}))); s.execute("geometry.open", &json!({"path": input.join("plate.solidcraft")})).unwrap(); defined(&mut s); // Writes go to the write root only: not the read root, not elsewhere. - refused(s.execute("file.save_as", &json!({"path": input.join("m.modelcraft")}))); - refused(s.execute("file.save_as", &json!({"path": outside.join("m.modelcraft")}))); - refused(s.execute("file.save_as", &json!({"path": output.join("../outside/m.modelcraft")}))); + refused(s.execute("file.save_as", &json!({"path": input.join("m.fieldcraft")}))); + refused(s.execute("file.save_as", &json!({"path": outside.join("m.fieldcraft")}))); + refused(s.execute("file.save_as", &json!({"path": output.join("../outside/m.fieldcraft")}))); refused(s.execute("deck.write", &json!({"format": "abaqus", "path": outside.join("m.inp")}))); - let saved = output.join("m.modelcraft"); + let saved = output.join("m.fieldcraft"); s.execute("file.save_as", &json!({"path": saved})).unwrap(); assert!(saved.is_file()); assert_eq!(std::fs::read_dir(&outside).unwrap().count(), 1, "nothing written outside"); @@ -402,8 +402,8 @@ mod tests { // opens without it. let mut other = Session::default(); other.execute("geometry.open", &json!({"path": outside.join("plate.solidcraft")})).unwrap(); - other.execute("file.save_as", &json!({"path": input.join("other.modelcraft")})).unwrap(); - let r = s.execute("file.open", &json!({"path": input.join("other.modelcraft")})).unwrap(); + other.execute("file.save_as", &json!({"path": input.join("other.fieldcraft")})).unwrap(); + let r = s.execute("file.open", &json!({"path": input.join("other.fieldcraft")})).unwrap(); assert!(r["warnings"][0].as_str().unwrap().contains("automation path rejected"), "{r}"); assert!(s.design.is_none()); assert!(s.file_access.is_confined()); @@ -414,7 +414,7 @@ mod tests { #[test] fn hostile_file_params_and_files_change_nothing() { let d = dir("hostile"); - let junk = d.join("junk.modelcraft"); + let junk = d.join("junk.fieldcraft"); std::fs::write(&junk, b"PK\x03\x04 not a model").unwrap(); let mut s = Session::default(); defined(&mut s); @@ -422,12 +422,12 @@ mod tests { for (id, p) in [ ("file.open", json!({"path": junk})), ("file.open", json!({"path": d})), - ("file.open", json!({"path": d.join("missing.modelcraft")})), + ("file.open", json!({"path": d.join("missing.fieldcraft")})), ("file.open", json!({"path": "\u{0}"})), ("file.open", json!({"path": 5})), ("file.save", json!({"path": ""})), ("file.save", json!({"path": d})), - ("file.save_as", json!({"path": d.join("no/such/dir/a.modelcraft")})), + ("file.save_as", json!({"path": d.join("no/such/dir/a.fieldcraft")})), ("file.save_as", json!({"path": "x".repeat(5000)})), ("file.save_as", json!({})), ("file.recover", json!({"id": "../../etc/passwd", "dir": d})), diff --git a/crates/engine/src/cmd/geometry.rs b/crates/engine/src/cmd/geometry.rs index 114c066..9ee9b76 100644 --- a/crates/engine/src/cmd/geometry.rs +++ b/crates/engine/src/cmd/geometry.rs @@ -1,9 +1,9 @@ use std::sync::Arc; -use modelcraft_geometry::tagged_mesh; -use modelcraft_geometry::{MAX_DESIGN_BYTES, Shape, SurfaceMesh, open_design}; -use modelcraft_io::SourceFile; -use modelcraft_io::roots::Access; +use fieldcraft_geometry::tagged_mesh; +use fieldcraft_geometry::{MAX_DESIGN_BYTES, Shape, SurfaceMesh, open_design}; +use fieldcraft_io::SourceFile; +use fieldcraft_io::roots::Access; use serde_json::{Value, json}; use crate::{CommandKind, CommandSpec, EngineError, Result, Session}; @@ -42,7 +42,7 @@ pub const IMPORT_MESH: CommandSpec = CommandSpec { fn open(s: &mut Session, p: &Value) -> Result { let path = super::file_param(s, p, "path", Access::Read)?; - let bytes = modelcraft_geometry::read_file(&path, MAX_DESIGN_BYTES)?; + let bytes = fieldcraft_geometry::read_file(&path, MAX_DESIGN_BYTES)?; let source = SourceFile::of(super::file::absolute(&path), &bytes)?; let design = open_design(&bytes)?; let out = json!({ @@ -98,10 +98,10 @@ fn tessellate(s: &mut Session, p: &Value) -> Result { fn import_mesh(s: &mut Session, p: &Value) -> Result { let path = super::file_param(s, p, "path", Access::Read)?; - if s.sources.meshes.len() >= modelcraft_io::MAX_MESH_SOURCES { - return Err(EngineError::Params(format!("a model imports at most {} meshes", modelcraft_io::MAX_MESH_SOURCES))); + if s.sources.meshes.len() >= fieldcraft_io::MAX_MESH_SOURCES { + return Err(EngineError::Params(format!("a model imports at most {} meshes", fieldcraft_io::MAX_MESH_SOURCES))); } - let bytes = modelcraft_geometry::read_file(&path, tagged_mesh::MAX_FILE_BYTES)?; + let bytes = fieldcraft_geometry::read_file(&path, tagged_mesh::MAX_FILE_BYTES)?; let source = SourceFile::of(super::file::absolute(&path), &bytes)?; let surfaces = tagged_mesh::read_3mf(&bytes)?; let first = s.surfaces.len(); @@ -130,7 +130,7 @@ mod tests { #[test] fn import_adds_tagged_surfaces_and_failures_change_nothing() { - let dir = std::env::temp_dir().join(format!("modelcraft-import-mesh-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-import-mesh-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let model = format!( r#" diff --git a/crates/engine/src/cmd/hex.rs b/crates/engine/src/cmd/hex.rs index 565706c..49cf3c8 100644 --- a/crates/engine/src/cmd/hex.rs +++ b/crates/engine/src/cmd/hex.rs @@ -2,11 +2,11 @@ use std::borrow::Cow; -use modelcraft_geometry::{FaceSurface, Shape, SurfaceMesh}; -use modelcraft_mesh::hex::{HexMesh, HexMethod, HexOptions, Surface, hex_mesh}; -use modelcraft_mesh::size::UniformSize; -use modelcraft_mesh::surface::BoundarySurface; -use modelcraft_model::{GeometryRef, Model}; +use fieldcraft_geometry::{FaceSurface, Shape, SurfaceMesh}; +use fieldcraft_mesh::hex::{HexMesh, HexMethod, HexOptions, Surface, hex_mesh}; +use fieldcraft_mesh::size::UniformSize; +use fieldcraft_mesh::surface::BoundarySurface; +use fieldcraft_model::{GeometryRef, Model}; use serde_json::{Value, json}; use crate::{CommandKind, CommandSpec, EngineError, Result, Session}; @@ -175,7 +175,7 @@ fn generate(s: &mut Session, p: &Value, default: HexMethod, allowed: &[HexMethod .map_err(|e| EngineError::Params(format!("body `{}`: {e}", b.name)))?; let chord = if b.surfaces.is_empty() { 0.0 } else { number(p, "tolerance")?.unwrap_or(DEFAULT_RELATIVE_TOLERANCE * b.diagonal) }; let options = - HexOptions { method, quadratic, chordal_tolerance: chord, max_elements: modelcraft_model::MAX_ELEMENTS / 8, ..HexOptions::default() }; + HexOptions { method, quadratic, chordal_tolerance: chord, max_elements: fieldcraft_model::MAX_ELEMENTS / 8, ..HexOptions::default() }; let mesh = hex_mesh(&surface, &b.surfaces, &UniformSize(h), &options).map_err(|e| EngineError::Params(format!("body `{}`: {e}", b.name)))?; meshes.push((b, mesh)); } @@ -242,7 +242,7 @@ fn tag_faces(model: &mut Model, b: &Input<'_>, m: &HexMesh, first_element: u32) #[cfg(test)] mod tests { use super::*; - use modelcraft_geometry::sample::Plate; + use fieldcraft_geometry::sample::Plate; fn opened() -> (crate::tests::TempDir, Session) { let (dir, path) = crate::tests::sample_design_file(&Plate::default()); @@ -261,7 +261,7 @@ mod tests { let n = body["elements"].as_u64().unwrap() as usize; assert!(n > 100, "{r}"); assert!(body["quality"]["min_scaled_jacobian"].as_f64().unwrap() > 0.3, "{r}"); - assert_eq!(s.model.element_counts(), vec![(modelcraft_model::ElementKind::Hex8, n)]); + assert_eq!(s.model.element_counts(), vec![(fieldcraft_model::ElementKind::Hex8, n)]); // Volume: the plate minus the hole, to the tessellation's accuracy. let v = 40.0 * 30.0 * 10.0 - std::f64::consts::PI * 25.0 * 10.0; assert!((body["volume"].as_f64().unwrap() - v).abs() < 0.01 * v, "{r}"); @@ -280,7 +280,7 @@ mod tests { // Hex20 with mid-edge nodes on the hole. let r2 = s.execute("mesh.hex.generate", &json!({"order": 2, "size": 2.5})).unwrap(); assert_eq!(r2["bodies"][0]["kind"], "hex20"); - assert_eq!(s.model.element_counts(), vec![(modelcraft_model::ElementKind::Hex20, n)]); + assert_eq!(s.model.element_counts(), vec![(fieldcraft_model::ElementKind::Hex20, n)]); let v2 = r2["bodies"][0]["volume"].as_f64().unwrap(); assert!((v2 - v).abs() < (body["volume"].as_f64().unwrap() - v).abs() + 1e-6 * v, "hex20 {v2} vs hex8 {} vs {v}", body["volume"]); // The set still resolves after the remesh. @@ -288,7 +288,7 @@ mod tests { assert!(set["faces"]["count"].as_u64().unwrap() > 0); // Undo returns the hex8 mesh. s.execute("edit.undo", &json!({})).unwrap(); - assert_eq!(s.model.element_counts(), vec![(modelcraft_model::ElementKind::Hex8, n)]); + assert_eq!(s.model.element_counts(), vec![(fieldcraft_model::ElementKind::Hex8, n)]); } #[test] diff --git a/crates/engine/src/cmd/mesh.rs b/crates/engine/src/cmd/mesh.rs index e11678c..7aee221 100644 --- a/crates/engine/src/cmd/mesh.rs +++ b/crates/engine/src/cmd/mesh.rs @@ -1,12 +1,12 @@ use std::borrow::Cow; use std::collections::BTreeMap; -use modelcraft_geometry::SurfaceMesh; -use modelcraft_mesh::cdt::{CdtOptions, tetrahedralize}; -use modelcraft_mesh::delaunay::Triangulation; -use modelcraft_mesh::quality::{self, QualityReport}; -use modelcraft_mesh::surface::{BoundarySurface, Defect, FeatureKind, SurfaceOptions}; -use modelcraft_model::{ElementKind, GeometryRef, MAX_ELEMENTS}; +use fieldcraft_geometry::SurfaceMesh; +use fieldcraft_mesh::cdt::{CdtOptions, tetrahedralize}; +use fieldcraft_mesh::delaunay::Triangulation; +use fieldcraft_mesh::quality::{self, QualityReport}; +use fieldcraft_mesh::surface::{BoundarySurface, Defect, FeatureKind, SurfaceOptions}; +use fieldcraft_model::{ElementKind, GeometryRef, MAX_ELEMENTS}; use serde_json::{Value, json}; use super::{label, labels, listed, optional_number, points, xyz}; @@ -196,8 +196,8 @@ fn polygon(v: &Value, what: &str) -> Result> { } fn planar(s: &mut Session, p: &Value) -> Result { - use modelcraft_mesh::planar::{Curve, face_boundary, mesh_region}; - use modelcraft_model::Entity; + use fieldcraft_mesh::planar::{Curve, face_boundary, mesh_region}; + use fieldcraft_model::Entity; let size = p .get("size") @@ -212,7 +212,7 @@ fn planar(s: &mut Session, p: &Value) -> Result { .filter(|k| k.dimension() == 2) .ok_or_else(|| EngineError::Params("`kind` must be quad4, quad8, tri3 or tri6".into()))?, }; - let opts = modelcraft_mesh::quad::QuadOptions { + let opts = fieldcraft_mesh::quad::QuadOptions { max_elements: optional_count(p, "max_elements", 1_000_000, 1..=PLANAR_MAX_ELEMENTS)?, ..Default::default() }; @@ -415,7 +415,7 @@ fn surface_check(s: &mut Session, p: &Value) -> Result { let options = surface_options(p, &mesh)?; let surface = match BoundarySurface::new(&mesh.positions, &mesh.triangles, &mesh.tri_face, &options) { Ok(surface) => surface, - Err(modelcraft_mesh::Error::Surface(d)) => { + Err(fieldcraft_mesh::Error::Surface(d)) => { return Ok(json!({"valid": false, "defects": {"count": d.total, "listed": d.listed.iter().map(defect_json).collect::>()}})); } Err(e) => return Err(e.into()), @@ -511,8 +511,8 @@ mod tests { #[test] fn a_tessellated_block_is_a_valid_boundary() { - use modelcraft_mesh::surface::{BoundarySurface, FeatureKind, SurfaceOptions}; - let mesh = modelcraft_geometry::block([0.0; 3], [2.0, 3.0, 4.0]).unwrap().tessellate(0.01).unwrap(); + use fieldcraft_mesh::surface::{BoundarySurface, FeatureKind, SurfaceOptions}; + let mesh = fieldcraft_geometry::block([0.0; 3], [2.0, 3.0, 4.0]).unwrap().tessellate(0.01).unwrap(); let options = SurfaceOptions { weld_distance: mesh.weld_distance, ..SurfaceOptions::default() }; let s = BoundarySurface::new(&mesh.positions, &mesh.triangles, &mesh.tri_face, &options).unwrap(); assert!(s.points().len() < mesh.positions.len(), "the tessellation is a soup that welding joins"); @@ -535,9 +535,9 @@ mod tests { fn surface_check_on_an_open_design_and_on_imported_surfaces() { use std::sync::Arc; - use modelcraft_geometry::sample::{Plate, plate_design}; - use modelcraft_geometry::tagged_mesh::TaggedSurface; - use modelcraft_geometry::{SurfaceMesh, open_design}; + use fieldcraft_geometry::sample::{Plate, plate_design}; + use fieldcraft_geometry::tagged_mesh::TaggedSurface; + use fieldcraft_geometry::{SurfaceMesh, open_design}; let plate = Plate::default(); let mut s = Session { design: Some(Arc::new(open_design(&plate_design(&plate).unwrap()).unwrap())), ..Session::default() }; @@ -590,8 +590,8 @@ mod tests { fn tet_generate_meshes_a_body_with_tags_and_appends() { use std::sync::Arc; - use modelcraft_geometry::open_design; - use modelcraft_geometry::sample::{Plate, plate_design}; + use fieldcraft_geometry::open_design; + use fieldcraft_geometry::sample::{Plate, plate_design}; let plate = Plate::default(); let mut s = Session { design: Some(Arc::new(open_design(&plate_design(&plate).unwrap()).unwrap())), ..Session::default() }; @@ -599,7 +599,7 @@ mod tests { let elements = r["elements"].as_u64().unwrap() as usize; assert!(elements > 0, "{r}"); assert_eq!(s.model.elements().len(), elements); - assert!(s.model.elements().values().all(|e| e.kind == modelcraft_model::ElementKind::Tet4)); + assert!(s.model.elements().values().all(|e| e.kind == fieldcraft_model::ElementKind::Tet4)); let (volume, boundary) = (r["volume"].as_f64().unwrap(), r["boundary_volume"].as_f64().unwrap()); assert!((volume - boundary).abs() <= 1e-9 * boundary, "{r}"); // Every element is on the body; boundary faces carry the 8 face names. @@ -641,8 +641,8 @@ mod tests { fn tet_generate_honours_the_session_caps() { use std::sync::Arc; - use modelcraft_geometry::open_design; - use modelcraft_geometry::sample::{Plate, plate_design}; + use fieldcraft_geometry::open_design; + use fieldcraft_geometry::sample::{Plate, plate_design}; use crate::{EngineError, Limits}; @@ -650,7 +650,7 @@ mod tests { let mut s = Session { design: Some(design), limits: Limits { max_tets: 10, ..Limits::default() }, ..Session::default() }; let plate = json!({"body": "Body1", "tolerance": 0.5}); let refused = s.execute("mesh.tet.generate", &plate).unwrap_err(); - assert!(matches!(refused, EngineError::Mesh(modelcraft_mesh::Error::Limit(_))), "{refused}"); + assert!(matches!(refused, EngineError::Mesh(fieldcraft_mesh::Error::Limit(_))), "{refused}"); assert!(s.model.elements().is_empty()); let r = s.execute("mesh.tet.generate", &json!({"body": "Body1", "tolerance": 0.5, "max_tets": 1_000_000})).unwrap(); assert!(r["elements"].as_u64().unwrap() > 0, "{r}"); @@ -660,8 +660,8 @@ mod tests { fn tet_generate_on_an_imported_surface_and_an_open_one() { use std::sync::Arc; - use modelcraft_geometry::SurfaceMesh; - use modelcraft_geometry::tagged_mesh::TaggedSurface; + use fieldcraft_geometry::SurfaceMesh; + use fieldcraft_geometry::tagged_mesh::TaggedSurface; let p = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]; let t = vec![[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]; @@ -692,7 +692,7 @@ mod tests { assert_eq!(r["kind"], "quad8"); let elements = r["elements"].as_u64().unwrap() as usize; assert!(elements > 20, "{r}"); - assert!(s.model.elements().values().all(|e| e.kind == modelcraft_model::ElementKind::Quad8)); + assert!(s.model.elements().values().all(|e| e.kind == fieldcraft_model::ElementKind::Quad8)); assert!(s.model.nodes().values().all(|n| n.xyz[2] == 0.0)); // Sets on the tagged edges carry the boundary condition and the load. for (cmd, params) in [ @@ -736,8 +736,8 @@ mod tests { fn planar_meshes_a_flat_face_of_a_design_with_edges_named_by_their_neighbours() { use std::sync::Arc; - use modelcraft_geometry::open_design; - use modelcraft_geometry::sample::{Plate, plate_design}; + use fieldcraft_geometry::open_design; + use fieldcraft_geometry::sample::{Plate, plate_design}; let design = open_design(&plate_design(&Plate::default()).unwrap()).unwrap(); let names: Vec = design.bodies[0].faces().iter().map(|f| f.name.clone()).collect(); diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index 2d73866..c12d67c 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -13,11 +13,11 @@ mod results; pub(crate) mod solve; mod tags; -use modelcraft_model::MAX_LABEL; +use fieldcraft_model::MAX_LABEL; use serde::Deserialize; use serde_json::{Value, json}; -use modelcraft_io::roots::Access; +use fieldcraft_io::roots::Access; use crate::{CommandSpec, EngineError, Result, Session}; @@ -120,7 +120,7 @@ pub(crate) fn label(p: &Value, key: &str) -> Result { /// The first node and element labels after the model's largest, for `nodes` new nodes and /// `elements` new elements, all within [`MAX_LABEL`]. -pub(crate) fn fresh_labels(model: &modelcraft_model::Model, nodes: usize, elements: usize) -> Result<(u32, u32)> { +pub(crate) fn fresh_labels(model: &fieldcraft_model::Model, nodes: usize, elements: usize) -> Result<(u32, u32)> { let first = |last: Option<&u32>, count: usize| { let first = last.map_or(Some(1), |l| l.checked_add(1))?; (u64::from(first) + count as u64 <= u64::from(MAX_LABEL) + 1).then_some(first) @@ -168,7 +168,7 @@ pub(crate) fn labels(p: &Value, key: &str, max_len: usize) -> Result> { fn path_param<'a>(p: &'a Value, key: &str) -> Result<&'a str> { p.get(key) .and_then(Value::as_str) - .filter(|s| !s.trim().is_empty() && s.len() <= modelcraft_io::MAX_PATH_LEN && !s.contains('\0')) + .filter(|s| !s.trim().is_empty() && s.len() <= fieldcraft_io::MAX_PATH_LEN && !s.contains('\0')) .ok_or_else(|| EngineError::Params(format!("`{key}` must be a file path"))) } diff --git a/crates/engine/src/cmd/model.rs b/crates/engine/src/cmd/model.rs index c3cebae..592da36 100644 --- a/crates/engine/src/cmd/model.rs +++ b/crates/engine/src/cmd/model.rs @@ -1,4 +1,4 @@ -use modelcraft_model::{Severity, UnitSystem}; +use fieldcraft_model::{Severity, UnitSystem}; use serde_json::{Value, json}; use super::{listed, name}; diff --git a/crates/engine/src/cmd/solve.rs b/crates/engine/src/cmd/solve.rs index db97d86..2e40549 100644 --- a/crates/engine/src/cmd/solve.rs +++ b/crates/engine/src/cmd/solve.rs @@ -1,12 +1,12 @@ //! `solve.*`: run the model's steps with the implicit, the explicit or the lattice Boltzmann //! solver, keep the results in the session, and read them back. -use modelcraft_explicit::{ExplicitResult, solve_explicit}; -use modelcraft_lbm::{FluidResult, Obstacle}; -use modelcraft_mesh::surface::{BoundarySurface, SurfaceOptions}; -use modelcraft_model::FluidDomain; -use modelcraft_parallel as parallel; -use modelcraft_solver::{ +use fieldcraft_explicit::{ExplicitResult, solve_explicit}; +use fieldcraft_lbm::{FluidResult, Obstacle}; +use fieldcraft_mesh::surface::{BoundarySurface, SurfaceOptions}; +use fieldcraft_model::FluidDomain; +use fieldcraft_parallel as parallel; +use fieldcraft_solver::{ FrequencyOptions, Limits, Location, MassKind, StaticResult, StepResult as ImplicitResult, solve_frequency, solve_heat, solve_static, }; use serde_json::{Value, json}; @@ -25,7 +25,7 @@ pub const RUN: CommandSpec = CommandSpec { pub const STATIC: CommandSpec = CommandSpec { id: "solve.static", label: "Solve Static Step", - params: r#"{"step": "Load", "max_dofs": 6000000, "max_factor_entries": 400000000, "threads": 8} — solves a linear static or coupled temperature–displacement step (the only one when `step` is left out) and keeps the result in the session; the caps may only be lowered; `threads` (1 to 256, by default the session's: `MODELCRAFT_THREADS` or the cores) changes only the time, never the result"#, + params: r#"{"step": "Load", "max_dofs": 6000000, "max_factor_entries": 400000000, "threads": 8} — solves a linear static or coupled temperature–displacement step (the only one when `step` is left out) and keeps the result in the session; the caps may only be lowered; `threads` (1 to 256, by default the session's: `FIELDCRAFT_THREADS` or the cores) changes only the time, never the result"#, kind: CommandKind::Model, run: run_static, }; @@ -129,7 +129,7 @@ fn extreme(values: impl Iterator, better: fn(f64, f64) -> boo } fn run_all(s: &mut Session, _: &Value) -> Result { - use modelcraft_model::Procedure; + use fieldcraft_model::Procedure; type Solve = fn(&mut Session, &Value) -> Result; let steps: Vec<(String, &'static str, Solve)> = s .model @@ -242,7 +242,7 @@ fn run_frequency(s: &mut Session, p: &Value) -> Result { } fn run_explicit(s: &mut Session, p: &Value) -> Result { - use modelcraft_explicit::Limits as ExplicitLimits; + use fieldcraft_explicit::Limits as ExplicitLimits; let limits = ExplicitLimits { max_dofs: cap(p, "max_dofs", ExplicitLimits::MAX_DOFS)?, max_increments: cap(p, "max_increments", ExplicitLimits::MAX_INCREMENTS)?, @@ -340,18 +340,18 @@ fn obstacles(s: &Session, domain: &FluidDomain) -> Result> { } fn run_fluid(s: &mut Session, p: &Value) -> Result { - let defaults = modelcraft_lbm::Limits::default(); - let limits = modelcraft_lbm::Limits { + let defaults = fieldcraft_lbm::Limits::default(); + let limits = fieldcraft_lbm::Limits { max_cells: cap(p, "max_cells", defaults.max_cells)?, max_steps: cap(p, "max_steps", defaults.max_steps)?, max_cell_updates: cap(p, "max_cell_updates", defaults.max_cell_updates)?, max_frame_bytes: cap(p, "max_frame_bytes", defaults.max_frame_bytes)?, }; let step = opt::(p, "step")?; - let (_, domain) = modelcraft_lbm::fluid_step(&s.model, step.as_deref())?; + let (_, domain) = fieldcraft_lbm::fluid_step(&s.model, step.as_deref())?; let obstacles = obstacles(s, domain)?; let model = &s.model; - let r = parallel::with_threads(threads(s, p)?, || modelcraft_lbm::solve_fluid(model, step.as_deref(), &obstacles, &limits))?; + let r = parallel::with_threads(threads(s, p)?, || fieldcraft_lbm::solve_fluid(model, step.as_deref(), &obstacles, &limits))?; let summary = fluid_summary(&r); s.store_result(StepResult::Fluid(r)); Ok(summary) @@ -475,7 +475,7 @@ fn probe(s: &mut Session, p: &Value) -> Result { } /// The node with a result nearest to each point (lowest label on a tie), where it is and how far. -fn nearest_nodes(model: &modelcraft_model::Model, with_result: &[u32], at: &[[f64; 3]]) -> Result> { +fn nearest_nodes(model: &fieldcraft_model::Model, with_result: &[u32], at: &[[f64; 3]]) -> Result> { let candidates: Vec<(u32, [f64; 3])> = with_result.iter().filter_map(|&l| Some((l, model.node(l)?.xyz))).collect(); at.iter() .map(|p| { @@ -604,7 +604,7 @@ fn probe_frame(r: &ExplicitResult, step: &str, nodes: &[u32], elements: &[u32], Ok(json!({ "element": l, "stress": f.stress.get(i), - "von_mises": f.stress.get(i).map(modelcraft_fem::von_mises), + "von_mises": f.stress.get(i).map(fieldcraft_fem::von_mises), "strain": f.strain.get(i), "plastic_strain": f.plastic_strain.get(i), "damage": f.damage.get(i), @@ -855,8 +855,8 @@ pub(crate) mod tests { #[test] fn a_fluid_domain_takes_its_obstacles_from_the_open_design() { - use modelcraft_geometry::sample::{Plate, plate_design}; - let dir = std::env::temp_dir().join(format!("modelcraft-fluid-{}", std::process::id())); + use fieldcraft_geometry::sample::{Plate, plate_design}; + let dir = std::env::temp_dir().join(format!("fieldcraft-fluid-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("plate.solidcraft"); std::fs::write(&path, plate_design(&Plate::default()).unwrap()).unwrap(); @@ -965,7 +965,7 @@ pub(crate) mod tests { /// ρ = 1, clamped at x = 0; one node set per cross-section (`x0`, `x1`, …) and the tip's /// corner node `tip`; a frequency step `Modes` and the section `beam`. fn explicit_cantilever(s: &mut Session, length: f64, n: [usize; 2]) { - use modelcraft_model::{ElementKind, Set}; + use fieldcraft_model::{ElementKind, Set}; let [nx, ny] = n; let label = |i: usize, j: usize, k: usize| (1 + i + (nx + 1) * (j + (ny + 1) * k)) as u32; for k in 0..=ny { diff --git a/crates/engine/src/cmd/tags.rs b/crates/engine/src/cmd/tags.rs index f19f306..ef85de9 100644 --- a/crates/engine/src/cmd/tags.rs +++ b/crates/engine/src/cmd/tags.rs @@ -1,7 +1,7 @@ //! Geometry tags on the mesh: which body, face, edge or vertex each mesh entity lies on. Meshers //! write them; these commands set them by hand (for a mesh from elsewhere) and clear them. -use modelcraft_model::{ElementFace, GeometryRef, MAX_ELEMENTS}; +use fieldcraft_model::{ElementFace, GeometryRef, MAX_ELEMENTS}; use serde_json::{Value, json}; use super::{opt, req}; diff --git a/crates/engine/src/history.rs b/crates/engine/src/history.rs index 36a30c9..f25950c 100644 --- a/crates/engine/src/history.rs +++ b/crates/engine/src/history.rs @@ -14,9 +14,9 @@ use std::collections::VecDeque; use std::sync::Arc; -use modelcraft_geometry::Design; -use modelcraft_geometry::tagged_mesh::TaggedSurface; -use modelcraft_model::Model; +use fieldcraft_geometry::Design; +use fieldcraft_geometry::tagged_mesh::TaggedSurface; +use fieldcraft_model::Model; use serde_json::{Value, json}; use crate::{EngineError, Result, Session}; @@ -44,7 +44,7 @@ pub(crate) struct State { model: Model, design: Option>, surfaces: Vec>, - sources: modelcraft_io::Geometry, + sources: fieldcraft_io::Geometry, revision: u64, } diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 5c4c19c..73f832e 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -6,8 +6,8 @@ //! the geometry are restored and an [`EngineError`] comes back. //! //! Commands that change the model are journalled and can be undone and redone -//! (`edit.undo`, `edit.redo`; [`history`]). The session is saved to and opened from `.modelcraft` -//! files (`file.save`, `file.open`; [`modelcraft_io`]) and autosaved for recovery after a crash +//! (`edit.undo`, `edit.redo`; [`history`]). The session is saved to and opened from `.fieldcraft` +//! files (`file.save`, `file.open`; [`fieldcraft_io`]) and autosaved for recovery after a crash //! ([`autosave`]). #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] #![forbid(unsafe_code)] @@ -23,10 +23,10 @@ pub mod view; use std::collections::BTreeMap; use std::sync::Arc; -use modelcraft_explicit::ExplicitResult; -use modelcraft_geometry::Design; -use modelcraft_geometry::tagged_mesh::TaggedSurface; -use modelcraft_model::Model; +use fieldcraft_explicit::ExplicitResult; +use fieldcraft_geometry::Design; +use fieldcraft_geometry::tagged_mesh::TaggedSurface; +use fieldcraft_model::Model; use serde_json::Value; use crate::history::History; @@ -38,21 +38,21 @@ pub enum EngineError { #[error("{0}")] Params(String), #[error(transparent)] - Model(#[from] modelcraft_model::Error), + Model(#[from] fieldcraft_model::Error), #[error(transparent)] - Geometry(#[from] modelcraft_geometry::Error), + Geometry(#[from] fieldcraft_geometry::Error), #[error(transparent)] - Mesh(#[from] modelcraft_mesh::Error), + Mesh(#[from] fieldcraft_mesh::Error), #[error(transparent)] - File(#[from] modelcraft_io::Error), + File(#[from] fieldcraft_io::Error), #[error(transparent)] - Deck(#[from] modelcraft_decks::Error), + Deck(#[from] fieldcraft_decks::Error), #[error(transparent)] - Solver(#[from] modelcraft_solver::Error), + Solver(#[from] fieldcraft_solver::Error), #[error(transparent)] - Explicit(#[from] modelcraft_explicit::Error), + Explicit(#[from] fieldcraft_explicit::Error), #[error(transparent)] - Fluid(#[from] modelcraft_lbm::Error), + Fluid(#[from] fieldcraft_lbm::Error), /// A command panicked; the model is unchanged. #[error("internal error in {command}: {message}")] Internal { command: String, message: String }, @@ -63,9 +63,9 @@ pub type Result = std::result::Result; /// The result of a solved step, from the implicit, the explicit or the lattice Boltzmann solver. #[derive(Debug, Clone, PartialEq)] pub enum StepResult { - Implicit(modelcraft_solver::StepResult), + Implicit(fieldcraft_solver::StepResult), Explicit(ExplicitResult), - Fluid(modelcraft_lbm::FluidResult), + Fluid(fieldcraft_lbm::FluidResult), } impl StepResult { @@ -104,7 +104,7 @@ pub struct CommandSpec { /// Our own dotted, lower-case id: `.[.]`, such as `mesh.node.add`. pub id: &'static str, pub label: &'static str, - /// The JSON parameters, documented for scripts, `modelcraft-cli commands` and agents. + /// The JSON parameters, documented for scripts, `fieldcraft-cli commands` and agents. pub params: &'static str, pub kind: CommandKind, pub run: fn(&mut Session, &Value) -> Result, @@ -136,15 +136,15 @@ pub struct Limits { /// `mesh.tet.generate`'s `max_tets`. pub max_tets: usize, /// Threads the solvers use (the `solve.*` commands' `threads`): by default - /// `MODELCRAFT_THREADS` or the available cores, 1 to [`modelcraft_parallel::MAX_THREADS`]. + /// `FIELDCRAFT_THREADS` or the available cores, 1 to [`fieldcraft_parallel::MAX_THREADS`]. /// Results do not depend on it. pub threads: usize, } impl Default for Limits { fn default() -> Self { - let cdt = modelcraft_mesh::cdt::CdtOptions::default(); - Limits { max_steiner: cdt.max_steiner, max_tets: cdt.max_tets, threads: modelcraft_parallel::default_threads() } + let cdt = fieldcraft_mesh::cdt::CdtOptions::default(); + Limits { max_steiner: cdt.max_steiner, max_tets: cdt.max_tets, threads: fieldcraft_parallel::default_threads() } } } @@ -158,12 +158,12 @@ pub struct Session { /// Each is shared, so the per-command snapshot is cheap. pub surfaces: Vec>, /// The files the design and the surfaces were read from, with the hash of what was read: - /// what a `.modelcraft` file records to open them again. - pub sources: modelcraft_io::Geometry, + /// what a `.fieldcraft` file records to open them again. + pub sources: fieldcraft_io::Geometry, /// Commands that changed the model, in order (a script that rebuilds it). Undo takes the /// last one off; redo puts it back. pub journal: Vec<(String, Value)>, - /// The `.modelcraft` file the session was opened from or last saved to. + /// The `.fieldcraft` file the session was opened from or last saved to. pub path: Option, history: History, /// Identifies the current state: it changes with every edit, undo and redo, and comes back @@ -181,7 +181,7 @@ pub struct Session { pub limits: Limits, /// The folders commands may read and write files in (an automation client's roots, /// docs/mcp.md); unconfined by default. A file replacing the session keeps them. - pub file_access: modelcraft_io::roots::Roots, + pub file_access: fieldcraft_io::roots::Roots, } impl Session { @@ -265,7 +265,7 @@ impl Session { } /// The path to use for `access` to the file at `path`, checked against [`Session::file_access`]. - pub fn checked_path(&self, path: &str, access: modelcraft_io::roots::Access) -> Result { + pub fn checked_path(&self, path: &str, access: fieldcraft_io::roots::Access) -> Result { let checked = self.file_access.check(std::path::Path::new(path), access)?; match checked.to_str() { Some(p) => Ok(p.to_string()), @@ -283,7 +283,7 @@ impl Session { #[cfg(test)] mod tests { use super::*; - use modelcraft_geometry::sample::{Plate, plate_design}; + use fieldcraft_geometry::sample::{Plate, plate_design}; use serde_json::json; #[test] @@ -541,7 +541,7 @@ mod tests { fn new() -> Self { static N: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let dir = std::env::temp_dir().join(format!("modelcraft-engine-{}-{n}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-engine-{}-{n}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); TempDir(dir) } diff --git a/crates/engine/src/results.rs b/crates/engine/src/results.rs index f38f43a..ba9b328 100644 --- a/crates/engine/src/results.rs +++ b/crates/engine/src/results.rs @@ -7,9 +7,9 @@ //! journalled nor undone, and it does not change the session revision. A result kept from an //! earlier revision is still shown, marked stale ([`Session::latest_result`]). -use modelcraft_explicit::{ExplicitResult, Frame}; -use modelcraft_model::Model; -use modelcraft_solver::{FrequencyResult, HeatResult, StaticResult}; +use fieldcraft_explicit::{ExplicitResult, Frame}; +use fieldcraft_model::Model; +use fieldcraft_solver::{FrequencyResult, HeatResult, StaticResult}; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; @@ -21,16 +21,16 @@ pub enum Kind<'a> { Frequency(&'a FrequencyResult), Explicit(&'a ExplicitResult), /// Per cell, not per node: no nodal fields to display yet. - Fluid(&'a modelcraft_lbm::FluidResult), + Fluid(&'a fieldcraft_lbm::FluidResult), Heat(&'a HeatResult), } impl StepResult { pub fn kind(&self) -> Kind<'_> { match self { - StepResult::Implicit(modelcraft_solver::StepResult::Static(r)) => Kind::Static(r), - StepResult::Implicit(modelcraft_solver::StepResult::Frequency(r)) => Kind::Frequency(r), - StepResult::Implicit(modelcraft_solver::StepResult::Heat(r)) => Kind::Heat(r), + StepResult::Implicit(fieldcraft_solver::StepResult::Static(r)) => Kind::Static(r), + StepResult::Implicit(fieldcraft_solver::StepResult::Frequency(r)) => Kind::Frequency(r), + StepResult::Implicit(fieldcraft_solver::StepResult::Heat(r)) => Kind::Heat(r), StepResult::Explicit(r) => Kind::Explicit(r), StepResult::Fluid(r) => Kind::Fluid(r), } @@ -306,7 +306,7 @@ pub fn field_values(model: &Model, r: &StepResult, field: Field, pick: Pick) -> } }; let each = |f: &dyn Fn(&[f64; 6]) -> f64| -> Result> { Ok(stresses()?.iter().map(f).collect()) }; - let principal = |k: usize| each(&|t| modelcraft_fem::principal(t).get(k).copied().unwrap_or(f64::NAN)); + let principal = |k: usize| each(&|t| fieldcraft_fem::principal(t).get(k).copied().unwrap_or(f64::NAN)); match field { Field::Displacement => component(|v| magnitude(*v)), Field::DisplacementX => component(|v| v[0]), @@ -328,7 +328,7 @@ pub fn field_values(model: &Model, r: &StepResult, field: Field, pick: Pick) -> Kind::Heat(h) => Ok(h.flux.iter().map(|v| magnitude(*v)).collect()), _ => Err(unavailable()), }, - Field::VonMises => each(&modelcraft_fem::von_mises), + Field::VonMises => each(&fieldcraft_fem::von_mises), Field::MaxPrincipal => principal(0), Field::MidPrincipal => principal(1), Field::MinPrincipal => principal(2), diff --git a/crates/engine/src/sample.rs b/crates/engine/src/sample.rs index c117cca..f9119b8 100644 --- a/crates/engine/src/sample.rs +++ b/crates/engine/src/sample.rs @@ -235,7 +235,7 @@ mod tests { assert_eq!(info["nodes"], 9 * 5 * 3 + 4 * 4 * 4); // Tet volumes are positive and add up to the cube's. let mut volume = 0.0; - for e in s.model.elements().values().filter(|e| e.kind == modelcraft_model::ElementKind::Tet4) { + for e in s.model.elements().values().filter(|e| e.kind == fieldcraft_model::ElementKind::Tet4) { let p: Vec<[f64; 3]> = e.nodes.iter().map(|n| s.model.node(*n).unwrap().xyz).collect(); let v = signed_volume(&p) / 6.0; assert!(v > 0.0); diff --git a/crates/engine/src/view.rs b/crates/engine/src/view.rs index f8dfcda..e2d03db 100644 --- a/crates/engine/src/view.rs +++ b/crates/engine/src/view.rs @@ -1,5 +1,5 @@ //! Headless pictures of a session: the model, its geometry and the results display drawn with -//! the CPU rasterizer of [`modelcraft_render`], with no window or GPU (the MCP server's +//! the CPU rasterizer of [`fieldcraft_render`], with no window or GPU (the MCP server's //! screenshots without an app, docs/mcp.md). //! //! The picture follows the desktop viewport's conventions: geometry draws as faces with their @@ -8,9 +8,9 @@ //! deformed shape (automatic or chosen scale) with its contour bands. Animation is not drawn: a //! picture shows the full deformation (a mode at its positive shape). -use modelcraft_model::ElementKind; -use modelcraft_render::contour::{Bands, band_color, split_triangle}; -use modelcraft_render::{Aabb3, Camera, DisplayMesh, Rgb, Scene, SceneLine, SceneTri, StandardView, Vec3}; +use fieldcraft_model::ElementKind; +use fieldcraft_render::contour::{Bands, band_color, split_triangle}; +use fieldcraft_render::{Aabb3, Camera, DisplayMesh, Rgb, Scene, SceneLine, SceneTri, StandardView, Vec3}; use crate::results::{displacements, field_values, magnitude, range}; use crate::{EngineError, Result, Session}; @@ -181,7 +181,7 @@ pub fn render_png(s: &Session, view: StandardView, w: usize, h: usize, p: &Palet cam.set_view(view); cam.fit(&bounds); scene.radius = if bounds.is_empty() { 1.0 } else { (cam.target.dist(bounds.center()) + bounds.diagonal() * 0.5).max(1e-3) }; - let png = modelcraft_render::render_png(&scene, &cam, w, h).map_err(|e| EngineError::Params(format!("render: {e}")))?; + let png = fieldcraft_render::render_png(&scene, &cam, w, h).map_err(|e| EngineError::Params(format!("render: {e}")))?; Ok((png, shown)) } diff --git a/crates/explicit/Cargo.toml b/crates/explicit/Cargo.toml index 40845fe..8db3ff8 100644 --- a/crates/explicit/Cargo.toml +++ b/crates/explicit/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-explicit" -description = "ModelCraft's explicit dynamics solver: central-difference time integration with lumped mass, one-point hexahedra with hourglass control, nodal-pressure tetrahedra, bulk viscosity and energy accounting" +name = "fieldcraft-explicit" +description = "FieldCraft's explicit dynamics solver: central-difference time integration with lumped mass, one-point hexahedra with hourglass control, nodal-pressure tetrahedra, bulk viscosity and energy accounting" version.workspace = true edition.workspace = true license.workspace = true @@ -9,8 +9,8 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } -modelcraft-fem = { workspace = true } -modelcraft-parallel = { workspace = true } +fieldcraft-model = { workspace = true } +fieldcraft-fem = { workspace = true } +fieldcraft-parallel = { workspace = true } thiserror = { workspace = true } serde = { workspace = true } diff --git a/crates/explicit/src/discrete.rs b/crates/explicit/src/discrete.rs index 51034b1..a410dfc 100644 --- a/crates/explicit/src/discrete.rs +++ b/crates/explicit/src/discrete.rs @@ -3,7 +3,7 @@ //! - **Trusses** (rod2, rod3), elastic, with lumped (HRZ) mass. With nonlinear geometry a rod2 //! is corotational: its strain is (l − L)/L from its current length l and its force acts along //! its current axis (Belytschko et al. 2014, §2.4 and §4.5); a rod3 then is not supported yet. -//! Without it the internal force is K u with the kernel's stiffness ([`modelcraft_fem::truss`]). +//! Without it the internal force is K u with the kernel's stiffness ([`fieldcraft_fem::truss`]). //! The stress is the uniaxial tensor along the axis (the mean of the integration points'), and //! the critical increment comes from the kernel's frequency bound (exact for rod2: L/c). //! - **Point masses** add to the lumped mass of their node, per direction. @@ -21,10 +21,10 @@ //! §6.3), which is 2/D for a dashpot alone. Rotary inertia and rotational springs need //! rotational degrees of freedom, which this solver does not have yet. -use modelcraft_fem::DofKind; -use modelcraft_fem::dense::Matrix; -use modelcraft_fem::truss::{Truss, uniaxial}; -use modelcraft_model::{ElementKind, Resolved, ResolvedDirection}; +use fieldcraft_fem::DofKind; +use fieldcraft_fem::dense::Matrix; +use fieldcraft_fem::truss::{Truss, uniaxial}; +use fieldcraft_model::{ElementKind, Resolved, ResolvedDirection}; use crate::system::NODE_DOFS; use crate::{Error, Result}; @@ -92,7 +92,7 @@ impl Rod { /// The element's degrees of freedom, node by node. pub fn dofs(&self) -> impl Iterator + '_ { - self.nodes.iter().flat_map(|n| modelcraft_fem::truss::DOFS.node.iter().filter_map(DofKind::direction).map(|d| dof(*n, d))) + self.nodes.iter().flat_map(|n| fieldcraft_fem::truss::DOFS.node.iter().filter_map(DofKind::direction).map(|d| dof(*n, d))) } /// Add its internal force into `force` and return the element's mean stress and strain diff --git a/crates/explicit/src/elements.rs b/crates/explicit/src/elements.rs index 60e9185..3bbd6b4 100644 --- a/crates/explicit/src/elements.rs +++ b/crates/explicit/src/elements.rs @@ -14,9 +14,9 @@ //! strain and the stiffness-form hourglass forces) is rotated with the increment's rotation — //! the Cayley transform of the spin increment (Jaumann rate, Hughes & Winget 1980) or the //! increment of the polar rotation (Green–Naghdi rate, D. P. Flanagan, L. M. Taylor, CMAME -//! 62(3):305–320, 1987), see [`modelcraft_fem::rotation`] — then the constitutive increment is +//! 62(3):305–320, 1987), see [`fieldcraft_fem::rotation`] — then the constitutive increment is //! added: hypoelastic, or J2 radial return with Johnson–Cook damage -//! ([`modelcraft_fem::plasticity`]). Orthotropic elasticity is updated in the polar frame (its +//! ([`fieldcraft_fem::plasticity`]). Orthotropic elasticity is updated in the polar frame (its //! axes turn with the material) whichever rate the step names. //! //! **One-point hexahedron** (D. P. Flanagan, T. Belytschko, IJNME 17(5):679–706, 1981): the @@ -44,7 +44,7 @@ //! Δt = L / (Q + √(Q² + c²)), Q = C_l c + C_q L |ε̇ᵥ| (Wilkins 1980). //! //! **Critical increment.** Each element's stable length L = 2c/ω_max comes from the bound on its -//! highest frequency in its current shape ([`modelcraft_fem::uniform`], with the element's lumped +//! highest frequency in its current shape ([`fieldcraft_fem::uniform`], with the element's lumped //! masses, plus the hourglass stiffness when it is used), so without bulk viscosity its critical //! increment is 2/ω_max (Belytschko et al. 2014, §6.3). The elastic moduli bound the tangent //! moduli of plastic flow, so the bound holds for the inelastic materials too. @@ -54,11 +54,11 @@ //! reaches its material's erosion plastic strain is removed: it carries no stress from then on, //! its nodes keep their mass and momentum, and its internal energy at that moment is reported. -use modelcraft_fem::material::stress; -use modelcraft_fem::plasticity::{Increment, PointState}; -use modelcraft_fem::rotation::{self, IDENTITY, Mat3, Polar}; -use modelcraft_fem::uniform::{Hex8, Tet4, UniformStrain}; -use modelcraft_model::{BulkViscosity, HourglassKind, StressRate}; +use fieldcraft_fem::material::stress; +use fieldcraft_fem::plasticity::{Increment, PointState}; +use fieldcraft_fem::rotation::{self, IDENTITY, Mat3, Polar}; +use fieldcraft_fem::uniform::{Hex8, Tet4, UniformStrain}; +use fieldcraft_model::{BulkViscosity, HourglassKind, StressRate}; use crate::system::{Material, NODE_DOFS, System}; use crate::{Error, Result}; @@ -330,7 +330,7 @@ fn current(mat: &Material, rest: f64, volume: f64) -> (f64, f64) { } /// The bound ω² ≤ k λ_max(Σ b bᵀ) / (V m_min) on an element's highest frequency -/// ([`modelcraft_fem::uniform`]), with the lumped masses of the undeformed element and +/// ([`fieldcraft_fem::uniform`]), with the lumped masses of the undeformed element and /// `gradient_norm` = λ_max(Σ b bᵀ). pub fn frequency_bound(k: &UniformStrain, gradient_norm: f64, modulus: f64, mass: &[f64; N]) -> f64 { let lightest = mass.iter().copied().fold(f64::INFINITY, f64::min); @@ -353,7 +353,7 @@ pub fn hex_scales( mass: &[f64; 8], mat: &Material, density: f64, - hourglass: modelcraft_model::Hourglass, + hourglass: fieldcraft_model::Hourglass, ) -> HexScales { let norm = k.gradient_norm(); // h: the edge of the cube with the element's volume and gradient norm. @@ -439,7 +439,7 @@ fn run( ) -> Result<()> { let mut all = pieces::(states, count, &index)?; for round in all.chunks_mut(ROUND) { - modelcraft_parallel::for_each_mut(round, |_, piece| { + fieldcraft_parallel::for_each_mut(round, |_, piece| { let Piece { items, base, states, out } = piece; *out = items .clone() @@ -476,7 +476,7 @@ fn hexahedron(sys: &System, h: &crate::system::Hex, m: &Motion, es: &mut Element if es.failed.is_some() { return Ok(Outcome::Skipped); } - let hourglass = modelcraft_model::Hourglass { kind: sys.hourglass, coefficient: sys.hourglass_coefficient }; + let hourglass = fieldcraft_model::Hourglass { kind: sys.hourglass, coefficient: sys.hourglass_coefficient }; let e = Nodal::gather(sys, &h.nodes, m); let x_now = e.positions(0.0); let (mid, now) = if sys.nonlinear { @@ -607,7 +607,7 @@ pub fn internal_forces(sys: &System, m: &Motion, state: &mut State, ws: &mut Wor // Pass 1: each tetrahedron's increment, and each node's mean volumetric increment. let increments = { let st: &State = state; - modelcraft_parallel::map_grained(tets.len(), BLOCK, |k| match tets.get(k) { + fieldcraft_parallel::map_grained(tets.len(), BLOCK, |k| match tets.get(k) { Some(t) => tetrahedron_increment(sys, t, m, st), None => Ok(None), }) diff --git a/crates/explicit/src/integrate.rs b/crates/explicit/src/integrate.rs index 8f9a9b0..b162626 100644 --- a/crates/explicit/src/integrate.rs +++ b/crates/explicit/src/integrate.rs @@ -20,7 +20,7 @@ //! The run starts from u = 0 and the initial velocities with the half increment //! v^{½} = v^0 + ½Δt^{½} a^0. -use modelcraft_model::{ExplicitDynamic, Model, OutputVariable, Procedure, Step}; +use fieldcraft_model::{ExplicitDynamic, Model, OutputVariable, Procedure, Step}; use crate::elements::{Motion, State, Workspace, internal_forces}; use crate::result::{ControllingElement, Energies, ErodedElement, ExplicitResult, Frame, History}; diff --git a/crates/explicit/src/lib.rs b/crates/explicit/src/lib.rs index 96131d5..c9aa5da 100644 --- a/crates/explicit/src/lib.rs +++ b/crates/explicit/src/lib.rs @@ -1,4 +1,4 @@ -//! ModelCraft's explicit dynamics solver: transient response of a model's explicit dynamic step +//! FieldCraft's explicit dynamics solver: transient response of a model's explicit dynamic step //! by central-difference time integration. //! //! The method is the explicit central-difference scheme of T. Belytschko, W. K. Liu, B. Moran, @@ -24,7 +24,7 @@ //! docs/solvers/literature.md, Parts F and J). Without it the gradients stay those of the //! undeformed mesh (small displacements). //! -//! Elements are updated on threads ([`modelcraft_parallel`]), each owning its state, and their +//! Elements are updated on threads ([`fieldcraft_parallel`]), each owning its state, and their //! nodal forces are added in label order as a sequential loop adds them, with no parallel //! reductions, so the same model gives the same numbers bit for bit on any number of threads; //! sizes, increments and output are capped by [`Limits`]. @@ -46,14 +46,14 @@ pub use result::{ControllingElement, Energies, ErodedElement, ExplicitResult, Fr #[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum Error { #[error(transparent)] - Model(#[from] modelcraft_model::Error), + Model(#[from] fieldcraft_model::Error), #[error("invalid input: {0}")] Invalid(String), #[error("not supported yet: {0}")] NotSupported(String), /// An element kernel failed (inverted geometry, …). #[error("element {label}: {source}")] - Element { label: u32, source: modelcraft_fem::Error }, + Element { label: u32, source: fieldcraft_fem::Error }, #[error("limit reached: {0}")] Limit(String), /// The solution stopped being finite. @@ -68,7 +68,7 @@ pub type Result = std::result::Result; impl Error { /// A kernel error, naming the element. - pub fn element(label: u32, source: modelcraft_fem::Error) -> Error { + pub fn element(label: u32, source: fieldcraft_fem::Error) -> Error { Error::Element { label, source } } } @@ -86,7 +86,7 @@ pub struct Limits { impl Limits { pub const MAX_DOFS: usize = 6_000_000; - pub const MAX_INCREMENTS: u64 = modelcraft_model::steps::MAX_INCREMENTS; + pub const MAX_INCREMENTS: u64 = fieldcraft_model::steps::MAX_INCREMENTS; pub const MAX_OUTPUT_VALUES: usize = 50_000_000; } diff --git a/crates/explicit/src/system.rs b/crates/explicit/src/system.rs index cac6fd7..2cdc773 100644 --- a/crates/explicit/src/system.rs +++ b/crates/explicit/src/system.rs @@ -3,11 +3,11 @@ //! ([`crate::discrete`]), prescribed velocities, rigid walls, initial velocities and the load //! vector of an explicit dynamic step. -use modelcraft_fem::material::Elasticity; -use modelcraft_fem::plasticity::J2; -use modelcraft_fem::uniform::{Hex8, Tet4, modulus_bound}; -use modelcraft_fem::{DofKind, DofLayout, DofSet, face}; -use modelcraft_model::{ +use fieldcraft_fem::material::Elasticity; +use fieldcraft_fem::plasticity::J2; +use fieldcraft_fem::uniform::{Hex8, Tet4, modulus_bound}; +use fieldcraft_fem::{DofKind, DofLayout, DofSet, face}; +use fieldcraft_model::{ Elastic, ElementKind, ExplicitDynamic, HourglassKind, Integration, Load, Model, Resolved, SectionKind, Step, StressRate, Target, }; @@ -30,7 +30,7 @@ pub struct Material { /// Dilatational wave speed √(M/ρ), with M the largest normal stiffness (λ + 2μ when /// isotropic). pub wave_speed: f64, - /// The bound of [`modelcraft_fem::uniform::modulus_bound`]. + /// The bound of [`fieldcraft_fem::uniform::modulus_bound`]. pub modulus: f64, /// The largest normal stiffness M (λ + 2μ when isotropic): c = √(M/ρ). pub stiffest: f64, @@ -143,7 +143,7 @@ fn material(model: &Model, name: &str) -> Result { .density .filter(|d| *d > 0.0 && d.is_finite()) .ok_or_else(|| invalid(format!("material `{name}` has no density, and explicit dynamics needs one")))?; - let d = modelcraft_fem::elasticity(elastic).map_err(|e| invalid(format!("material `{name}`: {e}")))?; + let d = fieldcraft_fem::elasticity(elastic).map_err(|e| invalid(format!("material `{name}`: {e}")))?; let stiffest = d[0][0].max(d[1][1]).max(d[2][2]); // The model keeps plasticity, damage and erosion to isotropic materials. let j2 = match *elastic { @@ -171,7 +171,7 @@ fn material(model: &Model, name: &str) -> Result { } /// The section's elements of a kind the explicit solver has, or why not. -fn check_formulation(kind: ElementKind, section_name: &str, f: &modelcraft_model::Formulation) -> Result<()> { +fn check_formulation(kind: ElementKind, section_name: &str, f: &fieldcraft_model::Formulation) -> Result<()> { let refuse = |why: &str| Err(Error::NotSupported(format!("section `{section_name}`: {why}"))); if f.hybrid { return refuse("hybrid elements in explicit dynamics"); @@ -327,7 +327,7 @@ impl System { ))); } let young = - modelcraft_fem::truss::young_modulus(source.elastic.as_ref().ok_or_else(|| Error::Internal("truss material".into()))?) + fieldcraft_fem::truss::young_modulus(source.elastic.as_ref().ok_or_else(|| Error::Internal("truss material".into()))?) .map_err(|e| invalid(format!("material `{}` of element {label}: {e}", mat.name)))?; let x: Vec<[f64; 3]> = idx.iter().map(|i| sys.xyz.get(*i).copied().unwrap_or_default()).collect(); let rod = Rod::new(label, index, kind, idx.clone(), &x, mi, crate::discrete::RodSection { young, density: rho, area })?; @@ -518,7 +518,7 @@ impl System { self.each_element(chosen, |nodes, weights, rho| { for (node, w) in nodes.iter().zip(weights) { let x = xyz.get(*node).copied().unwrap_or_default(); - let a = modelcraft_model::centrifugal_acceleration(*angular_velocity, *point, *axis, x); + let a = fieldcraft_model::centrifugal_acceleration(*angular_velocity, *point, *axis, x); add_at(&mut f, *node, &a.map(|c| rho * w * c)); } }); diff --git a/crates/explicit/tests/large_deformation.rs b/crates/explicit/tests/large_deformation.rs index caf8afc..bde845d 100644 --- a/crates/explicit/tests/large_deformation.rs +++ b/crates/explicit/tests/large_deformation.rs @@ -13,10 +13,10 @@ //! //! Run with `--nocapture` to see the errors. -use modelcraft_explicit::elements::{Motion, State, Workspace, internal_forces}; -use modelcraft_explicit::system::System; -use modelcraft_explicit::{ExplicitResult, Failure, Limits, solve_explicit}; -use modelcraft_model::*; +use fieldcraft_explicit::elements::{Motion, State, Workspace, internal_forces}; +use fieldcraft_explicit::system::System; +use fieldcraft_explicit::{ExplicitResult, Failure, Limits, solve_explicit}; +use fieldcraft_model::*; /// A block [0, size] of `n` cells: one hex8 per cell, or six tet4 (Kuhn's split). fn block(n: [usize; 3], size: [f64; 3], kind: ElementKind) -> Model { @@ -179,7 +179,7 @@ impl Driver { self.try_go(to, dt).unwrap() } - fn try_go(&mut self, to: impl Fn([f64; 3]) -> [f64; 3], dt: f64) -> modelcraft_explicit::Result<[f64; 6]> { + fn try_go(&mut self, to: impl Fn([f64; 3]) -> [f64; 3], dt: f64) -> fieldcraft_explicit::Result<[f64; 6]> { let mut du = vec![0.0; self.u.len()]; for (i, x0) in self.sys.xyz.iter().enumerate() { let x = to(*x0); diff --git a/crates/explicit/tests/one_d.rs b/crates/explicit/tests/one_d.rs index 01ae8b8..084f6e7 100644 --- a/crates/explicit/tests/one_d.rs +++ b/crates/explicit/tests/one_d.rs @@ -9,9 +9,9 @@ use std::f64::consts::PI; -use modelcraft_explicit::{ExplicitResult, Limits, solve_explicit}; -use modelcraft_fem::truss::axial; -use modelcraft_model::*; +use fieldcraft_explicit::{ExplicitResult, Limits, solve_explicit}; +use fieldcraft_fem::truss::axial; +use fieldcraft_model::*; fn model(step: ExplicitDynamic) -> Model { let mut m = Model::default(); diff --git a/crates/explicit/tests/thermal.rs b/crates/explicit/tests/thermal.rs index cc3e4f8..684aa76 100644 --- a/crates/explicit/tests/thermal.rs +++ b/crates/explicit/tests/thermal.rs @@ -2,8 +2,8 @@ //! a free bar by α ΔT L without stress; a bar held at both ends carries σ = −E α ΔT, and with //! perfectly plastic J2 the stress stops at the yield stress. -use modelcraft_explicit::{Limits, solve_explicit}; -use modelcraft_model::*; +use fieldcraft_explicit::{Limits, solve_explicit}; +use fieldcraft_model::*; const YOUNG: f64 = 1.0e4; const ALPHA: f64 = 1.0e-4; @@ -115,7 +115,7 @@ fn a_clamped_bar_carries_minus_e_alpha_dt_up_to_yield() { let r = solve_explicit(&bar(kind, true, Some(plastic)), None, &Limits::default()).unwrap(); let last = r.frames.last().unwrap(); for (s, ep) in last.stress.iter().zip(&last.plastic_strain) { - let mises = modelcraft_fem::von_mises(s); + let mises = fieldcraft_fem::von_mises(s); assert!((mises - 0.5 * sigma.abs()).abs() < 0.02 * sigma.abs(), "{kind:?}: {s:?}"); assert!(*ep > 0.0, "{kind:?}: plastic strain {ep}"); } diff --git a/crates/explicit/tests/verification.rs b/crates/explicit/tests/verification.rs index a90c394..d058cde 100644 --- a/crates/explicit/tests/verification.rs +++ b/crates/explicit/tests/verification.rs @@ -5,8 +5,8 @@ //! The bar has Poisson's ratio 0, so a uniaxial stress wave travels at the rod speed c = √(E/ρ) //! without lateral inertia effects (d'Alembert's solution of the one-dimensional wave equation). -use modelcraft_explicit::{ExplicitResult, Limits, solve_explicit}; -use modelcraft_model::*; +use fieldcraft_explicit::{ExplicitResult, Limits, solve_explicit}; +use fieldcraft_model::*; const YOUNG: f64 = 1.0e4; const DENSITY: f64 = 1.0; diff --git a/crates/fem/Cargo.toml b/crates/fem/Cargo.toml index 61a5d0f..16c0ae9 100644 --- a/crates/fem/Cargo.toml +++ b/crates/fem/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-fem" -description = "ModelCraft's finite element kernels: shape functions, quadrature, strain-displacement matrices, element stiffness and mass, elastic constitutive matrices" +name = "fieldcraft-fem" +description = "FieldCraft's finite element kernels: shape functions, quadrature, strain-displacement matrices, element stiffness and mass, elastic constitutive matrices" version.workspace = true edition.workspace = true license.workspace = true @@ -9,5 +9,5 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } +fieldcraft-model = { workspace = true } thiserror = { workspace = true } diff --git a/crates/fem/src/basis.rs b/crates/fem/src/basis.rs index ff1d6eb..f9943f4 100644 --- a/crates/fem/src/basis.rs +++ b/crates/fem/src/basis.rs @@ -28,7 +28,7 @@ //! //! Node orders are corners first, then mid-side nodes in the model's edge order (the model is the //! one place they are written down), then face and body nodes, the Abaqus/VTK convention of -//! [`modelcraft_model::ElementKind`]. The functions are the textbook ones (O. C. Zienkiewicz, +//! [`fieldcraft_model::ElementKind`]. The functions are the textbook ones (O. C. Zienkiewicz, //! R. L. Taylor, J. Z. Zhu, *The Finite Element Method: Its Basis and Fundamentals*, 7th ed., //! 2013, ch. 6; T. J. R. Hughes, *The Finite Element Method*, Dover 2000, ch. 3). //! @@ -49,7 +49,7 @@ use std::sync::LazyLock; -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use crate::dense::positive_inverse; use crate::jet::Jet; diff --git a/crates/fem/src/beam.rs b/crates/fem/src/beam.rs index a4f40b0..a68bdf0 100644 --- a/crates/fem/src/beam.rs +++ b/crates/fem/src/beam.rs @@ -39,7 +39,7 @@ //! webs or the two flanges of a box) (W. D. Pilkey, *Analysis and Design of Elastic Beams*, //! Wiley 2002, ch. 6). -use modelcraft_model::{BeamProfile, Elastic}; +use fieldcraft_model::{BeamProfile, Elastic}; use crate::dense::Matrix; use crate::dof::{DofSet, ElementDofs}; @@ -211,7 +211,7 @@ fn line_axis(xyz: &[[f64; 3]]) -> Result<(Vec3, f64)> { let [a, b] = xyz else { return Err(Error::Invalid(format!("a two-node line element has 2 nodes, got {}", xyz.len()))); }; - modelcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; + fieldcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; let d = sub(*b, *a); let length = norm(d); let ex = unit(d).ok_or_else(|| Error::Degenerate("a line element has zero length".into()))?; diff --git a/crates/fem/src/element.rs b/crates/fem/src/element.rs index 6c1f1a7..3a6e2f8 100644 --- a/crates/fem/src/element.rs +++ b/crates/fem/src/element.rs @@ -32,7 +32,7 @@ //! discontinuous finite element functions using a least squares method*, IJNME 8(3):461–480, //! 1974); mid-side nodes take the mean of their edge's corners. -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use crate::basis::Basis; use crate::dense::{Cholesky, Matrix, inverse3, positive_inverse}; @@ -163,10 +163,10 @@ impl Formulation { } /// The formulation a model section asks for, if this element type has it. - pub fn from_section(kind: ElementKind, f: &modelcraft_model::Formulation) -> Result { + pub fn from_section(kind: ElementKind, f: &fieldcraft_model::Formulation) -> Result { let ty = element_type(kind)?; if f.hybrid { - if f.incompatible_modes || f.integration == modelcraft_model::Integration::Reduced { + if f.incompatible_modes || f.integration == fieldcraft_model::Integration::Reduced { return Err(Error::NotSupported("hybrid elements with incompatible modes or reduced integration".into())); } return crate::hybrid::pressures(kind).map(|pressures| Formulation::Hybrid { pressures }).ok_or_else(|| { @@ -184,9 +184,9 @@ impl Formulation { }; } match f.integration { - modelcraft_model::Integration::Full => Ok(Formulation::Full), - modelcraft_model::Integration::Reduced if ty.reduced.is_some() => Ok(Formulation::Reduced), - modelcraft_model::Integration::Reduced => Err(Error::NotSupported(format!( + fieldcraft_model::Integration::Full => Ok(Formulation::Full), + fieldcraft_model::Integration::Reduced if ty.reduced.is_some() => Ok(Formulation::Reduced), + fieldcraft_model::Integration::Reduced => Err(Error::NotSupported(format!( "reduced integration of {} elements (hex8 needs hourglass control; use full integration or incompatible modes)", kind.name() ))), @@ -254,7 +254,7 @@ impl<'a> Solid<'a> { if xyz.len() != ty.nodes { return Err(Error::Invalid(format!("a {} element has {} nodes, got {}", kind.name(), ty.nodes, xyz.len()))); } - modelcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; + fieldcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; let ok = match formulation { Formulation::Full => true, Formulation::Reduced => ty.reduced.is_some(), diff --git a/crates/fem/src/face.rs b/crates/fem/src/face.rs index a8f1b5b..7bf6684 100644 --- a/crates/fem/src/face.rs +++ b/crates/fem/src/face.rs @@ -2,7 +2,7 @@ //! //! A pressure p on a face gives node i the force fᵢ = −p ∫ Nᵢ n dA, where n dA = //! (∂x/∂ξ × ∂x/∂η) dξ dη on the face's own, possibly curved, geometry; faces are numbered so this -//! normal points out of the element ([`modelcraft_model::ElementKind::faces`]), and a positive +//! normal points out of the element ([`fieldcraft_model::ElementKind::faces`]), and a positive //! pressure pushes in. This is the consistent load vector of the virtual work of the surface load //! (Zienkiewicz, Taylor & Zhu 2013, §6.6), integrated with the model's face rules (exact for a //! uniform pressure on a flat face). @@ -10,10 +10,10 @@ use crate::{Error, Result}; /// ∫ Nᵢ n dA for every node of a face (node coordinates in the face's node order): the model's -/// face integration ([`modelcraft_model::nodal::face_normal_weights`]), so pressures and the +/// face integration ([`fieldcraft_model::nodal::face_normal_weights`]), so pressures and the /// tractions the model distributes use the same rule. pub fn normal_weights(xyz: &[[f64; 3]]) -> Result> { - modelcraft_model::nodal::face_normal_weights(xyz).map_err(|e| Error::Invalid(e.to_string())) + fieldcraft_model::nodal::face_normal_weights(xyz).map_err(|e| Error::Invalid(e.to_string())) } /// Consistent nodal forces of a uniform pressure (positive pushes into the element). diff --git a/crates/fem/src/heat.rs b/crates/fem/src/heat.rs index 53b17f2..b7f886c 100644 --- a/crates/fem/src/heat.rs +++ b/crates/fem/src/heat.rs @@ -14,7 +14,7 @@ //! Faces are integrated on their own (possibly curved) geometry with the model's face rule //! (the one pressures use), exact for N Nᵀ on flat faces. -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use crate::basis::Basis; use crate::dense::Matrix; @@ -47,7 +47,7 @@ impl<'a> Conduction<'a> { if xyz.len() != ty.nodes { return Err(Error::Invalid(format!("a {} element has {} nodes, got {}", kind.name(), ty.nodes, xyz.len()))); } - modelcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; + fieldcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; Ok(Conduction { ty, basis: Basis::of(kind), xyz }) } @@ -119,11 +119,11 @@ impl<'a> Conduction<'a> { } /// Visit the points of a face (node coordinates in the face's node order): shape values and -/// the area weight, with the model's face rule ([`modelcraft_model::nodal::integrate_face`], the +/// the area weight, with the model's face rule ([`fieldcraft_model::nodal::integrate_face`], the /// one pressures and tractions use). fn each_face_point(xyz: &[[f64; 3]], mut visit: impl FnMut(&[f64], f64)) -> Result<()> { let mut collapsed = false; - modelcraft_model::nodal::integrate_face(xyz, |n, nda| { + fieldcraft_model::nodal::integrate_face(xyz, |n, nda| { let area = (nda[0] * nda[0] + nda[1] * nda[1] + nda[2] * nda[2]).sqrt(); collapsed |= !(area > 0.0 && area.is_finite()); visit(n, area); diff --git a/crates/fem/src/hybrid.rs b/crates/fem/src/hybrid.rs index 4dc3a98..1814d2c 100644 --- a/crates/fem/src/hybrid.rs +++ b/crates/fem/src/hybrid.rs @@ -36,7 +36,7 @@ //! symmetric indefinite saddle point that the Cholesky factorization cannot solve; it waits for //! an indefinite (LDLᵀ) factorization. -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use crate::dense::{Cholesky, Matrix}; use crate::element::{Formulation, Solid, ThermalStrain, accumulate_symmetric, b_block}; diff --git a/crates/fem/src/lib.rs b/crates/fem/src/lib.rs index ad41bf0..aaf88d8 100644 --- a/crates/fem/src/lib.rs +++ b/crates/fem/src/lib.rs @@ -1,7 +1,7 @@ -//! Finite element kernels shared by ModelCraft's structural solvers. +//! Finite element kernels shared by FieldCraft's structural solvers. //! //! Pure functions over element geometry and material constants, with no solver loop and no -//! global state, so the implicit solver (`modelcraft-solver`) and the explicit solver that follows +//! global state, so the implicit solver (`fieldcraft-solver`) and the explicit solver that follows //! it use the same element technology: //! - [`dof`]: degree-of-freedom kinds, what elements declare, and the global numbering //! ([`DofLayout`]) every solver assembles on; diff --git a/crates/fem/src/material.rs b/crates/fem/src/material.rs index b31c161..f518efd 100644 --- a/crates/fem/src/material.rs +++ b/crates/fem/src/material.rs @@ -10,7 +10,7 @@ //! S₁₁ = 1/E₁, S₁₂ = −ν₁₂/E₁, S₁₃ = −ν₁₃/E₁, S₂₃ = −ν₂₃/E₂ (ν_ji/E_j = ν_ij/E_i), and //! 1/G on the shear diagonal. -use modelcraft_model::Elastic; +use fieldcraft_model::Elastic; use crate::dense::inverse3; use crate::{Error, Result}; diff --git a/crates/fem/src/plane.rs b/crates/fem/src/plane.rs index bd19975..4a5cfe2 100644 --- a/crates/fem/src/plane.rs +++ b/crates/fem/src/plane.rs @@ -39,7 +39,7 @@ //! inside the element (r > 0) for every element with some node off the axis; the solver holds //! the radial displacement of nodes on the axis at zero, as symmetry requires. -use modelcraft_model::{ElementKind, Extent, SectionKind}; +use fieldcraft_model::{ElementKind, Extent, SectionKind}; use crate::basis::Basis; use crate::dense::{Cholesky, Matrix}; @@ -166,7 +166,7 @@ pub fn plane_type(kind: ElementKind) -> Result<&'static ElementType> { } /// The formulation a 2D section asks for, if this element type has it. -pub fn formulation(kind: ElementKind, f: &modelcraft_model::Formulation) -> Result { +pub fn formulation(kind: ElementKind, f: &fieldcraft_model::Formulation) -> Result { let ty = plane_type(kind)?; if f.hybrid { return Err(Error::NotSupported("hybrid (mixed displacement–pressure) 2D elements".into())); @@ -179,9 +179,9 @@ pub fn formulation(kind: ElementKind, f: &modelcraft_model::Formulation) -> Resu }; } match f.integration { - modelcraft_model::Integration::Full => Ok(Formulation::Full), - modelcraft_model::Integration::Reduced if ty.reduced.is_some() => Ok(Formulation::Reduced), - modelcraft_model::Integration::Reduced => Err(Error::NotSupported(format!( + fieldcraft_model::Integration::Full => Ok(Formulation::Full), + fieldcraft_model::Integration::Reduced if ty.reduced.is_some() => Ok(Formulation::Reduced), + fieldcraft_model::Integration::Reduced => Err(Error::NotSupported(format!( "reduced integration of 2D {} elements (quad4 needs hourglass control; use full integration or incompatible modes)", kind.name() ))), @@ -254,7 +254,7 @@ impl<'a> Plane<'a> { if xyz.len() != ty.nodes { return Err(Error::Invalid(format!("a {} element has {} nodes, got {}", kind.name(), ty.nodes, xyz.len()))); } - modelcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; + fieldcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; if xyz.iter().any(|p| p[2] != 0.0) { return Err(Error::Invalid(format!("a 2D {} element must lie in the XY plane (z = 0)", kind.name()))); } @@ -760,7 +760,7 @@ mod tests { let f = e.body_force_field(|x| [x[0], 0.0, 0.0]).unwrap(); assert!((f.iter().map(|v| v[0]).sum::() - 2.0 * pi * 7.0 / 3.0).abs() < 1e-12); // Pressure on the outer edge (x = 2) of the ring pushes inward with p · 2π · 2 · 1. - let fp = modelcraft_model::nodal::edge_pressure(&[sq[1], sq[2]], Theory::Axisymmetric.extent(), 1.0).unwrap(); + let fp = fieldcraft_model::nodal::edge_pressure(&[sq[1], sq[2]], Theory::Axisymmetric.extent(), 1.0).unwrap(); assert!((fp.iter().map(|v| v[0]).sum::() + 4.0 * pi).abs() < 1e-12, "{fp:?}"); } @@ -783,6 +783,6 @@ mod tests { let on_axis = sq.map(|p| [p[0] - 1.0, p[1], 0.0]); let e = Plane::new(ElementKind::Quad4, &on_axis, Formulation::IncompatibleModes, Theory::Axisymmetric).unwrap(); assert!(e.stiffness(&d).is_ok()); - assert!(formulation(ElementKind::Quad4, &modelcraft_model::Formulation { hybrid: true, ..Default::default() }).is_err()); + assert!(formulation(ElementKind::Quad4, &fieldcraft_model::Formulation { hybrid: true, ..Default::default() }).is_err()); } } diff --git a/crates/fem/src/plasticity.rs b/crates/fem/src/plasticity.rs index d667c99..8a36692 100644 --- a/crates/fem/src/plasticity.rs +++ b/crates/fem/src/plasticity.rs @@ -36,7 +36,7 @@ //! Stress 6-vectors hold tensor components (xx, yy, zz, xy, xz, yz), strain increments //! engineering shears. -use modelcraft_model::{Damage, Elastic, Hardening, Plastic}; +use fieldcraft_model::{Damage, Elastic, Hardening, Plastic}; use crate::material::von_mises; use crate::{Error, Result}; @@ -328,7 +328,7 @@ impl J2 { #[cfg(test)] mod tests { use super::*; - use modelcraft_model::HardeningPoint; + use fieldcraft_model::HardeningPoint; const E: f64 = 200_000.0; const NU: f64 = 0.3; diff --git a/crates/fem/src/shape.rs b/crates/fem/src/shape.rs index 76f2de1..dfddb10 100644 --- a/crates/fem/src/shape.rs +++ b/crates/fem/src/shape.rs @@ -1,6 +1,6 @@ //! Shape functions and their derivatives in natural coordinates. //! -//! Node order is the model's (Abaqus/VTK, [`modelcraft_model::ElementKind`]): +//! Node order is the model's (Abaqus/VTK, [`fieldcraft_model::ElementKind`]): //! - tetrahedra on the reference element with corners (0,0,0), (1,0,0), (0,1,0), (0,0,1) and the //! volume coordinates L₁ = 1 − ξ − η − ζ, L₂ = ξ, L₃ = η, L₄ = ζ; tet10 mid-side nodes on edges //! 1-2, 2-3, 3-1, 1-4, 2-4, 3-4 with N = 4 Lₐ L_b, corners N = L(2L − 1); @@ -9,7 +9,7 @@ //! N = ⅛(1 + ξξᵢ)(1 + ηηᵢ)(1 + ζζᵢ)(ξξᵢ + ηηᵢ + ζζᵢ − 2), a mid-side node with ξᵢ = 0 //! N = ¼(1 − ξ²)(1 + ηηᵢ)(1 + ζζᵢ), and likewise), mid-side nodes 9–12 on the bottom edges, //! 13–16 on the top edges, 17–20 on the vertical edges (mid-side positions come from -//! [`modelcraft_model::ElementKind::edges`], the one place the node order is written down); +//! [`fieldcraft_model::ElementKind::edges`], the one place the node order is written down); //! - the six-node wedge (pentahedron) as the product of the linear triangle {ξ, η ≥ 0, //! ξ + η ≤ 1} (area coordinates L₁ = 1 − ξ − η, L₂ = ξ, L₃ = η) and the linear interval //! ζ ∈ [−1, 1]: nodes 1–3 at ζ = −1 counter-clockwise seen from above, nodes 4–6 above them, @@ -19,7 +19,7 @@ //! Element Method: Its Basis and Fundamentals*, 7th ed., Butterworth-Heinemann 2013, ch. 6; //! T. J. R. Hughes, *The Finite Element Method*, Dover 2000, ch. 3). -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; /// Most nodes an element family has (hex27, [`crate::basis::Basis::Hex27`]). pub const MAX_NODES: usize = 27; diff --git a/crates/fem/src/shell.rs b/crates/fem/src/shell.rs index 9aa2b28..ba2f68d 100644 --- a/crates/fem/src/shell.rs +++ b/crates/fem/src/shell.rs @@ -57,7 +57,7 @@ //! the nodes (triangle, constant membrane and bending fields). Transverse shear stresses are //! reported at the mid-surface as the peak of the parabolic distribution, 3/2 Q/t. -use modelcraft_model::{Elastic, ElementKind}; +use fieldcraft_model::{Elastic, ElementKind}; use crate::dense::Matrix; use crate::dof::{DofSet, ElementDofs}; @@ -213,7 +213,7 @@ impl Shell { if xyz.len() != nodes { return Err(Error::Invalid(format!("a {} element has {nodes} nodes, got {}", kind.name(), xyz.len()))); } - modelcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; + fieldcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; let degenerate = || Error::Degenerate(format!("a {} shell element is degenerate (zero area, inverted or not convex)", kind.name())); let p = |i: usize| xyz.get(i).copied().unwrap_or_default(); let (normal, along) = if nodes == 3 { diff --git a/crates/fem/src/tests.rs b/crates/fem/src/tests.rs index 75d15f7..dd13b1d 100644 --- a/crates/fem/src/tests.rs +++ b/crates/fem/src/tests.rs @@ -1,7 +1,7 @@ //! Element-level checks: rigid-body modes, constant-strain reproduction (the single-element form //! of the patch test), mass and load totals, extrapolation. -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use crate::dense::{Cholesky, Matrix}; use crate::element::{Formulation, Solid}; @@ -203,7 +203,7 @@ fn bad_input_is_an_error() { #[test] fn section_formulations() { - use modelcraft_model::{Formulation as F, Integration}; + use fieldcraft_model::{Formulation as F, Integration}; let full = F::default(); let reduced = F { integration: Integration::Reduced, ..F::default() }; let im = F { incompatible_modes: true, ..F::default() }; diff --git a/crates/fem/src/truss.rs b/crates/fem/src/truss.rs index 647d4b6..580baaa 100644 --- a/crates/fem/src/truss.rs +++ b/crates/fem/src/truss.rs @@ -24,7 +24,7 @@ //! stiffness and mass of a straight member, and the usual full integration of a curved one. //! Material: the Young's modulus of an isotropic material; the truss knows no other constant. -use modelcraft_model::{Elastic, ElementKind}; +use fieldcraft_model::{Elastic, ElementKind}; use crate::basis::Basis; use crate::dense::Matrix; diff --git a/crates/fem/src/uniform.rs b/crates/fem/src/uniform.rs index 8cbbfa4..b1cdb79 100644 --- a/crates/fem/src/uniform.rs +++ b/crates/fem/src/uniform.rs @@ -28,7 +28,7 @@ //! element frequency satisfies ω² ≤ k λ_max(Σ b bᵀ) / (V min m_I). The bound is exact for a //! cube (its dilatation mode) and conservative for distorted elements. -use modelcraft_model::Elastic; +use fieldcraft_model::Elastic; use crate::dense::inverse3; use crate::material::Elasticity; @@ -58,7 +58,7 @@ fn degenerate(what: &str) -> Error { impl UniformStrain<8> { /// The uniform-strain hexahedron of corners `xyz` (the model's hex8 node order). pub fn hex8(xyz: &[[f64; 3]; 8]) -> Result { - modelcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; + fieldcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; let mut b = [[0.0; 3]; 8]; let mut nodal_volume = [0.0; 8]; let mut volume = 0.0; @@ -171,7 +171,7 @@ impl UniformStrain<8> { impl UniformStrain<4> { /// The linear tetrahedron of corners `xyz` (the model's tet4 node order). pub fn tet4(xyz: &[[f64; 3]; 4]) -> Result { - modelcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; + fieldcraft_model::check_coordinates(xyz.as_flattened()).map_err(|e| Error::Invalid(e.to_string()))?; let [x0, x1, x2, x3] = *xyz; let e = |x: [f64; 3]| [x[0] - x0[0], x[1] - x0[1], x[2] - x0[2]]; // J[a][c] = ∂x_c/∂ξ_a: the edges from corner 1. @@ -330,7 +330,7 @@ mod tests { } assert!((h.nodal_volume.iter().sum::() - h.volume).abs() < 1e-14); // The volume of the distorted hexahedron agrees with the fem element's. - let solid = crate::Solid::new(modelcraft_model::ElementKind::Hex8, &hex, crate::Formulation::Full).unwrap(); + let solid = crate::Solid::new(fieldcraft_model::ElementKind::Hex8, &hex, crate::Formulation::Full).unwrap(); assert!((solid.volume().unwrap() - h.volume).abs() < 1e-14); let mut inverted = hex; inverted.swap(0, 1); diff --git a/crates/geometry/Cargo.toml b/crates/geometry/Cargo.toml index 92d363b..897cd06 100644 --- a/crates/geometry/Cargo.toml +++ b/crates/geometry/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-geometry" -description = "ModelCraft's geometry boundary: CAD import, measures and tessellation, implemented with SolidCraft's B-rep kernel" +name = "fieldcraft-geometry" +description = "FieldCraft's geometry boundary: CAD import, measures and tessellation, implemented with SolidCraft's B-rep kernel" version.workspace = true edition.workspace = true license.workspace = true @@ -9,7 +9,7 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } +fieldcraft-model = { workspace = true } solidcraft-geom = { workspace = true } solidcraft-kernel = { workspace = true } solidcraft-doc = { workspace = true } diff --git a/crates/geometry/src/lib.rs b/crates/geometry/src/lib.rs index 6cc6b8b..bca9fc4 100644 --- a/crates/geometry/src/lib.rs +++ b/crates/geometry/src/lib.rs @@ -1,6 +1,6 @@ //! The geometry boundary. //! -//! Meshing in ModelCraft starts from a triangulated boundary surface. This crate produces it: +//! Meshing in FieldCraft starts from a triangulated boundary surface. This crate produces it: //! B-rep bodies come from SolidCraft's kernel (`solidcraft-kernel`, a pinned git dependency; //! plan/adr/0001-libraries.md) and leave it as a [`SurfaceMesh`] from SolidCraft's tessellator, //! with each triangle tagged by the B-rep face it lies on (for feature edges and for loads and diff --git a/crates/geometry/src/tagged_mesh.rs b/crates/geometry/src/tagged_mesh.rs index f89a408..53b87ca 100644 --- a/crates/geometry/src/tagged_mesh.rs +++ b/crates/geometry/src/tagged_mesh.rs @@ -30,7 +30,7 @@ const MAX_BUILD_ITEMS: usize = 10_000; const MAX_XML_DEPTH: usize = 256; /// Largest coordinate magnitude accepted, in the file and after units and transforms: the FE /// model's own limit, so every imported vertex can become a node. -use modelcraft_model::MAX_COORD; +use fieldcraft_model::MAX_COORD; /// A transform whose determinant is below this fraction of its largest row length cubed /// flattens the object (relative, so a uniform scale is never refused). const MIN_RELATIVE_DET: f64 = 1e-12; diff --git a/crates/io/Cargo.toml b/crates/io/Cargo.toml index ca2a99f..3d7fe17 100644 --- a/crates/io/Cargo.toml +++ b/crates/io/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-io" -description = "ModelCraft's files: the .modelcraft model file, atomic saves and autosave recovery" +name = "fieldcraft-io" +description = "FieldCraft's files: the .fieldcraft model file, atomic saves and autosave recovery" version.workspace = true edition.workspace = true license.workspace = true @@ -9,7 +9,7 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } +fieldcraft-model = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/io/src/fs.rs b/crates/io/src/fs.rs index cfe4582..6209ba7 100644 --- a/crates/io/src/fs.rs +++ b/crates/io/src/fs.rs @@ -142,7 +142,7 @@ mod tests { use super::*; fn dir(tag: &str) -> PathBuf { - let d = std::env::temp_dir().join(format!("modelcraft-fs-{tag}-{}", std::process::id())); + let d = std::env::temp_dir().join(format!("fieldcraft-fs-{tag}-{}", std::process::id())); let _ = fs::remove_dir_all(&d); fs::create_dir_all(&d).unwrap(); d @@ -151,7 +151,7 @@ mod tests { #[test] fn replaces_and_keeps_a_backup() { let d = dir("bak"); - let p = d.join("a.modelcraft"); + let p = d.join("a.fieldcraft"); write_atomic(&p, b"one", true).unwrap(); assert!(!backup_path(&p).exists(), "nothing to back up the first time"); write_atomic(&p, b"two", true).unwrap(); @@ -167,7 +167,7 @@ mod tests { #[test] fn a_save_stopped_part_way_keeps_the_old_file() { let d = dir("stop"); - let p = d.join("a.modelcraft"); + let p = d.join("a.fieldcraft"); write_atomic(&p, b"old", true).unwrap(); for stop in [Step::TempWritten, Step::BackupWritten, Step::Replaced] { write_steps(&p, b"new content", true, stop).unwrap(); @@ -189,7 +189,7 @@ mod tests { #[test] fn a_failed_write_leaves_the_target() { let d = dir("fail"); - let p = d.join("missing-dir").join("a.modelcraft"); + let p = d.join("missing-dir").join("a.fieldcraft"); assert!(write_atomic(&p, b"x", true).is_err()); assert!(!p.exists()); let _ = fs::remove_dir_all(&d); diff --git a/crates/io/src/lib.rs b/crates/io/src/lib.rs index 2604437..3bf6c28 100644 --- a/crates/io/src/lib.rs +++ b/crates/io/src/lib.rs @@ -1,9 +1,9 @@ -//! ModelCraft's files. +//! FieldCraft's files. //! -//! A `.modelcraft` file ([`write`], [`read`]; docs/formats/modelcraft.md) is a ZIP package of four +//! A `.fieldcraft` file ([`write`], [`read`]; docs/formats/fieldcraft.md) is a ZIP package of four //! parts, stored uncompressed so each can be read with any unzip tool: //! -//! - `modelcraft.json`: the manifest. The format tag (`modelcraft/1`), the program that wrote +//! - `fieldcraft.json`: the manifest. The format tag (`fieldcraft/1`), the program that wrote //! it, the geometry the model is built on (the SolidCraft design and the imported 3MF meshes, //! each a path and the SHA-256 of its content, opened again when the model is), and the size //! and SHA-256 of every other part. @@ -30,16 +30,16 @@ pub mod roots; use std::collections::BTreeMap; use std::io::{Cursor, Read, Write}; -use modelcraft_model::Model; +use fieldcraft_model::Model; use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2::{Digest, Sha256}; /// The format tag of the files this version writes and reads. -pub const FORMAT: &str = "modelcraft/1"; +pub const FORMAT: &str = "fieldcraft/1"; /// The file name extension. -pub const EXTENSION: &str = "modelcraft"; +pub const EXTENSION: &str = "fieldcraft"; /// Largest file read. pub const MAX_FILE_BYTES: u64 = 8 << 30; @@ -57,17 +57,17 @@ pub const MAX_MESH_SOURCES: usize = 10_000; pub const MAX_PATH_LEN: usize = 4096; const MAX_COMMAND_LEN: usize = 256; -const MANIFEST: &str = "modelcraft.json"; +const MANIFEST: &str = "fieldcraft.json"; const MODEL: &str = "model.json"; const MESH: &str = "mesh.bin"; const JOURNAL: &str = "journal.json"; #[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum Error { - /// Not a `.modelcraft` file, or a damaged or invalid one. + /// Not a `.fieldcraft` file, or a damaged or invalid one. #[error("{0}")] Invalid(String), - /// A `.modelcraft` file of a format version this program does not read. + /// A `.fieldcraft` file of a format version this program does not read. #[error("{0}")] Version(String), #[error("limit reached: {0}")] @@ -87,9 +87,9 @@ fn invalid(message: impl Into) -> Result { } /// A model error met while reading `part`. -pub(crate) fn model_error(part: &str, e: modelcraft_model::Error) -> Error { +pub(crate) fn model_error(part: &str, e: fieldcraft_model::Error) -> Error { match e { - modelcraft_model::Error::Limit(m) => Error::Limit(format!("{part}: {m}")), + fieldcraft_model::Error::Limit(m) => Error::Limit(format!("{part}: {m}")), e => Error::Invalid(format!("{part}: {e}")), } } @@ -101,7 +101,7 @@ pub fn sha256_hex(bytes: &[u8]) -> String { /// A file the model is built on: its path and the SHA-256 of the content that was opened. /// -/// A relative path is relative to the folder of the `.modelcraft` file that refers to it. +/// A relative path is relative to the folder of the `.fieldcraft` file that refers to it. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(try_from = "SourceFileRepr", into = "SourceFileRepr")] pub struct SourceFile { @@ -178,7 +178,7 @@ pub struct Geometry { /// One journal entry: a command id and its parameters. pub type JournalEntry = (String, Value); -/// What a `.modelcraft` file holds. +/// What a `.fieldcraft` file holds. #[derive(Debug, Clone, Default, PartialEq)] pub struct ModelFile { pub model: Model, @@ -233,7 +233,7 @@ fn journal_text(journal: &[JournalEntry]) -> Result> { Ok(out) } -/// The bytes of a `.modelcraft` file holding `model`, built on `geometry` by the commands in +/// The bytes of a `.fieldcraft` file holding `model`, built on `geometry` by the commands in /// `journal`. The same arguments always give the same bytes. pub fn write(model: &Model, geometry: &Geometry, journal: &[JournalEntry]) -> Result> { if geometry.meshes.len() > MAX_MESH_SOURCES { @@ -246,7 +246,7 @@ pub fn write(model: &Model, geometry: &Geometry, journal: &[JournalEntry]) -> Re let parts = [(MODEL, model_json), (MESH, mesh), (JOURNAL, journal)]; let manifest = Manifest { format: FORMAT.into(), - generator: format!("ModelCraft {}", env!("CARGO_PKG_VERSION")), + generator: format!("FieldCraft {}", env!("CARGO_PKG_VERSION")), geometry: geometry.clone(), parts: parts.iter().map(|(name, bytes)| (name.to_string(), Part { bytes: bytes.len() as u64, sha256: sha256_hex(bytes) })).collect(), }; @@ -285,15 +285,15 @@ fn read_part(zip: &mut zip::ZipArchive>, name: &str, cap: u64, fil Ok(buf) } -/// Read a `.modelcraft` file. +/// Read a `.fieldcraft` file. pub fn read(bytes: &[u8]) -> Result { if bytes.len() as u64 > MAX_FILE_BYTES { return Err(Error::Limit(format!("the file is {} MB (at most {} MB)", bytes.len() >> 20, MAX_FILE_BYTES >> 20))); } if !bytes.starts_with(b"PK\x03\x04") { - return invalid("not a ModelCraft file (not a ZIP package)"); + return invalid("not a FieldCraft file (not a ZIP package)"); } - let mut zip = zip::ZipArchive::new(Cursor::new(bytes)).map_err(|e| Error::Invalid(format!("not a ModelCraft file: {e}")))?; + let mut zip = zip::ZipArchive::new(Cursor::new(bytes)).map_err(|e| Error::Invalid(format!("not a FieldCraft file: {e}")))?; let names = [MANIFEST, MODEL, MESH, JOURNAL]; let mut seen = Vec::with_capacity(zip.len()); for name in zip.file_names() { @@ -310,13 +310,13 @@ pub fn read(bytes: &[u8]) -> Result { let manifest: Value = serde_json::from_slice(&manifest_bytes).map_err(|e| json_error(MANIFEST, e))?; match manifest.get("format").and_then(Value::as_str) { Some(FORMAT) => {} - Some(f) if f.starts_with("modelcraft/") => { + Some(f) if f.starts_with("fieldcraft/") => { return Err(Error::Version(format!( - "the file is format `{}`; this version of ModelCraft reads `{FORMAT}`", + "the file is format `{}`; this version of FieldCraft reads `{FORMAT}`", f.chars().take(40).collect::() ))); } - _ => return invalid(format!("not a ModelCraft file: `{MANIFEST}` has no `{FORMAT}` format tag")), + _ => return invalid(format!("not a FieldCraft file: `{MANIFEST}` has no `{FORMAT}` format tag")), } let manifest: Manifest = serde_json::from_value(manifest).map_err(|e| json_error(MANIFEST, e))?; if manifest.geometry.meshes.len() > MAX_MESH_SOURCES { diff --git a/crates/io/src/mesh.rs b/crates/io/src/mesh.rs index 8a78849..602c69d 100644 --- a/crates/io/src/mesh.rs +++ b/crates/io/src/mesh.rs @@ -1,4 +1,4 @@ -//! The binary mesh part of a `.modelcraft` file (`mesh.bin`): nodes, elements and the geometry +//! The binary mesh part of a `.fieldcraft` file (`mesh.bin`): nodes, elements and the geometry //! tags that say how the mesh lies on the geometry. //! //! Little-endian throughout: @@ -21,8 +21,8 @@ //! in the manifest; decoding checks every count against the bytes present before reading, and //! every value through the model's own checked methods. -use modelcraft_model::geometry::MAX_ENTITIES; -use modelcraft_model::{ElementKind, Entity, GeometryRef, MAX_ELEMENTS, MAX_NODES, Model}; +use fieldcraft_model::geometry::MAX_ENTITIES; +use fieldcraft_model::{ElementKind, Entity, GeometryRef, MAX_ELEMENTS, MAX_NODES, Model}; use crate::{Error, MAX_FILE_BYTES, Result, invalid, model_error}; @@ -221,7 +221,7 @@ pub fn decode_into(model: &mut Model, bytes: &[u8]) -> Result<()> { } let mut r = Reader { bytes, pos: 0 }; if r.take(MAGIC.len()).ok() != Some(MAGIC.as_slice()) { - return invalid(format!("{PART} is not a ModelCraft mesh (no {} header)", String::from_utf8_lossy(MAGIC))); + return invalid(format!("{PART} is not a FieldCraft mesh (no {} header)", String::from_utf8_lossy(MAGIC))); } let nodes = count(&mut r, "nodes", MAX_NODES)?; let elements = count(&mut r, "elements", MAX_ELEMENTS)?; diff --git a/crates/io/src/recovery.rs b/crates/io/src/recovery.rs index eaae57b..94caa69 100644 --- a/crates/io/src/recovery.rs +++ b/crates/io/src/recovery.rs @@ -1,6 +1,6 @@ //! Autosave storage and crash recovery. //! -//! A running app keeps one entry in the recovery folder: `.modelcraft` (the model as of the +//! A running app keeps one entry in the recovery folder: `.fieldcraft` (the model as of the //! last autosave), `.json` (its [`Entry`]: the model's own file, when it has one, and when it //! was autosaved) and `.lock`, which the app holds locked while it runs. An entry whose lock //! nobody holds belongs to an app that crashed or closed with unsaved changes: [`orphans`] lists @@ -17,19 +17,19 @@ use serde::{Deserialize, Serialize}; use crate::fs::{read_capped, write_atomic}; use crate::{EXTENSION, Error, MAX_FILE_BYTES, Result}; -/// The recovery folder: `MODELCRAFT_RECOVERY_DIR`, else `ModelCraft/recovery` (`modelcraft/` on +/// The recovery folder: `FIELDCRAFT_RECOVERY_DIR`, else `FieldCraft/recovery` (`fieldcraft/` on /// Linux) in the platform's per-user data folder. pub fn default_dir() -> Option { let env = |k: &str| std::env::var_os(k).filter(|d| !d.is_empty()).map(PathBuf::from); - if let Some(d) = env("MODELCRAFT_RECOVERY_DIR") { + if let Some(d) = env("FIELDCRAFT_RECOVERY_DIR") { return Some(d); } let (root, name) = if cfg!(windows) { - (env("LOCALAPPDATA")?, "ModelCraft") + (env("LOCALAPPDATA")?, "FieldCraft") } else if cfg!(target_os = "macos") { - (env("HOME")?.join("Library/Application Support"), "ModelCraft") + (env("HOME")?.join("Library/Application Support"), "FieldCraft") } else { - (env("XDG_DATA_HOME").or_else(|| env("HOME").map(|h| h.join(".local/share")))?, "modelcraft") + (env("XDG_DATA_HOME").or_else(|| env("HOME").map(|h| h.join(".local/share")))?, "fieldcraft") }; Some(root.join(name).join("recovery")) } @@ -210,7 +210,7 @@ mod tests { use super::*; fn dir(tag: &str) -> PathBuf { - let d = std::env::temp_dir().join(format!("modelcraft-recovery-{tag}-{}", std::process::id())); + let d = std::env::temp_dir().join(format!("fieldcraft-recovery-{tag}-{}", std::process::id())); let _ = fs::remove_dir_all(&d); d } @@ -219,7 +219,7 @@ mod tests { fn a_crashed_apps_autosave_is_offered_and_a_running_ones_is_not() { let d = dir("crash"); let mut a = Slot::new(&d).unwrap(); - a.write(b"model bytes", Some("/m/part.modelcraft"), 8, 1).unwrap(); + a.write(b"model bytes", Some("/m/part.fieldcraft"), 8, 1).unwrap(); assert!(orphans(&d).is_empty(), "held by a running app"); assert!(remove(&d, a.id()).is_err(), "a running app's entry stays"); let id = a.id().to_string(); @@ -229,7 +229,7 @@ mod tests { assert_eq!(found.iter().map(|e| e.id.as_str()).collect::>(), [id.as_str()]); let (bytes, entry) = load(&d, &id).unwrap(); assert_eq!(bytes, b"model bytes"); - assert_eq!((entry.path.as_deref(), entry.nodes, entry.elements), (Some("/m/part.modelcraft"), 8, 1)); + assert_eq!((entry.path.as_deref(), entry.nodes, entry.elements), (Some("/m/part.fieldcraft"), 8, 1)); // A new app gets its own entry and still sees the crashed one. let b = Slot::new(&d).unwrap(); assert_ne!(b.id(), id); @@ -264,11 +264,11 @@ mod tests { } // An entry naming another id, junk JSON and a huge description are not listed. fs::write(d.join("1-1-1.json"), br#"{"id": "2-2-2", "path": null, "saved_at": 1, "nodes": 0, "elements": 0}"#).unwrap(); - fs::write(d.join("1-1-1.modelcraft"), b"x").unwrap(); - fs::write(d.join("2-2-2.modelcraft"), b"x").unwrap(); + fs::write(d.join("1-1-1.fieldcraft"), b"x").unwrap(); + fs::write(d.join("2-2-2.fieldcraft"), b"x").unwrap(); fs::write(d.join("3-3-3.json"), b"{not json").unwrap(); fs::write(d.join("4-4-4.json"), vec![b' '; (MAX_ENTRY_BYTES + 1) as usize]).unwrap(); - fs::write(d.join("4-4-4.modelcraft"), b"x").unwrap(); + fs::write(d.join("4-4-4.fieldcraft"), b"x").unwrap(); assert!(orphans(&d).is_empty()); assert!(load(&d, "1-1-1").is_err()); let _ = fs::remove_dir_all(&d); diff --git a/crates/io/src/roots.rs b/crates/io/src/roots.rs index fb3a022..5b3d9d1 100644 --- a/crates/io/src/roots.rs +++ b/crates/io/src/roots.rs @@ -1,6 +1,6 @@ //! Folders an automation client may read from and write to. //! -//! An agent driving ModelCraft (the MCP server, docs/mcp.md) can be kept to the folders it was +//! An agent driving FieldCraft (the MCP server, docs/mcp.md) can be kept to the folders it was //! given: every file a command reads must lie inside the read root, every file it writes inside //! the write root. [`Roots::check`] decides, before the file is touched: //! @@ -153,7 +153,7 @@ mod tests { struct Dir(PathBuf); impl Dir { fn new(tag: &str) -> Dir { - let d = std::env::temp_dir().join(format!("modelcraft-roots-{tag}-{}", std::process::id())); + let d = std::env::temp_dir().join(format!("fieldcraft-roots-{tag}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&d); std::fs::create_dir_all(&d).unwrap(); Dir(std::fs::canonicalize(&d).unwrap()) diff --git a/crates/io/tests/file.rs b/crates/io/tests/file.rs index 9896bd0..2bee7ab 100644 --- a/crates/io/tests/file.rs +++ b/crates/io/tests/file.rs @@ -1,9 +1,9 @@ -//! `.modelcraft` files: round trips, stable bytes and hostile files. +//! `.fieldcraft` files: round trips, stable bytes and hostile files. use std::io::{Cursor, Read, Write}; -use modelcraft_io::{Error, Geometry, ModelFile, SourceFile, mesh, read, write}; -use modelcraft_model::{ElementKind, Entity, GeometryRef, Model, UnitSystem}; +use fieldcraft_io::{Error, Geometry, ModelFile, SourceFile, mesh, read, write}; +use fieldcraft_model::{ElementKind, Entity, GeometryRef, Model, UnitSystem}; use serde_json::{Value, json}; /// A hex and a tet sharing a face, tagged on geometry, with a few definitions. @@ -71,7 +71,7 @@ fn package(parts: &[(String, Vec)]) -> Vec { fn with_part(bytes: &[u8], name: &str, new: Vec) -> Vec { let mut ps = parts(bytes); let mut manifest: Value = serde_json::from_slice(&ps[0].1).unwrap(); - manifest["parts"][name] = json!({"bytes": new.len(), "sha256": modelcraft_io::sha256_hex(&new)}); + manifest["parts"][name] = json!({"bytes": new.len(), "sha256": fieldcraft_io::sha256_hex(&new)}); ps[0].1 = serde_json::to_vec(&manifest).unwrap(); for p in &mut ps { if p.0 == name { @@ -100,10 +100,10 @@ fn round_trip_keeps_everything_and_bytes_are_stable() { // The parts, in order, all stored as plain bytes. let names: Vec = parts(&bytes).into_iter().map(|(n, _)| n).collect(); - assert_eq!(names, ["modelcraft.json", "model.json", "mesh.bin", "journal.json"]); + assert_eq!(names, ["fieldcraft.json", "model.json", "mesh.bin", "journal.json"]); let ps = parts(&bytes); let manifest: Value = serde_json::from_slice(&ps[0].1).unwrap(); - assert_eq!(manifest["format"], "modelcraft/1"); + assert_eq!(manifest["format"], "fieldcraft/1"); assert_eq!(manifest["geometry"]["design"]["path"], "parts/plate.solidcraft"); let model_text = String::from_utf8(ps[1].1.clone()).unwrap(); assert!(model_text.contains("\"Steel\"") && !model_text.contains("\"nodes\""), "{model_text}"); @@ -148,12 +148,12 @@ fn hostile_files_are_refused_cleanly() { } // Not a model file at all. - for junk in [&b""[..], b"PK", b"PK\x03\x04garbage", b"{\"format\": \"modelcraft/1\"}", &[0u8; 4096]] { + for junk in [&b""[..], b"PK", b"PK\x03\x04garbage", b"{\"format\": \"fieldcraft/1\"}", &[0u8; 4096]] { assert!(matches!(read(junk), Err(Error::Invalid(_))), "{junk:?}"); } // Wrong and future versions. - let v2 = with_manifest(&good, |m| m["format"] = json!("modelcraft/2")); + let v2 = with_manifest(&good, |m| m["format"] = json!("fieldcraft/2")); assert!(matches!(read(&v2), Err(Error::Version(_)))); for f in [json!("solidcraft/16"), json!(null), json!(1)] { let b = with_manifest(&good, |m| m["format"] = f.clone()); diff --git a/crates/lbm/Cargo.toml b/crates/lbm/Cargo.toml index 53cfde9..04a3232 100644 --- a/crates/lbm/Cargo.toml +++ b/crates/lbm/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-lbm" -description = "ModelCraft's lattice Boltzmann flow solver: D3Q19 two-relaxation-time collision, bounce-back walls, Zou–He inlets and outlets, exact voxelisation of face-tagged surfaces, momentum-exchange wall forces" +name = "fieldcraft-lbm" +description = "FieldCraft's lattice Boltzmann flow solver: D3Q19 two-relaxation-time collision, bounce-back walls, Zou–He inlets and outlets, exact voxelisation of face-tagged surfaces, momentum-exchange wall forces" version.workspace = true edition.workspace = true license.workspace = true @@ -9,7 +9,7 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } -modelcraft-mesh = { workspace = true } -modelcraft-parallel = { workspace = true } +fieldcraft-model = { workspace = true } +fieldcraft-mesh = { workspace = true } +fieldcraft-parallel = { workspace = true } thiserror = { workspace = true } diff --git a/crates/lbm/src/flow.rs b/crates/lbm/src/flow.rs index 2ccc048..ed1349d 100644 --- a/crates/lbm/src/flow.rs +++ b/crates/lbm/src/flow.rs @@ -36,9 +36,9 @@ //! only its own populations; the wall forces are summed in link order. There are no parallel //! reductions, so the result is the same bit for bit on any number of threads and platform. -use modelcraft_model::{BoxFace, FaceCondition, Profile}; +use fieldcraft_model::{BoxFace, FaceCondition, Profile}; -use modelcraft_parallel as parallel; +use fieldcraft_parallel as parallel; use crate::lattice::{C, Q, W, c, equilibrium, moving_wall, opposite}; use crate::setup::{MAX_LATTICE_SPEED, Obstacle, Setup}; diff --git a/crates/lbm/src/lib.rs b/crates/lbm/src/lib.rs index e2e87c2..cdf108f 100644 --- a/crates/lbm/src/lib.rs +++ b/crates/lbm/src/lib.rs @@ -1,6 +1,6 @@ -//! ModelCraft's lattice Boltzmann flow solver (CPU). +//! FieldCraft's lattice Boltzmann flow solver (CPU). //! -//! It solves a fluid step of the model ([`modelcraft_model::Procedure::Fluid`]) in its fluid +//! It solves a fluid step of the model ([`fieldcraft_model::Procedure::Fluid`]) in its fluid //! domain: weakly compressible, isothermal, Newtonian flow on a uniform grid of cubic cells that //! fills the domain's box, less the cells inside the obstacle bodies. //! @@ -33,14 +33,14 @@ //! - P. Lallemand, L.-S. Luo, *Theory of the lattice Boltzmann method: dispersion, dissipation, //! isotropy, Galilean invariance, and stability*, PRE 61:6546, 2000 (stability limits). //! - H. Edelsbrunner, E. P. Mücke, *Simulation of Simplicity*, ACM TOG 9(1):66–104, 1990; -//! J. R. Shewchuk 1997 (exact predicates, through `modelcraft_mesh::predicates`). +//! J. R. Shewchuk 1997 (exact predicates, through `fieldcraft_mesh::predicates`). //! //! No code of any lattice Boltzmann package was read; everything is written from these papers and //! the book. //! //! # Determinism //! Double precision and fixed loop orders. Collision and streaming run on threads by fixed blocks -//! of cells ([`modelcraft_parallel`]), each cell's update independent of the others; the wall +//! of cells ([`fieldcraft_parallel`]), each cell's update independent of the others; the wall //! forces and every other sum run in a fixed order on one thread. The same model gives the same //! result bit for bit on any number of threads and on every platform, wasm included. #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] @@ -71,9 +71,9 @@ pub enum Error { #[error("unstable: {0}")] Unstable(String), #[error(transparent)] - Model(#[from] modelcraft_model::Error), + Model(#[from] fieldcraft_model::Error), #[error(transparent)] - Mesh(#[from] modelcraft_mesh::Error), + Mesh(#[from] fieldcraft_mesh::Error), } pub type Result = std::result::Result; diff --git a/crates/lbm/src/setup.rs b/crates/lbm/src/setup.rs index 922de05..9e9cd6e 100644 --- a/crates/lbm/src/setup.rs +++ b/crates/lbm/src/setup.rs @@ -25,8 +25,8 @@ //! While running, a lattice speed above [`MAX_LATTICE_SPEED`] or a non-finite or non-positive //! density stops the run with [`crate::Error::Unstable`]. -use modelcraft_mesh::surface::BoundarySurface; -use modelcraft_model::{BoxFace, FaceCondition, FluidDomain, Model, Procedure, SurfaceCondition}; +use fieldcraft_mesh::surface::BoundarySurface; +use fieldcraft_model::{BoxFace, FaceCondition, FluidDomain, Model, Procedure, SurfaceCondition}; use crate::voxel::Grid; use crate::{Error, Limits, Result}; @@ -101,7 +101,7 @@ fn norm(v: [f64; 3]) -> f64 { } /// The fluid step named `step` (or the only fluid step when `step` is `None`) and its domain. -pub fn fluid_step<'m>(model: &'m Model, step: Option<&str>) -> Result<(&'m modelcraft_model::Step, &'m FluidDomain)> { +pub fn fluid_step<'m>(model: &'m Model, step: Option<&str>) -> Result<(&'m fieldcraft_model::Step, &'m FluidDomain)> { let s = model.select_step(step, |p| p.fluid_domain().is_some(), "fluid")?; let name = s.procedure.fluid_domain().unwrap_or_default(); let domain = model.fluid_domain(name).ok_or_else(|| Error::NotFound(format!("fluid domain `{name}`")))?; @@ -258,7 +258,7 @@ impl Setup { for sb in domain.surfaces.iter().filter(|sb| &sb.face.body == body) { let mut hit = false; for (tag, name) in names.iter().enumerate() { - if sb.face.selects(&modelcraft_model::GeometryRef::face(body.as_str(), name.as_str())?) { + if sb.face.selects(&fieldcraft_model::GeometryRef::face(body.as_str(), name.as_str())?) { hit = true; if let (Some(slot), SurfaceCondition::MovingWall { velocity }) = (velocities.get_mut(tag), sb.condition) { *slot = velocity.map(|c| c * dt / h); diff --git a/crates/lbm/src/solve.rs b/crates/lbm/src/solve.rs index e209155..5d7052b 100644 --- a/crates/lbm/src/solve.rs +++ b/crates/lbm/src/solve.rs @@ -1,6 +1,6 @@ //! Running a fluid step: the time loop, output frames and force histories, in model units. -use modelcraft_model::Model; +use fieldcraft_model::Model; use crate::flow::Flow; use crate::setup::{Obstacle, Setup}; diff --git a/crates/lbm/src/voxel.rs b/crates/lbm/src/voxel.rs index a185999..91d7c23 100644 --- a/crates/lbm/src/voxel.rs +++ b/crates/lbm/src/voxel.rs @@ -30,8 +30,8 @@ //! lower triangle index, so the assignment is deterministic. This is a classification for //! reporting, not a meshing decision, so floating-point distances are adequate. -use modelcraft_mesh::predicates::{Point2, Point3, Sign, orient2d, orient3d, snap_to_domain}; -use modelcraft_mesh::surface::BoundarySurface; +use fieldcraft_mesh::predicates::{Point2, Point3, Sign, orient2d, orient3d, snap_to_domain}; +use fieldcraft_mesh::surface::BoundarySurface; use crate::{Error, Result}; @@ -373,7 +373,7 @@ impl TriangleTree { #[cfg(test)] mod tests { use super::*; - use modelcraft_mesh::surface::SurfaceOptions; + use fieldcraft_mesh::surface::SurfaceOptions; /// The surface of the box [lo, hi], outward, two triangles per side, side k tagged k. pub(crate) fn cube(lo: [f64; 3], hi: [f64; 3]) -> BoundarySurface { diff --git a/crates/lbm/tests/benchmark.rs b/crates/lbm/tests/benchmark.rs index 9642ac6..a65d207 100644 --- a/crates/lbm/tests/benchmark.rs +++ b/crates/lbm/tests/benchmark.rs @@ -1,13 +1,13 @@ //! Benchmark suite: comparisons with published numerical reference values (not analytic; the //! analytic suite in `verification.rs` is the gate). Each case names its source. The cases are //! long, so they are ignored by default: -//! `cargo test --release -p modelcraft-lbm --test benchmark -- --ignored --nocapture`. +//! `cargo test --release -p fieldcraft-lbm --test benchmark -- --ignored --nocapture`. mod common; use common::*; -use modelcraft_lbm::{Limits, solve_fluid}; -use modelcraft_model::{FaceCondition, Procedure, Profile}; +use fieldcraft_lbm::{Limits, solve_fluid}; +use fieldcraft_model::{FaceCondition, Procedure, Profile}; /// Benchmark "2D-1" (steady flow around a cylinder at Re = 20): M. Schäfer, S. Turek, *Benchmark /// computations of laminar flow around a cylinder*, in: Flow Simulation with High-Performance diff --git a/crates/lbm/tests/common/mod.rs b/crates/lbm/tests/common/mod.rs index 64fde11..2125a70 100644 --- a/crates/lbm/tests/common/mod.rs +++ b/crates/lbm/tests/common/mod.rs @@ -4,9 +4,9 @@ use std::collections::BTreeMap; -use modelcraft_lbm::{Flow, Limits, Obstacle, Setup}; -use modelcraft_mesh::surface::{BoundarySurface, SurfaceOptions}; -use modelcraft_model::{BoxFaces, Fluid, FluidDomain, Material, Model, Procedure, Step}; +use fieldcraft_lbm::{Flow, Limits, Obstacle, Setup}; +use fieldcraft_mesh::surface::{BoundarySurface, SurfaceOptions}; +use fieldcraft_model::{BoxFaces, Fluid, FluidDomain, Material, Model, Procedure, Step}; /// A domain over [min, max] with walls on every face. pub fn domain(min: [f64; 3], max: [f64; 3], spacing: f64) -> FluidDomain { diff --git a/crates/lbm/tests/throughput.rs b/crates/lbm/tests/throughput.rs index 308236c..95ff545 100644 --- a/crates/lbm/tests/throughput.rs +++ b/crates/lbm/tests/throughput.rs @@ -1,10 +1,10 @@ //! Throughput of the solver (ignored; run with -//! `cargo test --release -p modelcraft-lbm --test throughput -- --ignored --nocapture`). +//! `cargo test --release -p fieldcraft-lbm --test throughput -- --ignored --nocapture`). mod common; use common::*; -use modelcraft_model::{BoxFace, FaceCondition}; +use fieldcraft_model::{BoxFace, FaceCondition}; #[test] #[ignore] @@ -26,7 +26,7 @@ fn cell_updates_per_second() { 1.0, 1.0 / 6.0, d, - modelcraft_model::Procedure::Fluid { + fieldcraft_model::Procedure::Fluid { domain: "Flow".into(), end_time: None, steps: Some(steps), diff --git a/crates/lbm/tests/verification.rs b/crates/lbm/tests/verification.rs index 0ccc72b..82d1723 100644 --- a/crates/lbm/tests/verification.rs +++ b/crates/lbm/tests/verification.rs @@ -4,13 +4,13 @@ //! of a periodic array of spheres (Hasimoto 1959), inlet/outlet channel flow, mass conservation //! to round-off, and the stability limits. //! -//! Each test prints its errors and measured orders (`cargo test -p modelcraft-lbm -- --nocapture`). +//! Each test prints its errors and measured orders (`cargo test -p fieldcraft-lbm -- --nocapture`). mod common; use common::*; -use modelcraft_lbm::{Error, Limits, Setup, solve_fluid}; -use modelcraft_model::{FaceCondition, Profile}; +use fieldcraft_lbm::{Error, Limits, Setup, solve_fluid}; +use fieldcraft_model::{FaceCondition, Profile}; const PERIODIC: FaceCondition = FaceCondition::Periodic {}; @@ -100,7 +100,7 @@ fn taylor_green(n: usize) -> (f64, f64) { let (nu, u0, l) = (0.1, 0.1, 2.0 * std::f64::consts::PI); let dx = l / n as f64; let mut d = domain([0.0; 3], [l, l, dx], dx); - for f in modelcraft_model::BoxFace::ALL { + for f in fieldcraft_model::BoxFace::ALL { *d.faces.get_mut(f) = PERIODIC; } // τ = 0.8: Δt = 0.3 Δx² / (3 ν). @@ -222,7 +222,7 @@ fn sphere_drag(r: f64, l: usize, steps: u64) -> (f64, f64) { let (nu, a) = (5.0 / 6.0, 1e-6); let side = l as f64; let mut d = domain([0.0; 3], [side; 3], 1.0); - for f in modelcraft_model::BoxFace::ALL { + for f in fieldcraft_model::BoxFace::ALL { *d.faces.get_mut(f) = PERIODIC; } d.acceleration = [a, 0.0, 0.0]; @@ -232,7 +232,7 @@ fn sphere_drag(r: f64, l: usize, steps: u64) -> (f64, f64) { 1.0, nu, d, - modelcraft_model::Procedure::Fluid { + fieldcraft_model::Procedure::Fluid { domain: "Flow".into(), end_time: None, steps: Some(steps), @@ -287,7 +287,7 @@ fn channel_with_a_parabolic_inlet_and_a_pressure_outlet_develops_poiseuille_flow // τ = 0.6. The pressure drop changes the density along the channel by Δp / (ρ c_s²) in lattice // units (the weakly compressible error), here 0.07%; the automatic time step would give 1.6%. let dt = 0.1 / 3.0 * dx * dx / nu; - let procedure = modelcraft_model::Procedure::Fluid { + let procedure = fieldcraft_model::Procedure::Fluid { domain: "Flow".into(), end_time: Some(4.0 * h * h / nu), steps: None, @@ -372,7 +372,7 @@ fn results_are_bit_for_bit_reproducible() { 1.0, 0.01, d, - modelcraft_model::Procedure::Fluid { domain: "Flow".into(), end_time: Some(1.0), steps: None, time_step: None, frames: 3, magic: None }, + fieldcraft_model::Procedure::Fluid { domain: "Flow".into(), end_time: Some(1.0), steps: None, time_step: None, frames: 3, magic: None }, ); let a = solve_fluid(&m, None, std::slice::from_ref(&ball), &Limits::default()).unwrap(); let b = solve_fluid(&m, None, std::slice::from_ref(&ball), &Limits::default()).unwrap(); diff --git a/crates/mcp/Cargo.toml b/crates/mcp/Cargo.toml index 3b86e15..e0d676f 100644 --- a/crates/mcp/Cargo.toml +++ b/crates/mcp/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-mcp" -description = "ModelCraft's MCP server: JSON-RPC 2.0 over stdio, driving a headless session or the running app" +name = "fieldcraft-mcp" +description = "FieldCraft's MCP server: JSON-RPC 2.0 over stdio, driving a headless session or the running app" version.workspace = true edition.workspace = true license.workspace = true @@ -9,10 +9,10 @@ license.workspace = true workspace = true [dependencies] -modelcraft-engine = { workspace = true } -modelcraft-io = { workspace = true } -modelcraft-render = { workspace = true } +fieldcraft-engine = { workspace = true } +fieldcraft-io = { workspace = true } +fieldcraft-render = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -modelcraft-testkit = { workspace = true } +fieldcraft-testkit = { workspace = true } diff --git a/crates/mcp/src/backend.rs b/crates/mcp/src/backend.rs index c607d97..4cf224b 100644 --- a/crates/mcp/src/backend.rs +++ b/crates/mcp/src/backend.rs @@ -8,10 +8,10 @@ use std::path::PathBuf; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; -use modelcraft_engine::Session; -use modelcraft_engine::view::{Palette, render_png}; -use modelcraft_io::roots::Roots; -use modelcraft_render::StandardView; +use fieldcraft_engine::Session; +use fieldcraft_engine::view::{Palette, render_png}; +use fieldcraft_io::roots::Roots; +use fieldcraft_render::StandardView; use serde_json::{Value, json}; /// What a picture shows. @@ -33,7 +33,7 @@ pub struct Picture { pub info: Value, } -/// Something that runs ModelCraft commands and draws pictures. +/// Something that runs FieldCraft commands and draws pictures. pub trait Backend { /// Run a command (`engine.execute`). fn execute(&mut self, command: &str, params: Value) -> Result; @@ -93,7 +93,7 @@ impl Backend for Headless { } fn commands(&mut self) -> Result { - Ok(modelcraft_engine::commands().iter().map(|c| json!({"id": c.id, "label": c.label, "params": c.params})).collect()) + Ok(fieldcraft_engine::commands().iter().map(|c| json!({"id": c.id, "label": c.label, "params": c.params})).collect()) } fn picture(&mut self, r: &PictureRequest) -> Result { @@ -122,12 +122,12 @@ impl Backend for Headless { } fn describe(&self) -> String { - "a headless in-process ModelCraft session (no window; screenshots are CPU renders)".into() + "a headless in-process FieldCraft session (no window; screenshots are CPU renders)".into() } } -/// A running ModelCraft app reached through its loopback control channel -/// (`modelcraft --control PORT`). +/// A running FieldCraft app reached through its loopback control channel +/// (`fieldcraft --control PORT`). pub struct Remote { addr: String, conn: Option<(BufReader, TcpStream)>, @@ -198,7 +198,7 @@ impl Remote { let reply = match self.roundtrip(&line) { Ok(r) => r, // One retry on a fresh connection (the app may have restarted). - Err(_) => self.roundtrip(&line).map_err(|e| format!("ModelCraft at {}: {e}", self.addr))?, + Err(_) => self.roundtrip(&line).map_err(|e| format!("FieldCraft at {}: {e}", self.addr))?, }; let v: Value = serde_json::from_str(&reply).map_err(|e| format!("bad reply from the app: {e}"))?; if v.get("ok").and_then(Value::as_bool) == Some(true) { @@ -212,7 +212,7 @@ impl Remote { /// A temporary PNG path the app writes and the server reads back (both on this computer). fn temp_png() -> PathBuf { let n = SHOT.fetch_add(1, Ordering::Relaxed); - std::env::temp_dir().join(format!("modelcraft-mcp-{}-{n}.png", std::process::id())) + std::env::temp_dir().join(format!("fieldcraft-mcp-{}-{n}.png", std::process::id())) } impl Backend for Remote { @@ -262,6 +262,6 @@ impl Backend for Remote { } fn describe(&self) -> String { - format!("connected to the ModelCraft app at {} (the user watches its window)", self.addr) + format!("connected to the FieldCraft app at {} (the user watches its window)", self.addr) } } diff --git a/crates/mcp/src/lib.rs b/crates/mcp/src/lib.rs index 2777e09..65851dd 100644 --- a/crates/mcp/src/lib.rs +++ b/crates/mcp/src/lib.rs @@ -1,4 +1,4 @@ -//! ModelCraft's MCP server (docs/mcp.md). +//! FieldCraft's MCP server (docs/mcp.md). //! //! JSON-RPC 2.0 over stdio, one message per line, implementing the Model Context Protocol's //! lifecycle, tools and resources. Every tool runs engine commands (the same ones the UI, @@ -10,7 +10,7 @@ //! The server treats its input as hostile: a message line is capped at [`MAX_LINE`] bytes and //! answered with an error past it, a JSON-RPC batch at [`MAX_BATCH_MESSAGES`] messages, a //! `batch` tool call at [`tools::MAX_STEPS`] commands, and tool arguments are checked against -//! the tool's schema before anything runs. Read and write roots ([`modelcraft_io::roots`]) +//! the tool's schema before anything runs. Read and write roots ([`fieldcraft_io::roots`]) //! confine every file a tool touches. //! //! Adapted from SolidCraft's `crates/mcp` (MIT OR Apache-2.0), see ATTRIBUTION.md. @@ -24,9 +24,9 @@ pub mod tools; pub use backend::{Backend, Headless, Picture, PictureRequest, Remote}; pub use server::{MAX_BATCH_MESSAGES, MAX_LINE, PROTOCOL_VERSION, SUPPORTED_VERSIONS, Server}; -use modelcraft_io::roots::Roots; +use fieldcraft_io::roots::Roots; -/// How the server is started (`modelcraft-cli mcp`). +/// How the server is started (`fieldcraft-cli mcp`). #[derive(Debug, Clone, Default)] pub struct Options { /// The control channel of a running app (`127.0.0.1:7070`); `None` runs headless. @@ -46,7 +46,7 @@ pub fn backend(options: Options) -> Result<(Box, Option), S )), Some(addr) => match Remote::connect(&addr) { Ok(r) => Ok((Box::new(r), None)), - Err(e) => Ok((headless(options.roots), Some(format!("ModelCraft at {addr} cannot be reached ({e}); running headless")))), + Err(e) => Ok((headless(options.roots), Some(format!("FieldCraft at {addr} cannot be reached ({e}); running headless")))), }, None => Ok((headless(options.roots), None)), } diff --git a/crates/mcp/src/server.rs b/crates/mcp/src/server.rs index e4bc4c8..e81b676 100644 --- a/crates/mcp/src/server.rs +++ b/crates/mcp/src/server.rs @@ -23,9 +23,9 @@ const INVALID_PARAMS: i64 = -32602; const INTERNAL_ERROR: i64 = -32603; const RESOURCE_NOT_FOUND: i64 = -32002; -const INSTRUCTIONS: &str = "ModelCraft turns geometry into finite element models and solves them. The model is \ +const INSTRUCTIONS: &str = "FieldCraft turns geometry into finite element models and solves them. The model is \ unit-consistent and never converts (SolidCraft geometry is in mm, so use mm-t-s-N-MPa: E in MPa, density in t/mm³, \ -forces in N); Z is up. A typical analysis: `open` a .solidcraft design (or a .modelcraft model, or a face-tagged \ +forces in N); Z is up. A typical analysis: `open` a .solidcraft design (or a .fieldcraft model, or a face-tagged \ .3mf); `inspect_model` for its bodies and their persistent face names; `mesh` (method tet: {body, tolerance}); \ then `execute` the model definition commands (model.set.create {name, geometry: [{body, face}]}, \ model.material.create, model.section.create, model.boundary_condition.create, model.load.create, model.step.create \ @@ -182,7 +182,7 @@ impl Server { Ok(json!({ "protocolVersion": version, "capabilities": {"tools": {"listChanged": false}, "resources": {"listChanged": false}}, - "serverInfo": {"name": "modelcraft", "title": "ModelCraft", "version": env!("CARGO_PKG_VERSION")}, + "serverInfo": {"name": "fieldcraft", "title": "FieldCraft", "version": env!("CARGO_PKG_VERSION")}, "instructions": format!("{INSTRUCTIONS} Backend: {}.", self.backend.describe()), })) } @@ -198,16 +198,16 @@ impl Server { Ok(call_tool(self.backend.as_mut(), name, &args)) } "resources/list" => Ok(json!({"resources": [ - {"uri": "modelcraft://model", "name": "Model", "description": "model.info: node and element counts, units, definitions", "mimeType": "application/json"}, - {"uri": "modelcraft://commands", "name": "Command catalog", "description": "every command with its parameter docs", "mimeType": "application/json"}, + {"uri": "fieldcraft://model", "name": "Model", "description": "model.info: node and element counts, units, definitions", "mimeType": "application/json"}, + {"uri": "fieldcraft://commands", "name": "Command catalog", "description": "every command with its parameter docs", "mimeType": "application/json"}, ]})), "resources/templates/list" => Ok(json!({"resourceTemplates": []})), "prompts/list" => Ok(json!({"prompts": []})), "resources/read" => { let uri = params.get("uri").and_then(Value::as_str).unwrap_or(""); let v = match uri { - "modelcraft://model" => self.backend.execute("model.info", json!({})), - "modelcraft://commands" => self.backend.commands(), + "fieldcraft://model" => self.backend.execute("model.info", json!({})), + "fieldcraft://commands" => self.backend.commands(), _ => return Err((RESOURCE_NOT_FOUND, format!("unknown resource `{}`", uri.chars().take(200).collect::()))), } .map_err(|e| (INTERNAL_ERROR, e))?; diff --git a/crates/mcp/src/tests.rs b/crates/mcp/src/tests.rs index 2752d46..c20b68a 100644 --- a/crates/mcp/src/tests.rs +++ b/crates/mcp/src/tests.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; -use modelcraft_io::roots::Roots; +use fieldcraft_io::roots::Roots; use serde_json::{Value, json}; use crate::{Headless, MAX_LINE, Options, Server}; @@ -38,7 +38,7 @@ fn ok(s: &mut Server, name: &str, args: Value) -> Value { struct Dir(PathBuf); impl Dir { fn new(tag: &str) -> Dir { - let d = std::env::temp_dir().join(format!("modelcraft-mcp-{tag}-{}", std::process::id())); + let d = std::env::temp_dir().join(format!("fieldcraft-mcp-{tag}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&d); std::fs::create_dir_all(&d).unwrap(); Dir(std::fs::canonicalize(&d).unwrap()) @@ -55,7 +55,7 @@ fn initialize_lists_tools_and_resources() { let mut s = server(); let r = send(&mut s, json!({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-03-26", "capabilities": {}}})); assert_eq!(r["result"]["protocolVersion"], "2025-03-26"); - assert_eq!(r["result"]["serverInfo"]["name"], "modelcraft"); + assert_eq!(r["result"]["serverInfo"]["name"], "fieldcraft"); assert!(r["result"]["instructions"].as_str().unwrap().contains("headless")); let r = send(&mut s, json!({"jsonrpc": "2.0", "id": 2, "method": "initialize", "params": {"protocolVersion": "1999-01-01"}})); assert_eq!(r["result"]["protocolVersion"], crate::PROTOCOL_VERSION); @@ -83,9 +83,9 @@ fn initialize_lists_tools_and_resources() { ] { assert!(names.contains(&t), "{t}"); } - let r = send(&mut s, json!({"jsonrpc": "2.0", "id": 4, "method": "resources/read", "params": {"uri": "modelcraft://model"}})); + let r = send(&mut s, json!({"jsonrpc": "2.0", "id": 4, "method": "resources/read", "params": {"uri": "fieldcraft://model"}})); assert!(r["result"]["contents"][0]["text"].as_str().unwrap().contains("nodes")); - let r = send(&mut s, json!({"jsonrpc": "2.0", "id": 5, "method": "resources/read", "params": {"uri": "modelcraft://commands"}})); + let r = send(&mut s, json!({"jsonrpc": "2.0", "id": 5, "method": "resources/read", "params": {"uri": "fieldcraft://commands"}})); assert!(r["result"]["contents"][0]["text"].as_str().unwrap().contains("solve.static")); assert_eq!(send(&mut s, json!({"jsonrpc": "2.0", "id": 6, "method": "ping"}))["result"], json!({})); } @@ -159,7 +159,7 @@ fn hostile_tool_arguments_are_tool_errors() { ("execute", json!({"command": "mesh.element.add", "params": {"label": 1, "kind": "hex20", "nodes": vec![u32::MAX; 20]}})), ("execute", json!({"command": "file.open", "params": {"path": "\u{0}"}})), ("open", json!({"path": "/etc/passwd"})), - ("open", json!({"path": "/no/such/model.modelcraft"})), + ("open", json!({"path": "/no/such/model.fieldcraft"})), ("open", json!({"path": huge.clone() + ".solidcraft"})), ("save", json!({})), ("write_deck", json!({"format": "abaqus", "path": "/no/such/dir/x.inp"})), @@ -221,7 +221,7 @@ fn file_access_stays_inside_the_roots() { for f in [&input, &output, &outside] { std::fs::create_dir_all(f).unwrap(); } - let design = modelcraft_testkit::corpus::Analytic::Cylinder { radius: 5.0, height: 10.0 }.design().unwrap(); + let design = fieldcraft_testkit::corpus::Analytic::Cylinder { radius: 5.0, height: 10.0 }.design().unwrap(); std::fs::write(outside.join("c.solidcraft"), &design).unwrap(); std::fs::write(input.join("c.solidcraft"), &design).unwrap(); #[cfg(unix)] @@ -236,16 +236,16 @@ fn file_access_stays_inside_the_roots() { refused(&mut s, "open", json!({"path": input.join("link.solidcraft")})); refused(&mut s, "execute", json!({"command": "geometry.open", "params": {"path": input.join("../outside/c.solidcraft")}})); ok(&mut s, "open", json!({"path": input.join("c.solidcraft")})); - refused(&mut s, "save", json!({"path": outside.join("m.modelcraft")})); - refused(&mut s, "save", json!({"path": input.join("m.modelcraft")})); - refused(&mut s, "execute", json!({"command": "file.save_as", "params": {"path": outside.join("m.modelcraft")}})); + refused(&mut s, "save", json!({"path": outside.join("m.fieldcraft")})); + refused(&mut s, "save", json!({"path": input.join("m.fieldcraft")})); + refused(&mut s, "execute", json!({"command": "file.save_as", "params": {"path": outside.join("m.fieldcraft")}})); refused(&mut s, "write_deck", json!({"format": "abaqus", "path": outside.join("m.inp")})); refused(&mut s, "screenshot", json!({"path": outside.join("x.png")})); refused(&mut s, "screenshot", json!({"path": input.join("x.png")})); refused(&mut s, "execute", json!({"command": "file.recovery.list", "params": {"dir": outside}})); - ok(&mut s, "save", json!({"path": output.join("m.modelcraft")})); + ok(&mut s, "save", json!({"path": output.join("m.fieldcraft")})); ok(&mut s, "screenshot", json!({"path": output.join("x.png")})); - assert!(output.join("m.modelcraft").is_file() && output.join("x.png").is_file()); + assert!(output.join("m.fieldcraft").is_file() && output.join("x.png").is_file()); assert_eq!(std::fs::read_dir(&outside).unwrap().count(), 1, "nothing written outside"); } diff --git a/crates/mcp/src/tools.rs b/crates/mcp/src/tools.rs index 82b171d..6d7a2ee 100644 --- a/crates/mcp/src/tools.rs +++ b/crates/mcp/src/tools.rs @@ -3,7 +3,7 @@ use std::path::Path; -use modelcraft_io::roots::Access; +use fieldcraft_io::roots::Access; use serde_json::{Map, Value, json}; use crate::backend::{Backend, PictureRequest}; @@ -29,7 +29,7 @@ const ANALYSES: &[(&str, &str)] = const VIEWS: &[&str] = &["iso", "home", "fit", "front", "back", "top", "bottom", "left", "right"]; fn available(table: &'static [T], command: fn(T) -> &'static str) -> impl Iterator { - table.iter().copied().filter(move |t| modelcraft_engine::find_command(command(*t)).is_some()) + table.iter().copied().filter(move |t| fieldcraft_engine::find_command(command(*t)).is_some()) } fn obj(props: Value, required: &[&str]) -> Value { @@ -137,17 +137,17 @@ pub fn tool_definitions() -> Vec { tool( "open", "Open a file", - "Open by extension: a .modelcraft model (file.open: replaces the model; its design and meshes open again), a \ + "Open by extension: a .fieldcraft model (file.open: replaces the model; its design and meshes open again), a \ SolidCraft design .solidcraft (geometry.open: its bodies become the geometry) or a face-tagged .3mf mesh \ (geometry.import.mesh: added as a surface).", - obj(json!({"path": string("Path to a .modelcraft, .solidcraft or .3mf file")}), &["path"]), + obj(json!({"path": string("Path to a .fieldcraft, .solidcraft or .3mf file")}), &["path"]), false, true, ), tool( "save", "Save the model", - "Save the model as a .modelcraft file (file.save): to `path`, or to the file it came from.", + "Save the model as a .fieldcraft file (file.save): to `path`, or to the file it came from.", obj(json!({"path": string("Path to save to (default: the model's file)")}), &[]), false, false, @@ -406,10 +406,10 @@ fn dispatch(b: &mut dyn Backend, name: &str, a: &Map) -> Result "file.open", + Some("fieldcraft") => "file.open", Some("solidcraft") => "geometry.open", Some("3mf") => "geometry.import.mesh", - _ => return Err("open: a .modelcraft model, a .solidcraft design or a .3mf mesh".into()), + _ => return Err("open: a .fieldcraft model, a .solidcraft design or a .3mf mesh".into()), }; let r = b.execute(command, json!({"path": path}))?; Ok(json!({"command": command, "result": r})) @@ -534,7 +534,7 @@ fn capture(b: &mut dyn Backend, a: &Map) -> Result<(Vec, Valu let picture = b.picture(&request)?; let mut info = picture.info; if let Some(path) = &keep { - modelcraft_io::fs::write_atomic(path, &picture.png, false).map_err(|e| e.to_string())?; + fieldcraft_io::fs::write_atomic(path, &picture.png, false).map_err(|e| e.to_string())?; info["path"] = json!(path.to_string_lossy()); } info["bytes"] = json!(picture.png.len()); diff --git a/crates/mcp/tests/workflow.rs b/crates/mcp/tests/workflow.rs index e0835dc..07f5b06 100644 --- a/crates/mcp/tests/workflow.rs +++ b/crates/mcp/tests/workflow.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; -use modelcraft_io::roots::Roots; -use modelcraft_mcp::{Headless, Server}; +use fieldcraft_io::roots::Roots; +use fieldcraft_mcp::{Headless, Server}; use serde_json::{Value, json}; struct Client { @@ -57,18 +57,18 @@ fn base64_decode(s: &str) -> Vec { #[test] fn a_corpus_part_is_meshed_loaded_solved_and_rendered_over_mcp() { - let dir: PathBuf = std::env::temp_dir().join(format!("modelcraft-mcp-workflow-{}", std::process::id())); + let dir: PathBuf = std::env::temp_dir().join(format!("fieldcraft-mcp-workflow-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let dir = std::fs::canonicalize(&dir).unwrap(); - let part = modelcraft_testkit::corpus::read_recipe(&modelcraft_testkit::corpus::corpus_dir().join("stepped-block")).unwrap(); + let part = fieldcraft_testkit::corpus::read_recipe(&fieldcraft_testkit::corpus::corpus_dir().join("stepped-block")).unwrap(); let design = dir.join("steps.solidcraft"); - std::fs::write(&design, modelcraft_testkit::corpus::design_bytes(&part).unwrap()).unwrap(); + std::fs::write(&design, fieldcraft_testkit::corpus::design_bytes(&part).unwrap()).unwrap(); let roots = Roots::new(Some(&dir), Some(&dir)).unwrap(); let mut c = Client { server: Server::new(Box::new(Headless::new(roots))), next: 0 }; let init = c.request("initialize", json!({"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "test", "version": "1"}})); - assert_eq!(init["serverInfo"]["name"], "modelcraft"); + assert_eq!(init["serverInfo"]["name"], "fieldcraft"); assert!(c.server.handle_line(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#).is_none()); // Open the design and look at its faces. @@ -134,8 +134,8 @@ fn a_corpus_part_is_meshed_loaded_solved_and_rendered_over_mcp() { assert_eq!(info["results"]["step"], "Load", "{info}"); // Save the model and write a deck, both inside the root. - c.json("save", json!({"path": dir.join("steps.modelcraft")})); + c.json("save", json!({"path": dir.join("steps.fieldcraft")})); c.json("write_deck", json!({"format": "abaqus", "path": dir.join("steps.inp")})); - assert!(dir.join("steps.modelcraft").is_file() && dir.join("steps.inp").is_file()); + assert!(dir.join("steps.fieldcraft").is_file() && dir.join("steps.inp").is_file()); let _ = std::fs::remove_dir_all(&dir); } diff --git a/crates/mesh/Cargo.toml b/crates/mesh/Cargo.toml index d1df9b1..948375e 100644 --- a/crates/mesh/Cargo.toml +++ b/crates/mesh/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-mesh" -description = "ModelCraft's meshing: robust predicates, surface preparation, tetrahedral and hexahedral meshing, quality metrics" +name = "fieldcraft-mesh" +description = "FieldCraft's meshing: robust predicates, surface preparation, tetrahedral and hexahedral meshing, quality metrics" version.workspace = true edition.workspace = true license.workspace = true @@ -9,11 +9,11 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } +fieldcraft-model = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -# Test oracles only (never linked into ModelCraft): exact rationals, and georust's port of +# Test oracles only (never linked into FieldCraft): exact rationals, and georust's port of # Shewchuk's public-domain predicates.c as a differential reference. num-bigint = "0.4" num-rational = { version = "0.4", default-features = false, features = ["num-bigint", "std"] } diff --git a/crates/mesh/src/block.rs b/crates/mesh/src/block.rs index b988fcb..daf8afd 100644 --- a/crates/mesh/src/block.rs +++ b/crates/mesh/src/block.rs @@ -18,7 +18,7 @@ //! Every element is checked with the certified Jacobian test ([`crate::quality`]); a block //! whose corners are not in hex8 order or whose curves fold it is an error. -use modelcraft_model::{ElementKind, MAX_COORD}; +use fieldcraft_model::{ElementKind, MAX_COORD}; use crate::quad::{lattice_index, transfinite_faces, transfinite_grid_blended, transfinite_volume}; use crate::quality::{Validity, element_validity, planar_quad_validity}; diff --git a/crates/mesh/src/cdt/mod.rs b/crates/mesh/src/cdt/mod.rs index a653085..fb1ad30 100644 --- a/crates/mesh/src/cdt/mod.rs +++ b/crates/mesh/src/cdt/mod.rs @@ -54,7 +54,7 @@ mod tests; use std::collections::HashMap; -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use crate::delaunay::Triangulation; use crate::predicates::{GenericPoint, Point3, Sign}; diff --git a/crates/mesh/src/delaunay/tests.rs b/crates/mesh/src/delaunay/tests.rs index 60aec50..171b102 100644 --- a/crates/mesh/src/delaunay/tests.rs +++ b/crates/mesh/src/delaunay/tests.rs @@ -1,4 +1,4 @@ -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use super::*; use crate::quality::{GateOptions, enclosed_volume, validity_gate_elements}; diff --git a/crates/mesh/src/hex/mod.rs b/crates/mesh/src/hex/mod.rs index 431bf34..11e4803 100644 --- a/crates/mesh/src/hex/mod.rs +++ b/crates/mesh/src/hex/mod.rs @@ -54,7 +54,7 @@ use crate::size::SizeField; use crate::surface::BoundarySurface; use crate::vec3::{P, cross, dot, sub}; use crate::{Error, Result}; -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; /// Which structured method to use. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -323,7 +323,7 @@ fn finish(ctx: &Ctx, raw: Raw, method: HexMethod) -> Result { if hexes.len() > ctx.opts.max_elements { return Err(Error::Limit(format!("the mesh has {} hexahedra; the limit is {}", hexes.len(), ctx.opts.max_elements))); } - modelcraft_model::check_coordinates(nodes.as_flattened())?; + fieldcraft_model::check_coordinates(nodes.as_flattened())?; let at = |nodes: &[P], i: u32| nodes.get(i as usize).copied().ok_or_else(|| Error::Failed(format!("node {i} out of range"))); // Boundary faces: hex faces used once. let mut uses: HashMap<[u32; 4], u32> = HashMap::with_capacity(hexes.len() * 3); @@ -548,6 +548,6 @@ fn mid_edge_nodes(ctx: &Ctx, nodes: &mut Vec

, hexes: &[[u32; 8]], boundary: & } out.push(m); } - modelcraft_model::check_coordinates(nodes.as_flattened())?; + fieldcraft_model::check_coordinates(nodes.as_flattened())?; Ok(out) } diff --git a/crates/mesh/src/join.rs b/crates/mesh/src/join.rs index fffe255..0f0ad48 100644 --- a/crates/mesh/src/join.rs +++ b/crates/mesh/src/join.rs @@ -5,7 +5,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; -use modelcraft_model::{ElementKind, Model}; +use fieldcraft_model::{ElementKind, Model}; use crate::vec3::{P, dot, sub}; use crate::{Error, Result}; diff --git a/crates/mesh/src/lib.rs b/crates/mesh/src/lib.rs index a6eb594..dab51ad 100644 --- a/crates/mesh/src/lib.rs +++ b/crates/mesh/src/lib.rs @@ -35,7 +35,7 @@ pub enum Error { #[error("invalid boundary surface: {0}")] Surface(surface::Defects), #[error(transparent)] - Model(#[from] modelcraft_model::Error), + Model(#[from] fieldcraft_model::Error), } pub type Result = std::result::Result; diff --git a/crates/mesh/src/planar.rs b/crates/mesh/src/planar.rs index 0460581..8a69cea 100644 --- a/crates/mesh/src/planar.rs +++ b/crates/mesh/src/planar.rs @@ -14,7 +14,7 @@ use std::collections::BTreeMap; -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use crate::quad::{QuadOptions, free_quad_mesh, free_tri_mesh}; use crate::size::{UniformSize, divide_polyline, edge_divisions, even_divisions, ideal_divisions}; diff --git a/crates/mesh/src/predicates/eft.rs b/crates/mesh/src/predicates/eft.rs index 857dd95..6627ea2 100644 --- a/crates/mesh/src/predicates/eft.rs +++ b/crates/mesh/src/predicates/eft.rs @@ -3,7 +3,7 @@ //! Each function returns `(x, y)` with `x = fl(a ∘ b)` and `y` the exact roundoff, so that //! `a ∘ b = x + y` exactly. They assume IEEE 754 binary64 with round-to-nearest-even, no //! intermediate extended precision and no fused multiply-add contraction; that is what Rust's -//! `f64` guarantees on every target ModelCraft builds for (x86_64 with SSE2, aarch64, wasm32): +//! `f64` guarantees on every target FieldCraft builds for (x86_64 with SSE2, aarch64, wasm32): //! rustc never contracts `a * b + c` into an FMA and does not use x87. No overflow or underflow //! may occur, which the coordinate domain of [`super::Point2`] / [`super::Point3`] guarantees. diff --git a/crates/mesh/src/quad/cdt.rs b/crates/mesh/src/quad/cdt.rs index e042dfd..3b0dea5 100644 --- a/crates/mesh/src/quad/cdt.rs +++ b/crates/mesh/src/quad/cdt.rs @@ -75,7 +75,7 @@ pub(crate) const CLEARANCE: f64 = 0.3; /// model's coordinate range. fn steiner_point(c: [f64; 2]) -> Option { let c = [snap_to_domain(c[0]), snap_to_domain(c[1])]; - if !(c[0].abs() <= modelcraft_model::MAX_COORD && c[1].abs() <= modelcraft_model::MAX_COORD) { + if !(c[0].abs() <= fieldcraft_model::MAX_COORD && c[1].abs() <= fieldcraft_model::MAX_COORD) { return None; } Point2::new(c[0], c[1]).ok() diff --git a/crates/mesh/src/quad/domain.rs b/crates/mesh/src/quad/domain.rs index 24b9123..a47ab5e 100644 --- a/crates/mesh/src/quad/domain.rs +++ b/crates/mesh/src/quad/domain.rs @@ -39,7 +39,7 @@ pub(crate) fn bbox(pts: impl IntoIterator) -> ([f64; 2], [f64; /// Admissible exact-predicate point from user coordinates (finite, within the model limit; tiny /// magnitudes rounded to zero). pub(crate) fn admissible(c: [f64; 2]) -> Result<[f64; 2]> { - modelcraft_model::check_coordinates(&c)?; + fieldcraft_model::check_coordinates(&c)?; Ok([snap_to_domain(c[0]), snap_to_domain(c[1])]) } diff --git a/crates/mesh/src/quad/free.rs b/crates/mesh/src/quad/free.rs index fdb3c1e..0b484f2 100644 --- a/crates/mesh/src/quad/free.rs +++ b/crates/mesh/src/quad/free.rs @@ -22,7 +22,7 @@ pub(crate) fn mesh(loops: &[Vec<[f64; 2]>], size: &dyn SizeField, opts: &QuadOpt if l.len() % 2 == 1 { return Err(Error::Invalid(format!( "loop {k} has an odd number ({}) of boundary points; an all-quadrilateral mesh needs an even number on every loop \ - (choose the edge divisions with modelcraft_mesh::size::loop_divisions or even_divisions)", + (choose the edge divisions with fieldcraft_mesh::size::loop_divisions or even_divisions)", l.len() ))); } diff --git a/crates/mesh/src/quad/tests.rs b/crates/mesh/src/quad/tests.rs index ffb0e12..4fe1863 100644 --- a/crates/mesh/src/quad/tests.rs +++ b/crates/mesh/src/quad/tests.rs @@ -511,7 +511,7 @@ fn planar_quad_metrics() { } #[test] -#[ignore = "timing and quality report: cargo test --release -p modelcraft-mesh quad_report -- --ignored --nocapture"] +#[ignore = "timing and quality report: cargo test --release -p fieldcraft-mesh quad_report -- --ignored --nocapture"] fn quad_report() { let cases: Vec<(&str, Vec>, f64)> = vec![ ("rectangle", vec![rect(0.0, 0.0, 10.0, 5.0, 20, 10)], 0.5), diff --git a/crates/mesh/src/quad/tfi.rs b/crates/mesh/src/quad/tfi.rs index 0885bed..321d49e 100644 --- a/crates/mesh/src/quad/tfi.rs +++ b/crates/mesh/src/quad/tfi.rs @@ -85,7 +85,7 @@ fn grid( let count = (nu + 1).checked_mul(nv + 1).filter(|&c| c <= MAX_GRID_NODES); let count = count.ok_or_else(|| Error::Limit(format!("a {nu} × {nv} grid exceeds {MAX_GRID_NODES} nodes")))?; for side in [bottom, right, top, left] { - modelcraft_model::check_coordinates(side.as_flattened())?; + fieldcraft_model::check_coordinates(side.as_flattened())?; } let corner = |s: &[[f64; D]], last: bool| if last { s.last().copied() } else { s.first().copied() }; let (p00, p10, p01, p11) = match (corner(bottom, false), corner(bottom, true), corner(top, false), corner(top, true)) { diff --git a/crates/mesh/src/quality/jacobian.rs b/crates/mesh/src/quality/jacobian.rs index d249b06..e5fbadb 100644 --- a/crates/mesh/src/quality/jacobian.rs +++ b/crates/mesh/src/quality/jacobian.rs @@ -1,7 +1,7 @@ //! The Jacobian determinant of a solid element as an exact polynomial, its certified sign and //! the element's exact volume. //! -//! The geometry map of every solid element ModelCraft knows is a polynomial on its reference +//! The geometry map of every solid element FieldCraft knows is a polynomial on its reference //! domain, so its Jacobian determinant is one too: constant for tet4, cubic for tet10, degree //! (2, 2) on triangle × segment for wedge6, degree (2, 2, 2) for hex8 and (5, 5, 5) for hex20. It //! is built here in Bernstein form ([`super::bernstein`]) and its sign is settled by bounds and @@ -22,7 +22,7 @@ //! - hex8/hex20: face 1-2-3-4, then 5-6-7-8 above it; mid-side nodes 9–12 on the edges of 1-2-3-4, //! 13–16 on those of 5-6-7-8, 17–20 on edges 1-5, 2-6, 3-7, 4-8. -use modelcraft_model::ElementKind; +use fieldcraft_model::ElementKind; use super::bernstein::{Bernstein, Factor}; use crate::{Error, Result}; diff --git a/crates/mesh/src/quality/mod.rs b/crates/mesh/src/quality/mod.rs index 6a7ba98..f16841c 100644 --- a/crates/mesh/src/quality/mod.rs +++ b/crates/mesh/src/quality/mod.rs @@ -11,7 +11,7 @@ //! - Hexahedron, wedge and quadrilateral shape: Knupp, *Algebraic mesh quality metrics*, SIAM J. //! Sci. Comput. 23(1):193–218, 2001. //! -//! Inputs are hostile: coordinates must be finite and within [`modelcraft_model::MAX_COORD`], +//! Inputs are hostile: coordinates must be finite and within [`fieldcraft_model::MAX_COORD`], //! and every metric has a defined value on degenerate and inverted elements. //! //! Linear tetrahedra take their validity from the exact `orient3d` ([`crate::predicates`]); the @@ -23,7 +23,7 @@ pub mod jacobian; mod report; pub(crate) mod shape; -use modelcraft_model::{ElementKind, check_coordinates}; +use fieldcraft_model::{ElementKind, check_coordinates}; pub use jacobian::{JacobianCheck, Validity}; pub use report::{ diff --git a/crates/mesh/src/quality/report.rs b/crates/mesh/src/quality/report.rs index e02b2ce..1800843 100644 --- a/crates/mesh/src/quality/report.rs +++ b/crates/mesh/src/quality/report.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet}; -use modelcraft_model::{Element, ElementKind, Model, check_coordinates}; +use fieldcraft_model::{Element, ElementKind, Model, check_coordinates}; use super::{ElementQuality, JacobianCheck, Metric, Validity, element_quality, element_validity, is_measured}; use crate::{Error, Result}; @@ -49,7 +49,7 @@ fn coordinates(model: &Model, label: u32, element: &Element) -> Result(model: &'a Model, labels: Option<&[u32]>) -> Result>() .into_iter() - .map(|l| model.element(*l).map(|e| (*l, e)).ok_or_else(|| Error::Model(modelcraft_model::Error::NotFound(format!("element {l}"))))) + .map(|l| model.element(*l).map(|e| (*l, e)).ok_or_else(|| Error::Model(fieldcraft_model::Error::NotFound(format!("element {l}"))))) .collect(), } } diff --git a/crates/mesh/src/quality/tests.rs b/crates/mesh/src/quality/tests.rs index cf34f4d..0e894f8 100644 --- a/crates/mesh/src/quality/tests.rs +++ b/crates/mesh/src/quality/tests.rs @@ -1,6 +1,6 @@ //! Analytic tests: every expected value is closed-form. -use modelcraft_model::{ElementKind, Model}; +use fieldcraft_model::{ElementKind, Model}; use super::*; diff --git a/crates/mesh/src/size.rs b/crates/mesh/src/size.rs index 32fcf37..1448cde 100644 --- a/crates/mesh/src/size.rs +++ b/crates/mesh/src/size.rs @@ -68,7 +68,7 @@ fn check_polyline(polyline: &[[f64; 3]]) -> Result<()> { if polyline.len() > MAX_POLYLINE_POINTS { return Err(Error::Limit(format!("polyline has {} points; the limit is {MAX_POLYLINE_POINTS}", polyline.len()))); } - modelcraft_model::check_coordinates(polyline.as_flattened())?; + fieldcraft_model::check_coordinates(polyline.as_flattened())?; Ok(()) } diff --git a/crates/mesh/src/surface/mod.rs b/crates/mesh/src/surface/mod.rs index 6e6d3df..3ec3454 100644 --- a/crates/mesh/src/surface/mod.rs +++ b/crates/mesh/src/surface/mod.rs @@ -4,7 +4,7 @@ //! tagged 3MF) into one that every volume mesher can rely on, or fails with the list of //! [`Defect`]s, each located by input triangle or vertex index and a position: //! -//! 1. **Coordinates and indices** are checked: finite, within [`modelcraft_model::MAX_COORD`], +//! 1. **Coordinates and indices** are checked: finite, within [`fieldcraft_model::MAX_COORD`], //! in range. //! 2. **Weld.** Vertices closer than a distance (relative to the bounding-box diagonal, or //! absolute: [`SurfaceOptions`]) become one, in input order (the earliest vertex of a cluster @@ -53,7 +53,7 @@ mod tests; use std::fmt; -use modelcraft_model::{MAX_COORD, MAX_ELEMENTS, MAX_NODES}; +use fieldcraft_model::{MAX_COORD, MAX_ELEMENTS, MAX_NODES}; use crate::predicates::Point3; use crate::vec3::{cross, norm, sub}; @@ -83,7 +83,7 @@ pub struct SurfaceOptions { pub weld_tolerance: f64, /// Absolute weld distance in model units, at least 0 (the combined distance may be at most /// [`MAX_RELATIVE_WELD`] of the bounding-box diagonal). A tessellation carries its own - /// (`modelcraft_geometry::SurfaceMesh::weld_distance`). + /// (`fieldcraft_geometry::SurfaceMesh::weld_distance`). pub weld_distance: f64, /// An edge inside one B-rep face is a feature when the normals of its two triangles differ /// by more than this angle, in degrees, in `(0, 180]` (180: face boundaries only). diff --git a/crates/mesh/tests/delaunay_bench.rs b/crates/mesh/tests/delaunay_bench.rs index 5e1c791..5de1dc0 100644 --- a/crates/mesh/tests/delaunay_bench.rs +++ b/crates/mesh/tests/delaunay_bench.rs @@ -1,10 +1,10 @@ //! Throughput of the Delaunay tetrahedralization on 1M random points and on a 100³ grid (every //! cell cospherical). Ignored by default; run with -//! `cargo test --release -p modelcraft-mesh --test delaunay_bench -- --ignored --nocapture`. +//! `cargo test --release -p fieldcraft-mesh --test delaunay_bench -- --ignored --nocapture`. use std::time::Instant; -use modelcraft_mesh::delaunay::Triangulation; +use fieldcraft_mesh::delaunay::Triangulation; struct Rng(u64); diff --git a/crates/mesh/tests/predicates_bench.rs b/crates/mesh/tests/predicates_bench.rs index 469d226..0a11f59 100644 --- a/crates/mesh/tests/predicates_bench.rs +++ b/crates/mesh/tests/predicates_bench.rs @@ -1,10 +1,10 @@ //! Throughput and filter hit rate of the exact predicates. Ignored by default; run with -//! `cargo test --release -p modelcraft-mesh --test predicates_bench -- --ignored --nocapture`. +//! `cargo test --release -p fieldcraft-mesh --test predicates_bench -- --ignored --nocapture`. use std::hint::black_box; use std::time::Instant; -use modelcraft_mesh::predicates::{ +use fieldcraft_mesh::predicates::{ GenericPoint, Lpi, Point3, Sign, generic_orient3d, insphere, insphere_filter, insphere_sos, orient3d, orient3d_filter, }; diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml index 3af3b4e..6e0fd75 100644 --- a/crates/model/Cargo.toml +++ b/crates/model/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-model" -description = "ModelCraft's solver-neutral finite element model: nodes, elements and their validation" +name = "fieldcraft-model" +description = "FieldCraft's solver-neutral finite element model: nodes, elements and their validation" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index 85fb301..0a1b232 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -314,7 +314,7 @@ impl Model { } /// A copy with the unit system and every definition but no mesh: no nodes, elements or - /// geometry tags. A `.modelcraft` file stores this part as text and the mesh in binary. + /// geometry tags. A `.fieldcraft` file stores this part as text and the mesh in binary. pub fn without_mesh(&self) -> Model { Model { nodes: BTreeMap::new(), diff --git a/crates/model/src/mesh.rs b/crates/model/src/mesh.rs index c1552cc..e8e7f2b 100644 --- a/crates/model/src/mesh.rs +++ b/crates/model/src/mesh.rs @@ -105,7 +105,7 @@ impl ElementKind { } } - /// The name used in commands and `.modelcraft` files (`hex8`, `tet10`, …). + /// The name used in commands and `.fieldcraft` files (`hex8`, `tet10`, …). pub fn name(self) -> &'static str { match self { ElementKind::Rod2 => "rod2", diff --git a/crates/model/tests/definition.rs b/crates/model/tests/definition.rs index 0178af5..c4c8c3a 100644 --- a/crates/model/tests/definition.rs +++ b/crates/model/tests/definition.rs @@ -1,7 +1,7 @@ //! A whole model definition on a tagged mesh: sets by geometry name survive a remesh, the check //! names what is missing, and the model round-trips through JSON. -use modelcraft_model::*; +use fieldcraft_model::*; /// A bar of `n` unit hexahedra along X, labels starting after `offset`, tagged as body `Bar` with /// faces `left` (x = 0) and `right` (x = n), and the vertex `tip` at (n, 0, 0). diff --git a/crates/parallel/Cargo.toml b/crates/parallel/Cargo.toml index d2cc1fe..1f8af28 100644 --- a/crates/parallel/Cargo.toml +++ b/crates/parallel/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-parallel" -description = "ModelCraft's deterministic data parallelism: fixed work partitions on a thread pool, results independent of the thread count" +name = "fieldcraft-parallel" +description = "FieldCraft's deterministic data parallelism: fixed work partitions on a thread pool, results independent of the thread count" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/parallel/src/lib.rs b/crates/parallel/src/lib.rs index 97344cc..0b43625 100644 --- a/crates/parallel/src/lib.rs +++ b/crates/parallel/src/lib.rs @@ -1,4 +1,4 @@ -//! Deterministic data parallelism for ModelCraft's CPU solvers. +//! Deterministic data parallelism for FieldCraft's CPU solvers. //! //! The solvers promise the same result bit for bit on every run, platform and thread count //! (AGENTS.md, Determinism). Threads keep that promise when every floating-point value is @@ -20,7 +20,7 @@ //! //! **Thread count.** [`with_threads`] runs a closure on a pool of that many threads (pools are //! cached; at most [`MAX_THREADS`]). Outside it, the helpers use the default pool: -//! [`ENV`] (`MODELCRAFT_THREADS`) when it holds a whole number from 1 to [`MAX_THREADS`], else +//! [`ENV`] (`FIELDCRAFT_THREADS`) when it holds a whole number from 1 to [`MAX_THREADS`], else //! the available cores. //! //! Rayon (MIT OR Apache-2.0) provides the pool (docs/adr/0001-libraries.md). @@ -31,7 +31,7 @@ pub const MAX_THREADS: usize = 256; /// The environment variable that sets the default thread count. -pub const ENV: &str = "MODELCRAFT_THREADS"; +pub const ENV: &str = "FIELDCRAFT_THREADS"; /// A thread count from text: a whole number from 1 to [`MAX_THREADS`]. pub fn parse_threads(text: &str) -> Option { @@ -123,7 +123,7 @@ mod pool { static DEFAULT: OnceLock>> = OnceLock::new(); fn build(threads: usize) -> Option> { - ThreadPoolBuilder::new().num_threads(threads).stack_size(STACK).thread_name(|i| format!("modelcraft-worker-{i}")).build().ok().map(Arc::new) + ThreadPoolBuilder::new().num_threads(threads).stack_size(STACK).thread_name(|i| format!("fieldcraft-worker-{i}")).build().ok().map(Arc::new) } fn cached(threads: usize) -> Option> { diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 01125aa..55d2009 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-render" -description = "ModelCraft display data: the visible faces, edges and nodes of a finite element model, an orbit camera, and a CPU rasterizer for headless snapshots (no GPU dependency)" +name = "fieldcraft-render" +description = "FieldCraft display data: the visible faces, edges and nodes of a finite element model, an orbit camera, and a CPU rasterizer for headless snapshots (no GPU dependency)" version.workspace = true edition.workspace = true license.workspace = true @@ -9,7 +9,7 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } +fieldcraft-model = { workspace = true } serde = { workspace = true } png = { workspace = true } diff --git a/crates/render/src/display.rs b/crates/render/src/display.rs index d3630d0..ca14fe8 100644 --- a/crates/render/src/display.rs +++ b/crates/render/src/display.rs @@ -12,7 +12,7 @@ //! //! The output is deterministic: elements come out in label order, edges sorted. -use modelcraft_model::{ElementKind, Model}; +use fieldcraft_model::{ElementKind, Model}; use crate::{Aabb3, Vec3}; diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index cc28809..86d721c 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -1,4 +1,4 @@ -//! Display data for ModelCraft, with no GPU dependency: what a finite element model looks like +//! Display data for FieldCraft, with no GPU dependency: what a finite element model looks like //! on screen ([`DisplayMesh`]: the visible faces, element edges, line elements and nodes), an //! orbit camera with perspective or orthographic projection, and a CPU rasterizer for headless //! snapshots. The desktop viewport draws the same [`Scene`] on the GPU. diff --git a/crates/solver/Cargo.toml b/crates/solver/Cargo.toml index 0c68a6b..860f48c 100644 --- a/crates/solver/Cargo.toml +++ b/crates/solver/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-solver" -description = "ModelCraft's implicit solver: assembly, boundary conditions, sparse Cholesky, linear static and frequency analysis" +name = "fieldcraft-solver" +description = "FieldCraft's implicit solver: assembly, boundary conditions, sparse Cholesky, linear static and frequency analysis" version.workspace = true edition.workspace = true license.workspace = true @@ -9,8 +9,8 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } -modelcraft-fem = { workspace = true } -modelcraft-parallel = { workspace = true } +fieldcraft-model = { workspace = true } +fieldcraft-fem = { workspace = true } +fieldcraft-parallel = { workspace = true } thiserror = { workspace = true } serde = { workspace = true } diff --git a/crates/solver/src/analysis.rs b/crates/solver/src/analysis.rs index 14d4592..1692d81 100644 --- a/crates/solver/src/analysis.rs +++ b/crates/solver/src/analysis.rs @@ -1,15 +1,15 @@ //! Linear static and frequency analysis of a model's step, and their results. -use modelcraft_fem::ElementType; -use modelcraft_fem::beam::BeamForces; -use modelcraft_fem::element::{Extrapolation, extrapolation}; -use modelcraft_fem::quadrature::Rule; -use modelcraft_fem::shell::ShellStress; -use modelcraft_fem::{DofKind, DofLayout, PointStress, von_mises}; -use modelcraft_model::{ElementKind, Model, Procedure, Resolved, Step}; +use fieldcraft_fem::ElementType; +use fieldcraft_fem::beam::BeamForces; +use fieldcraft_fem::element::{Extrapolation, extrapolation}; +use fieldcraft_fem::quadrature::Rule; +use fieldcraft_fem::shell::ShellStress; +use fieldcraft_fem::{DofKind, DofLayout, PointStress, von_mises}; +use fieldcraft_model::{ElementKind, Model, Procedure, Resolved, Step}; use serde::Serialize; -use modelcraft_parallel as parallel; +use fieldcraft_parallel as parallel; use crate::cholesky::{Factor, FactorError, UpperColumns}; use crate::constraints::{Budget, Reduction, coupling_rows}; diff --git a/crates/solver/src/cholesky.rs b/crates/solver/src/cholesky.rs index 984d0fd..54334fa 100644 --- a/crates/solver/src/cholesky.rs +++ b/crates/solver/src/cholesky.rs @@ -34,7 +34,7 @@ //! zero-stiffness direction); the factorization stops and names the column: the first such column //! in the postorder, whichever thread meets it. -use modelcraft_parallel as parallel; +use fieldcraft_parallel as parallel; use crate::{Error, Result}; diff --git a/crates/solver/src/constraints.rs b/crates/solver/src/constraints.rs index 0937b13..ce49732 100644 --- a/crates/solver/src/constraints.rs +++ b/crates/solver/src/constraints.rs @@ -51,9 +51,9 @@ use std::collections::BTreeMap; -use modelcraft_fem::dense::inverse3; -use modelcraft_fem::{DofKind, DofSet}; -use modelcraft_model::{CouplingKind, Model, Resolved}; +use fieldcraft_fem::dense::inverse3; +use fieldcraft_fem::{DofKind, DofSet}; +use fieldcraft_model::{CouplingKind, Model, Resolved}; use crate::sparse::Csr; use crate::system::Mesh; diff --git a/crates/solver/src/discrete.rs b/crates/solver/src/discrete.rs index fe21b42..29bd07a 100644 --- a/crates/solver/src/discrete.rs +++ b/crates/solver/src/discrete.rs @@ -1,6 +1,6 @@ //! Point masses, rotary inertia, springs and dashpots in the implicit system. //! -//! The model resolves each definition to its nodes ([`modelcraft_model::Resolved::masses`] and +//! The model resolves each definition to its nodes ([`fieldcraft_model::Resolved::masses`] and //! `springs`); here each copy is turned into global degrees of freedom and its matrix: //! //! - a point mass adds m_x, m_y, m_z on the node's translations and its inertia tensor J (3 × 3, @@ -12,11 +12,11 @@ //! - a dashpot has the same form on velocities; static and frequency steps have no velocities, so //! they ignore dashpots with a warning (damping enters dynamic steps). //! -//! What each copy needs at its nodes ([`needs`]) goes into the mesh's [`modelcraft_fem::DofLayout`] +//! What each copy needs at its nodes ([`needs`]) goes into the mesh's [`fieldcraft_fem::DofLayout`] //! like an element's declared kinds; [`dof_of`] then finds its unknowns there. -use modelcraft_fem::{DofKind, DofSet}; -use modelcraft_model::{Resolved, ResolvedSpring}; +use fieldcraft_fem::{DofKind, DofSet}; +use fieldcraft_model::{Resolved, ResolvedSpring}; use crate::sparse::{Csr, add_entry}; use crate::system::Mesh; @@ -103,7 +103,7 @@ impl Discrete { .filter(|d| m.mass[*d] != 0.0 && DofKind::translation(*d).is_some_and(|k| planar.contains(k))) .map(|d| dof_of(mesh, m.node, d).map(|dof| (dof, m.mass[d])).ok_or_else(|| missing("point mass", &m.name, m.node, d))) .collect::>()?; - let inertia = modelcraft_model::discrete::inertia_matrix(m.inertia); + let inertia = fieldcraft_model::discrete::inertia_matrix(m.inertia); let rotations = if m.inertia.iter().any(|c| *c != 0.0) { (3..6).map(|d| dof_of(mesh, m.node, d).ok_or_else(|| missing("point mass", &m.name, m.node, d))).collect::>()? } else { diff --git a/crates/solver/src/eigen.rs b/crates/solver/src/eigen.rs index 709a147..b25dc93 100644 --- a/crates/solver/src/eigen.rs +++ b/crates/solver/src/eigen.rs @@ -286,7 +286,7 @@ pub fn lanczos(op: &impl Operator, shift: f64, wanted: Wanted, block: usize, max // W = Op Q_j. let mut w: Vec> = Vec::with_capacity(b); // The block's solves are independent: one task each. - for y in modelcraft_parallel::map(mcur.len(), |i| { + for y in fieldcraft_parallel::map(mcur.len(), |i| { let mut y = mcur.get(i).cloned().unwrap_or_default(); op.solve(&mut y).map(|()| y) }) { diff --git a/crates/solver/src/heat.rs b/crates/solver/src/heat.rs index f2dd583..11f489e 100644 --- a/crates/solver/src/heat.rs +++ b/crates/solver/src/heat.rs @@ -1,9 +1,9 @@ //! Heat transfer: steady and transient conduction with prescribed temperatures, surface fluxes, //! convection and internal heat sources. //! -//! Every node of the solid elements carries one temperature ([`modelcraft_fem::heat::HEAT_DOFS`]). +//! Every node of the solid elements carries one temperature ([`fieldcraft_fem::heat::HEAT_DOFS`]). //! The conductivity K (with the convection films) and capacity C are assembled on the node -//! graph in element label order ([`modelcraft_fem::heat`]); prescribed temperatures are +//! graph in element label order ([`fieldcraft_fem::heat`]); prescribed temperatures are //! eliminated, the free ones numbered by nested dissection and solved by sparse Cholesky. //! //! - **Steady**: K T = f. @@ -16,10 +16,10 @@ //! The heat flux −k∇T is reported at the integration points and averaged at the nodes; the heat //! flow at prescribed temperatures is the reaction K T − f (C Ṫ included in transient steps). -use modelcraft_fem::element::{Extrapolation, extrapolation}; -use modelcraft_fem::heat::{Conduction, PointFlux, face_film, face_weights}; -use modelcraft_fem::{DofLayout, DofSet}; -use modelcraft_model::{ElementKind, Load, Model, Procedure, Resolved, SectionKind, Step, TEMPERATURE_DOF}; +use fieldcraft_fem::element::{Extrapolation, extrapolation}; +use fieldcraft_fem::heat::{Conduction, PointFlux, face_film, face_weights}; +use fieldcraft_fem::{DofLayout, DofSet}; +use fieldcraft_model::{ElementKind, Load, Model, Procedure, Resolved, SectionKind, Step, TEMPERATURE_DOF}; use crate::cholesky::{Factor, FactorError, UpperColumns}; use crate::ordering::nested_dissection; diff --git a/crates/solver/src/lib.rs b/crates/solver/src/lib.rs index f11ead9..41e9737 100644 --- a/crates/solver/src/lib.rs +++ b/crates/solver/src/lib.rs @@ -1,9 +1,9 @@ -//! ModelCraft's implicit solver: linear static and frequency analysis of a model's steps. +//! FieldCraft's implicit solver: linear static and frequency analysis of a model's steps. //! -//! The pipeline, for one step of a [`modelcraft_model::Model`]: +//! The pipeline, for one step of a [`fieldcraft_model::Model`]: //! 1. **System** ([`system`]): the model is resolved; every section's elements become solid //! elements with their material's elasticity and the section's formulation -//! ([`modelcraft_fem`] kernels); the step's boundary conditions give the prescribed degrees of +//! ([`fieldcraft_fem`] kernels); the step's boundary conditions give the prescribed degrees of //! freedom, its loads the load vector (forces and tractions as the model distributes them, //! consistent pressure loads, gravity and body forces). //! 2. **Assembly** ([`sparse`]) on the node graph, elements in label order. @@ -16,7 +16,7 @@ //! kind of factorization. //! //! Element matrices and stresses, the factorization's subtrees and dense tiles, the Lanczos -//! block solves and matrix products run on threads ([`modelcraft_parallel`]), but every number +//! block solves and matrix products run on threads ([`fieldcraft_parallel`]), but every number //! is computed by one task in an order fixed by the model alone and sums are taken in a fixed //! order, so the same model gives the same numbers bit for bit on any number of threads and on //! every platform; sizes and iterations are capped by [`Limits`]. @@ -44,14 +44,14 @@ pub use heat::{HeatResult, solve_heat}; #[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum Error { #[error(transparent)] - Model(#[from] modelcraft_model::Error), + Model(#[from] fieldcraft_model::Error), #[error("invalid input: {0}")] Invalid(String), #[error("not supported yet: {0}")] NotSupported(String), /// An element kernel failed (inverted geometry, a formulation it lacks, …). #[error("element {label}: {source}")] - Element { label: u32, source: modelcraft_fem::Error }, + Element { label: u32, source: fieldcraft_fem::Error }, /// The stiffness (or shifted matrix) is singular. #[error("{0}")] Singular(String), @@ -68,7 +68,7 @@ pub type Result = std::result::Result; impl Error { /// A kernel error, naming the element. - pub fn element(label: u32, source: modelcraft_fem::Error) -> Error { + pub fn element(label: u32, source: fieldcraft_fem::Error) -> Error { Error::Element { label, source } } } diff --git a/crates/solver/src/sparse.rs b/crates/solver/src/sparse.rs index daa9cbc..7ab54d9 100644 --- a/crates/solver/src/sparse.rs +++ b/crates/solver/src/sparse.rs @@ -7,7 +7,7 @@ //! label, then the element's own node order), so assembled values are the same bit for bit on //! every run and platform. -use modelcraft_fem::{DofLayout, DofSet}; +use fieldcraft_fem::{DofLayout, DofSet}; use crate::{Error, Result}; @@ -34,7 +34,7 @@ impl Csr { /// Rows are computed in parallel blocks; each row's sum runs in column order. pub fn mul(&self, x: &[f64], y: &mut [f64]) { let n = self.n.min(y.len()); - modelcraft_parallel::chunks_mut(y.get_mut(..n).unwrap_or_default(), ROWS, |b, block| { + fieldcraft_parallel::chunks_mut(y.get_mut(..n).unwrap_or_default(), ROWS, |b, block| { for (i, yr) in block.iter_mut().enumerate() { let (cols, vals) = self.row(b * ROWS + i); *yr = cols.iter().zip(vals).map(|(c, v)| v * x.get(*c).copied().unwrap_or(0.0)).sum(); @@ -130,7 +130,7 @@ impl NodeGraph { } } -/// Most kinds a node carries ([`modelcraft_fem::DofKind::ALL`]). +/// Most kinds a node carries ([`fieldcraft_fem::DofKind::ALL`]). const MAX_KINDS: usize = 8; /// Where node b's block lies in node a's rows of a matrix built by [`NodeGraph::zero_matrix`], diff --git a/crates/solver/src/system.rs b/crates/solver/src/system.rs index 0e53ef7..b7557cc 100644 --- a/crates/solver/src/system.rs +++ b/crates/solver/src/system.rs @@ -7,17 +7,17 @@ //! translations for solids, Ux and Uy for 2D elements), nodes by label, then six per coupled reference point (points by //! name): translations along X, Y, Z and rotations about them. -use modelcraft_fem::beam::{Beam, BeamSection}; -use modelcraft_fem::dense::Matrix; -use modelcraft_fem::element::{Formulation, Solid, ThermalStrain}; -use modelcraft_fem::plane::{Plane, Theory}; -use modelcraft_fem::shell::{Shell, ShellSection}; -use modelcraft_fem::truss::{Truss, young_modulus}; -use modelcraft_fem::{DofKind, DofLayout, DofSet, Elasticity, ElementDofs, PointStress, elasticity, face, plane}; -use modelcraft_model::nodal::edge_pressure; +use fieldcraft_fem::beam::{Beam, BeamSection}; +use fieldcraft_fem::dense::Matrix; +use fieldcraft_fem::element::{Formulation, Solid, ThermalStrain}; +use fieldcraft_fem::plane::{Plane, Theory}; +use fieldcraft_fem::shell::{Shell, ShellSection}; +use fieldcraft_fem::truss::{Truss, young_modulus}; +use fieldcraft_fem::{DofKind, DofLayout, DofSet, Elasticity, ElementDofs, PointStress, elasticity, face, plane}; +use fieldcraft_model::nodal::edge_pressure; use std::collections::BTreeSet; -use modelcraft_model::{Elastic, ElementKind, Integration, Load, Model, Resolved, SectionKind, Step, Target, centrifugal_acceleration}; +use fieldcraft_model::{Elastic, ElementKind, Integration, Load, Model, Resolved, SectionKind, Step, Target, centrifugal_acceleration}; use crate::analysis::MassKind; use crate::{Error, Result}; @@ -56,21 +56,21 @@ impl Kernel<'_> { } } - pub fn stiffness(&self, d: &Elasticity) -> modelcraft_fem::Result { + pub fn stiffness(&self, d: &Elasticity) -> fieldcraft_fem::Result { match self { Kernel::Solid(s) => s.stiffness(d), Kernel::Plane(p) => p.stiffness(d), } } - pub fn mass(&self, density: f64) -> modelcraft_fem::Result { + pub fn mass(&self, density: f64) -> fieldcraft_fem::Result { match self { Kernel::Solid(s) => s.mass(density), Kernel::Plane(p) => p.mass(density), } } - pub fn lumped_mass(&self, density: f64) -> modelcraft_fem::Result> { + pub fn lumped_mass(&self, density: f64) -> fieldcraft_fem::Result> { match self { Kernel::Solid(s) => s.lumped_mass(density), Kernel::Plane(p) => p.lumped_mass(density), @@ -78,7 +78,7 @@ impl Kernel<'_> { } /// Consistent nodal forces of a uniform force per volume, as 3-vectors (Z zero for 2D). - pub fn body_force(&self, b: [f64; 3]) -> modelcraft_fem::Result> { + pub fn body_force(&self, b: [f64; 3]) -> fieldcraft_fem::Result> { match self { Kernel::Solid(s) => s.body_force(b), Kernel::Plane(p) => Ok(p.body_force_field(|_| b)?.into_iter().map(|v| [v[0], v[1], 0.0]).collect()), @@ -86,7 +86,7 @@ impl Kernel<'_> { } /// Consistent nodal forces of a force per volume b(x), as 3-vectors (Z zero for 2D). - pub fn body_force_field(&self, b: impl Fn([f64; 3]) -> [f64; 3]) -> modelcraft_fem::Result> { + pub fn body_force_field(&self, b: impl Fn([f64; 3]) -> [f64; 3]) -> fieldcraft_fem::Result> { match self { Kernel::Solid(s) => s.body_force_field(b), Kernel::Plane(p) => Ok(p.body_force_field(b)?.into_iter().map(|v| [v[0], v[1], 0.0]).collect()), @@ -95,7 +95,7 @@ impl Kernel<'_> { /// Integration-point stresses for nodal displacements given as 3-vectors, with a thermal /// strain when given. - pub fn stresses_with(&self, d: &Elasticity, u: &[[f64; 3]], thermal: Option<&ThermalStrain>) -> modelcraft_fem::Result> { + pub fn stresses_with(&self, d: &Elasticity, u: &[[f64; 3]], thermal: Option<&ThermalStrain>) -> fieldcraft_fem::Result> { match self { Kernel::Solid(s) => s.stresses_with(d, u, thermal), Kernel::Plane(p) => p.stresses_with(d, &u.iter().map(|v| [v[0], v[1]]).collect::>(), thermal), @@ -103,21 +103,21 @@ impl Kernel<'_> { } /// The equivalent nodal forces of a thermal strain, as 3-vectors (Z zero for 2D). - pub fn thermal_load(&self, d: &Elasticity, thermal: &ThermalStrain) -> modelcraft_fem::Result> { + pub fn thermal_load(&self, d: &Elasticity, thermal: &ThermalStrain) -> fieldcraft_fem::Result> { match self { Kernel::Solid(s) => s.thermal_load(d, thermal), Kernel::Plane(p) => Ok(p.thermal_load(d, thermal)?.into_iter().map(|v| [v[0], v[1], 0.0]).collect()), } } - pub fn rule(&self) -> modelcraft_fem::quadrature::Rule { + pub fn rule(&self) -> fieldcraft_fem::quadrature::Rule { match self { Kernel::Solid(s) => s.rule(), Kernel::Plane(p) => p.rule(), } } - pub fn ty(&self) -> &'static modelcraft_fem::ElementType { + pub fn ty(&self) -> &'static fieldcraft_fem::ElementType { match self { Kernel::Solid(s) => s.ty, Kernel::Plane(p) => p.ty, @@ -217,7 +217,7 @@ impl Structural { } impl StructuralKernel { - pub fn stiffness(&self) -> modelcraft_fem::Result { + pub fn stiffness(&self) -> fieldcraft_fem::Result { match self { StructuralKernel::Shell(s) => s.stiffness(), StructuralKernel::Beam(b) => Ok(b.stiffness()), @@ -225,7 +225,7 @@ impl StructuralKernel { } /// The consistent or lumped mass over every element degree of freedom. - pub fn mass(&self, density: f64, kind: MassKind) -> modelcraft_fem::Result { + pub fn mass(&self, density: f64, kind: MassKind) -> fieldcraft_fem::Result { Ok(match (self, kind) { (StructuralKernel::Shell(s), MassKind::Consistent) => s.mass(density)?, (StructuralKernel::Shell(s), MassKind::Lumped) => { @@ -242,7 +242,7 @@ impl StructuralKernel { } /// Consistent nodal forces and moments of a uniform force per volume, six per node. - pub fn body_force(&self, b: [f64; 3]) -> modelcraft_fem::Result> { + pub fn body_force(&self, b: [f64; 3]) -> fieldcraft_fem::Result> { match self { StructuralKernel::Shell(s) => s.body_force(b), StructuralKernel::Beam(beam) => Ok(beam.body_force(b)), @@ -405,8 +405,8 @@ impl Mesh { ))); } let set = resolved.sets.get(§ion.set).ok_or_else(|| Error::Invalid(format!("set `{}` is not resolved", section.set)))?; - let bad = |e: modelcraft_fem::Error| match e { - modelcraft_fem::Error::NotSupported(m) => Error::NotSupported(format!("section `{name}`: {m}")), + let bad = |e: fieldcraft_fem::Error| match e { + fieldcraft_fem::Error::NotSupported(m) => Error::NotSupported(format!("section `{name}`: {m}")), e => Error::Invalid(format!("section `{name}`, material `{}`: {e}", section.material)), }; let shared = match §ion.kind { @@ -483,7 +483,7 @@ impl Mesh { layout.element(&e.2, e.7); } for e in &trusses { - layout.element(&e.2, modelcraft_fem::ElementDofs::new(translations)); + layout.element(&e.2, fieldcraft_fem::ElementDofs::new(translations)); } for e in &structural { layout.element(&e.2, ElementDofs::new(Structural::KINDS)); @@ -705,7 +705,7 @@ pub fn loads(model: &Model, resolved: &Resolved, mesh: &Mesh, step: &Step, activ let xyz: Vec<[f64; 3]> = idx.iter().filter_map(|i| mesh.xyz.get(*i).copied()).collect(); let theory = e.theory.ok_or_else(|| Error::Internal("2D element without a theory".into()))?; let forces = edge_pressure(&xyz, theory.extent(), *magnitude) - .map_err(|err| Error::element(g.element, modelcraft_fem::Error::Invalid(err.to_string())))?; + .map_err(|err| Error::element(g.element, fieldcraft_fem::Error::Invalid(err.to_string())))?; for (i, v) in idx.iter().zip(forces) { add(&mut f, &translations(mesh, *i)?, v); } diff --git a/crates/solver/tests/benchmarks.rs b/crates/solver/tests/benchmarks.rs index 1c5e046..793a7a5 100644 --- a/crates/solver/tests/benchmarks.rs +++ b/crates/solver/tests/benchmarks.rs @@ -17,11 +17,11 @@ //! Both are modelled on one symmetric part (a quarter of the roof, an eighth of the cylinder) //! with quad4 (MITC4) shells on a structured mesh whose quadrilaterals are exactly flat. -use modelcraft_model::{ +use fieldcraft_model::{ BoundaryCondition, Distribution, Elastic, ElementKind, Formulation, Load, Material, Model, Procedure, Section, SectionKind, Set, Step, Symmetry, Target, }; -use modelcraft_solver::{Limits, solve_static}; +use fieldcraft_solver::{Limits, solve_static}; /// A cylindrical shell patch: x from 0 to `length`, angle φ from 0 (top, z = R) to `arc`, /// y = R sin φ, z = R cos φ; n × n quad4 cells. Returns the model and the node at (x, φ). diff --git a/crates/solver/tests/common/mod.rs b/crates/solver/tests/common/mod.rs index 5db0eb9..6715034 100644 --- a/crates/solver/tests/common/mod.rs +++ b/crates/solver/tests/common/mod.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; -use modelcraft_model::{ +use fieldcraft_model::{ BoundaryCondition, Coupling, CouplingKind, Dofs, Elastic, ElementFace, ElementKind, Formulation, Integration, Load, Material, Model, Procedure, ReferencePoint, Section, SectionKind, Set, Step, Symmetry, Target, }; diff --git a/crates/solver/tests/couplings.rs b/crates/solver/tests/couplings.rs index b3d5368..0309f28 100644 --- a/crates/solver/tests/couplings.rs +++ b/crates/solver/tests/couplings.rs @@ -6,8 +6,8 @@ mod common; use std::f64::consts::PI; use common::*; -use modelcraft_model::{BoundaryCondition, CouplingKind, Distribution, ElementKind, Load, Procedure, Symmetry, Target}; -use modelcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static}; +use fieldcraft_model::{BoundaryCondition, CouplingKind, Distribution, ElementKind, Load, Procedure, Symmetry, Target}; +use fieldcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static}; const ALL: [u8; 6] = [1, 2, 3, 4, 5, 6]; @@ -221,7 +221,7 @@ fn tube(nr: usize, nt: usize, nz: usize, ri: f64, ro: f64, length: f64) -> Grid .filter(|(_, p)| near(p[1], 1.0)) .map(|(l, p)| (*l, *g.param.iter().find(|(_, o)| near(o[1], 0.0) && near(o[0], p[0]) && near(o[2], p[2])).unwrap().0)) .collect(); - let elements: Vec<(u32, modelcraft_model::Element)> = g.model.elements().iter().map(|(l, e)| (*l, e.clone())).collect(); + let elements: Vec<(u32, fieldcraft_model::Element)> = g.model.elements().iter().map(|(l, e)| (*l, e.clone())).collect(); for (label, e) in elements { let nodes: Vec = e.nodes.iter().map(|n| seam.iter().find(|(a, _)| a == n).map_or(*n, |(_, b)| *b)).collect(); g.model.set_element(label, e.kind, nodes).unwrap(); @@ -308,7 +308,7 @@ fn coupling_errors_name_the_problem() { g.load("m", moment(rp("Tip"), [1.0, 0.0, 0.0])); assert!(err(&g).contains("rotation about X is not coupled"), "{}", err(&g)); - let loose = |g: &mut Grid| g.model.add_reference_point("Loose", modelcraft_model::ReferencePoint { xyz: [3.0, 0.0, 0.0], label: 0 }).unwrap(); + let loose = |g: &mut Grid| g.model.add_reference_point("Loose", fieldcraft_model::ReferencePoint { xyz: [3.0, 0.0, 0.0], label: 0 }).unwrap(); // A force on a reference point nothing couples. let mut g = base(); diff --git a/crates/solver/tests/hybrid.rs b/crates/solver/tests/hybrid.rs index b91600b..30f3cd0 100644 --- a/crates/solver/tests/hybrid.rs +++ b/crates/solver/tests/hybrid.rs @@ -6,8 +6,8 @@ mod common; use common::*; -use modelcraft_model::*; -use modelcraft_solver::{Limits, solve_static}; +use fieldcraft_model::*; +use fieldcraft_solver::{Limits, solve_static}; const NU: f64 = 0.4999; diff --git a/crates/solver/tests/one_d.rs b/crates/solver/tests/one_d.rs index ac099b0..30e4265 100644 --- a/crates/solver/tests/one_d.rs +++ b/crates/solver/tests/one_d.rs @@ -8,15 +8,15 @@ //! bars, P / (1 + 2 cos³θ) in the vertical one. //! - rod3: constant-stress patch with off-centre middle nodes; a bar hanging under its own //! weight (quadratic displacement, exact at the nodes); curved members are checked in -//! `modelcraft_fem::truss` (a ring's radial expansion gives the hoop strain δ/R exactly). +//! `fieldcraft_fem::truss` (a ring's radial expansion gives the hoop strain δ/R exactly). //! - Spring–mass ω = √(k/m), springs along three skew directions, the two-mass chain, a bar with //! a tip mass ω = √(EA/(LM)), the torsional pendulum ω = √(k_t/J). use std::f64::consts::PI; -use modelcraft_fem::truss::axial; -use modelcraft_model::*; -use modelcraft_solver::{FrequencyOptions, FrequencyResult, Limits, MassKind, StaticResult, solve_frequency, solve_static}; +use fieldcraft_fem::truss::axial; +use fieldcraft_model::*; +use fieldcraft_solver::{FrequencyOptions, FrequencyResult, Limits, MassKind, StaticResult, solve_frequency, solve_static}; const ZERO_Z: [Option; 6] = [None, None, Some(0.0), None, None, None]; diff --git a/crates/solver/tests/plane.rs b/crates/solver/tests/plane.rs index 5fda0b5..a1177d7 100644 --- a/crates/solver/tests/plane.rs +++ b/crates/solver/tests/plane.rs @@ -6,11 +6,11 @@ use std::collections::BTreeMap; use std::f64::consts::PI; -use modelcraft_model::{ +use fieldcraft_model::{ BoundaryCondition, Elastic, ElementEdge, ElementKind, Formulation, Integration, Load, Material, Model, Procedure, Section, SectionKind, Set, Step, Symmetry, Target, }; -use modelcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static}; +use fieldcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static}; /// A structured 2D mesh of `n` cells on [0, 1]², mapped by `map`; nodes on the doubled lattice so /// quadratic elements take their mid-side nodes from the map. @@ -540,15 +540,15 @@ fn two_dimensional_results_are_deterministic_and_errors_name_the_problem() { assert_eq!(g.solve(), g.solve()); // A coupling is not supported in 2D yet. let mut g = make(); - g.model.add_reference_point("RP", modelcraft_model::ReferencePoint { xyz: [0.0, 2.0, 0.0], label: 0 }).unwrap(); + g.model.add_reference_point("RP", fieldcraft_model::ReferencePoint { xyz: [0.0, 2.0, 0.0], label: 0 }).unwrap(); g.model .add_coupling( "c", - modelcraft_model::Coupling { + fieldcraft_model::Coupling { reference_point: "RP".into(), set: "bottom".into(), - kind: modelcraft_model::CouplingKind::Kinematic, - dofs: modelcraft_model::Dofs::all(), + kind: fieldcraft_model::CouplingKind::Kinematic, + dofs: fieldcraft_model::Dofs::all(), }, ) .unwrap(); @@ -571,8 +571,8 @@ fn two_dimensional_results_are_deterministic_and_errors_name_the_problem() { /// Give the material an expansion coefficient and heat every node by `dt` above the reference. fn heat(g: &mut Grid, alpha: f64, dt: f64) { let mut m = g.model.material("Mat").unwrap().clone(); - m.thermal = Some(modelcraft_model::Thermal { - expansion: Some(modelcraft_model::Directional::Isotropic(alpha)), + m.thermal = Some(fieldcraft_model::Thermal { + expansion: Some(fieldcraft_model::Directional::Isotropic(alpha)), reference_temperature: 20.0, ..Default::default() }); diff --git a/crates/solver/tests/structural.rs b/crates/solver/tests/structural.rs index 82c9eef..d45c2e1 100644 --- a/crates/solver/tests/structural.rs +++ b/crates/solver/tests/structural.rs @@ -9,11 +9,11 @@ use std::collections::BTreeMap; -use modelcraft_model::{ +use fieldcraft_model::{ BeamProfile, BoundaryCondition, Distribution, Elastic, ElementFace, ElementKind, Formulation, Load, Material, Model, Procedure, Section, SectionKind, Set, Step, Target, }; -use modelcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static}; +use fieldcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static}; /// A model under construction: nodes and elements numbered from 1, one material, one step. struct Build { @@ -343,7 +343,7 @@ fn cantilever_beam_deflection_is_exact() { // A stocky I beam, so shear deformation matters: δ = P L³/(3 E I) + P L/(G A_s) in each plane. let profile = BeamProfile::I { height: 200.0, width: 100.0, web: 6.0, flange: 10.0 }; let length: f64 = 1000.0; - let p = modelcraft_fem::beam::properties(&profile, 0.3); + let p = fieldcraft_fem::beam::properties(&profile, 0.3); let (e, g) = (210e3, 210e3 / 2.6); for n in [1, 4] { for (along_y, name) in [(true, "weak"), (false, "strong")] { @@ -377,7 +377,7 @@ fn cantilever_beam_frequencies() { // Euler–Bernoulli ω_k = (β_k L)² √(E I / (ρ A L⁴)). let profile = BeamProfile::Pipe { radius: 10.0, thickness: 1.0 }; let length: f64 = 10000.0; - let p = modelcraft_fem::beam::properties(&profile, 0.3); + let p = fieldcraft_fem::beam::properties(&profile, 0.3); let base = (210e3 * p.iy / (7.85e-9 * p.area * length.powi(4))).sqrt() / (2.0 * std::f64::consts::PI); let beta = [1.875_104_068_711_961, 4.694_091_132_974_175, 7.854_757_438_237_613]; for mass in [MassKind::Consistent, MassKind::Lumped] { @@ -443,7 +443,7 @@ fn portal_frame_matches_the_slope_deflection_solution() { b.force("push", vec![n as u32 + 1], [load, 0.0, 0.0]); let r = b.solve(); let g = e / 2.6; - let (pc, pb) = (modelcraft_fem::beam::properties(&column, 0.3), modelcraft_fem::beam::properties(&girder, 0.3)); + let (pc, pb) = (fieldcraft_fem::beam::properties(&column, 0.3), fieldcraft_fem::beam::properties(&girder, 0.3)); let (phi_c, phi_b) = (12.0 * e * pc.iy / (g * pc.shear_z * h * h), 12.0 * e * pb.iy / (g * pb.shear_z * l * l)); let (a, bb) = (e * pc.iy / (h * (1.0 + phi_c)), e * pb.iy / (l * (1.0 + phi_b))); let c = 6.0 * a / (h * (a * (4.0 + phi_c) + 6.0 * bb)); @@ -499,7 +499,7 @@ fn rods_continue_a_solid_column() { } let rods: Vec<_> = r.elements.iter().filter(|e| e.kind == ElementKind::Rod2).collect(); assert_eq!(rods.len(), 4); - assert!(rods.iter().flat_map(|e| &e.nodal).all(|s| rel(modelcraft_fem::von_mises(s), 4.0) < 1e-12), "{rods:?}"); + assert!(rods.iter().flat_map(|e| &e.nodal).all(|s| rel(fieldcraft_fem::von_mises(s), 4.0) < 1e-12), "{rods:?}"); // Solid nodes have no rotations. assert!(r.rotation.iter().all(|x| *x == [0.0; 3])); } diff --git a/crates/solver/tests/thermal.rs b/crates/solver/tests/thermal.rs index 710f702..d7568e7 100644 --- a/crates/solver/tests/thermal.rs +++ b/crates/solver/tests/thermal.rs @@ -7,8 +7,8 @@ mod common; use common::*; -use modelcraft_model::*; -use modelcraft_solver::{Limits, solve_heat, solve_static}; +use fieldcraft_model::*; +use fieldcraft_solver::{Limits, solve_heat, solve_static}; const K: f64 = 45.0; @@ -26,7 +26,7 @@ fn hold(g: &mut Grid, name: &str, value: f64, f: impl Fn([f64; 3]) -> bool) { g.bc(name, BoundaryCondition::Temperature { target: Target::Set(name.into()), value }); } -fn temperature_at(r: &modelcraft_solver::HeatResult, label: u32) -> f64 { +fn temperature_at(r: &fieldcraft_solver::HeatResult, label: u32) -> f64 { r.temperature()[r.nodes.binary_search(&label).unwrap()] } @@ -384,7 +384,7 @@ fn heat_steps_refuse_what_they_cannot_solve() { /// One rod3 or two rod2 along X over [0, `len`], heated by `dt`, fixed at x = 0, the other /// nodes held in Y and Z, and the end held along X too when `held`. -fn heated_rod(kind: ElementKind, len: f64, alpha: [f64; 3], dt: f64, held: bool) -> modelcraft_solver::StaticResult { +fn heated_rod(kind: ElementKind, len: f64, alpha: [f64; 3], dt: f64, held: bool) -> fieldcraft_solver::StaticResult { let mut m = Model::default(); for (label, x) in [(1, 0.0), (2, len), (3, 0.5 * len)] { m.set_node(label, [x, 0.0, 0.0]).unwrap(); diff --git a/crates/solver/tests/verification.rs b/crates/solver/tests/verification.rs index 7f53709..c5c0db9 100644 --- a/crates/solver/tests/verification.rs +++ b/crates/solver/tests/verification.rs @@ -6,8 +6,8 @@ mod common; use std::f64::consts::PI; use common::*; -use modelcraft_model::{BoundaryCondition, ElementKind, Load, Procedure, Symmetry, Target}; -use modelcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static}; +use fieldcraft_model::{BoundaryCondition, ElementKind, Load, Procedure, Symmetry, Target}; +use fieldcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static}; fn static_solve(g: &Grid) -> StaticResult { solve_static(&g.model, Some("Step"), &Limits::default()).unwrap() @@ -50,7 +50,7 @@ fn patch_test_every_element() { let x = g.model.node(label).unwrap().xyz; let u = field(x); let set = format!("b{i}"); - g.model.add_set(&set, modelcraft_model::Set { nodes: vec![label], ..Default::default() }).unwrap(); + g.model.add_set(&set, fieldcraft_model::Set { nodes: vec![label], ..Default::default() }).unwrap(); g.bc( &set, BoundaryCondition::Displacement { target: Target::Set(set.clone()), values: [Some(u[0]), Some(u[1]), Some(u[2]), None, None, None] }, @@ -132,7 +132,7 @@ fn gravity_and_body_force_totals() { /// Cantilever L × b × h, clamped at x = 0, a shear traction on the free end. Timoshenko /// deflection δ = P L³ / (3 E I) + P L / (κ G A), κ = 5/6, with ν = 0 so the clamp adds no /// lateral restraint. -fn cantilever_tip(kind: ElementKind, formulation: modelcraft_model::Formulation, n: [usize; 3]) -> f64 { +fn cantilever_tip(kind: ElementKind, formulation: fieldcraft_model::Formulation, n: [usize; 3]) -> f64 { let (l, b, h, young, p) = (10.0, 1.0, 1.0, 1000.0, 1.0); let mut g = grid(n, kind, |q| [l * q[0], b * q[1], h * q[2]]); g.define(iso(young, 0.0), None, formulation, Procedure::Static {}); @@ -377,7 +377,7 @@ fn plastic_materials_solve_elastically_with_a_warning() { g.define(iso(1000.0, 0.3), None, full(), Procedure::Static {}); if plastic { let mut m = g.model.material("Mat").unwrap().clone(); - m.plastic = Some(modelcraft_model::Plastic::Bilinear { yield_stress: 1.0, hardening_modulus: 0.0, hardening: Default::default() }); + m.plastic = Some(fieldcraft_model::Plastic::Bilinear { yield_stress: 1.0, hardening_modulus: 0.0, hardening: Default::default() }); g.model.replace_material("Mat", m).unwrap(); } g.fixed("root", |q| near(q[0], 0.0)); diff --git a/crates/testkit/Cargo.toml b/crates/testkit/Cargo.toml index c6a6414..9959f15 100644 --- a/crates/testkit/Cargo.toml +++ b/crates/testkit/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-testkit" -description = "ModelCraft test tooling: the meshing test corpus (SolidCraft recipes and analytic shapes) and the verification oracle" +name = "fieldcraft-testkit" +description = "FieldCraft test tooling: the meshing test corpus (SolidCraft recipes and analytic shapes) and the verification oracle" version.workspace = true edition.workspace = true license.workspace = true @@ -10,8 +10,8 @@ publish = false workspace = true [dependencies] -modelcraft-geometry = { workspace = true, features = ["scripts"] } -modelcraft-mesh = { workspace = true } -modelcraft-engine = { workspace = true } +fieldcraft-geometry = { workspace = true, features = ["scripts"] } +fieldcraft-mesh = { workspace = true } +fieldcraft-engine = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/testkit/src/bin/corpus.rs b/crates/testkit/src/bin/corpus.rs index 1ba5bdb..9855a27 100644 --- a/crates/testkit/src/bin/corpus.rs +++ b/crates/testkit/src/bin/corpus.rs @@ -1,4 +1,4 @@ -//! `corpus [--part NAME] [--no-write]`: see [`modelcraft_testkit::runner`]; run it as +//! `corpus [--part NAME] [--no-write]`: see [`fieldcraft_testkit::runner`]; run it as //! `cargo xtask corpus`. //! //! `corpus --part NAME --design PATH` writes that part's `.solidcraft` design to `PATH` instead @@ -31,7 +31,7 @@ fn main() -> ExitCode { let result = match (design, only) { (Some(path), Some(name)) => write_design(name, Path::new(path)), (Some(_), None) => Err(format!("--design needs --part\n{USAGE}")), - (None, _) => modelcraft_testkit::runner::run(&modelcraft_testkit::corpus::workspace_root(), only, write), + (None, _) => fieldcraft_testkit::runner::run(&fieldcraft_testkit::corpus::workspace_root(), only, write), }; match result { Ok(()) => ExitCode::SUCCESS, @@ -44,9 +44,9 @@ fn main() -> ExitCode { /// Write the design of the corpus part `name` to `path`. fn write_design(name: &str, path: &Path) -> Result<(), String> { - let parts = modelcraft_testkit::corpus::parts().map_err(|e| e.to_string())?; + let parts = fieldcraft_testkit::corpus::parts().map_err(|e| e.to_string())?; let part = parts.iter().find(|p| p.name == name).ok_or_else(|| format!("no corpus part named `{name}`"))?; - let bytes = modelcraft_testkit::corpus::design_bytes(part).map_err(|e| e.to_string())?; + let bytes = fieldcraft_testkit::corpus::design_bytes(part).map_err(|e| e.to_string())?; std::fs::write(path, bytes).map_err(|e| format!("{}: {e}", path.display()))?; eprintln!("corpus: wrote {} ({name})", path.display()); Ok(()) diff --git a/crates/testkit/src/bin/oracle.rs b/crates/testkit/src/bin/oracle.rs index 70fc286..83c5474 100644 --- a/crates/testkit/src/bin/oracle.rs +++ b/crates/testkit/src/bin/oracle.rs @@ -1,11 +1,11 @@ //! `oracle [--case NAME]... [--bless NAME|all]... [--quick] [--no-write]`: see -//! [`modelcraft_testkit::oracle`]; run it as `cargo xtask oracle`. +//! [`fieldcraft_testkit::oracle`]; run it as `cargo xtask oracle`. #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] #![forbid(unsafe_code)] use std::process::ExitCode; -use modelcraft_testkit::oracle::{Options, run}; +use fieldcraft_testkit::oracle::{Options, run}; const USAGE: &str = "usage: oracle [--case NAME]... [--bless NAME|all]... [--quick] [--no-write]"; @@ -33,7 +33,7 @@ fn main() -> ExitCode { return ExitCode::FAILURE; } } - match run(&modelcraft_testkit::corpus::workspace_root(), &options) { + match run(&fieldcraft_testkit::corpus::workspace_root(), &options) { Ok(()) => ExitCode::SUCCESS, Err(e) => { eprintln!("error: {e}"); diff --git a/crates/testkit/src/corpus.rs b/crates/testkit/src/corpus.rs index afd7f13..07943ac 100644 --- a/crates/testkit/src/corpus.rs +++ b/crates/testkit/src/corpus.rs @@ -5,14 +5,14 @@ //! `solidcraft-cli run` takes) with a `corpus` object beside its `commands` giving the //! categories it stresses and, when it has one, its closed-form volume; `README.md` beside it is //! one line on what it stresses. The script runs in SolidCraft's engine, the design is saved and -//! opened again through [`modelcraft_geometry::open_design`], the path a person's design takes. +//! opened again through [`fieldcraft_geometry::open_design`], the path a person's design takes. //! - **Analytic shapes** ([`Analytic`]): sphere, torus, cylinder, thick-walled pipe and a plate //! with a hole, built in code with their exact volume, the classic solver verification //! geometries. use std::path::{Path, PathBuf}; -use modelcraft_geometry::{Design, open_design, sample, script_design}; +use fieldcraft_geometry::{Design, open_design, sample, script_design}; use serde_json::{Value, json}; #[derive(Debug, thiserror::Error)] @@ -22,7 +22,7 @@ pub enum Error { #[error("{part}: {message}")] Part { part: String, message: String }, #[error("{part}: {source}")] - Geometry { part: String, source: modelcraft_geometry::Error }, + Geometry { part: String, source: fieldcraft_geometry::Error }, } pub type Result = std::result::Result; @@ -107,7 +107,7 @@ impl Analytic { } /// The design as `.solidcraft` bytes. - pub fn design(&self) -> modelcraft_geometry::Result> { + pub fn design(&self) -> fieldcraft_geometry::Result> { let script = match *self { Analytic::Sphere { radius } => json!([{"command": "solid.sphere", "params": {"radius": radius, "body_name": "Sphere"}}]), Analytic::Torus { major, minor } => json!([{"command": "solid.torus", "params": {"major": major, "minor": minor, "body_name": "Torus"}}]), @@ -300,7 +300,7 @@ mod tests { #[test] fn hostile_recipes_are_an_error() { - let dir = std::env::temp_dir().join(format!("modelcraft-corpus-test-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-corpus-test-{}", std::process::id())); let part = dir.join("bad"); std::fs::create_dir_all(&part).unwrap(); std::fs::write(part.join("README.md"), "A bad part.\n").unwrap(); diff --git a/crates/testkit/src/oracle.rs b/crates/testkit/src/oracle.rs index f3f3830..434837e 100644 --- a/crates/testkit/src/oracle.rs +++ b/crates/testkit/src/oracle.rs @@ -5,7 +5,7 @@ //! A case directory holds: //! - `expected.json`: the case's kind (theory, experiment, benchmark), source, status and its //! quantities: how each is measured, the reference value, the tolerance and the reason for it; -//! - `recipe.json`: the model as ModelCraft commands (a JSON array of `{"command", "params"}`, +//! - `recipe.json`: the model as FieldCraft commands (a JSON array of `{"command", "params"}`, //! each step optionally with an `id` that measures can read its output by); absent while a //! case is pending; //! - `design.json` (optional): a SolidCraft script that builds the geometry, opened first; @@ -21,7 +21,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Instant; -use modelcraft_engine::Session; +use fieldcraft_engine::Session; use serde_json::{Value, json}; /// Fixture band: a result may move by this fraction of its size (and a thousandth of the @@ -232,8 +232,8 @@ pub fn run_case(dir: &Path, expected: &Expected) -> Result Result<(), String> { } } let text = serde_json::to_string_pretty(&json!({ - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": values, })) .map_err(|e| e.to_string())?; @@ -565,7 +565,7 @@ pub fn report(cases: &[(String, Expected)], rows: &[Row]) -> String { md, "# Verification and validation oracle\n\n\ Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the policy is [verification.md](verification.md). \ -Every case is our own model, built with ModelCraft commands from the problem's published description, and compared with \ +Every case is our own model, built with FieldCraft commands from the problem's published description, and compared with \ the reference value its `expected.json` cites, within the stated tolerance and for the stated reason. Accepted results are \ frozen in each case's `fixture.json`: a later change that moves one beyond its band (10⁻⁹ of its size) fails CI even while \ the reference still passes. No reference value was produced with a commercial product.\n\n\ @@ -589,7 +589,7 @@ benchmark, {} experiment; {} pending cases wait for capabilities still being bui }; let _ = write!( md, - "## {heading}\n\n| Case | Quantity | Reference | ModelCraft | Error | Tolerance | Result | Fixture |\n|---|---|---|---|---|---|---|---|\n" + "## {heading}\n\n| Case | Quantity | Reference | FieldCraft | Error | Tolerance | Result | Fixture |\n|---|---|---|---|---|---|---|---|\n" ); for (case, _) in &in_kind { for r in rows.iter().filter(|r| &r.case == case) { diff --git a/crates/testkit/src/runner.rs b/crates/testkit/src/runner.rs index 9e8e05c..f02827c 100644 --- a/crates/testkit/src/runner.rs +++ b/crates/testkit/src/runner.rs @@ -13,11 +13,11 @@ use std::path::Path; use std::time::Instant; use crate::corpus::{self, CATEGORIES, Part}; -use modelcraft_geometry::{Design, FaceSurface, Measure, SurfaceMesh}; -use modelcraft_mesh::cdt::{CdtOptions, tetrahedralize}; -use modelcraft_mesh::hex::{HexOptions, Surface, hex_mesh}; -use modelcraft_mesh::size::UniformSize; -use modelcraft_mesh::surface::{BoundarySurface, SurfaceOptions}; +use fieldcraft_geometry::{Design, FaceSurface, Measure, SurfaceMesh}; +use fieldcraft_mesh::cdt::{CdtOptions, tetrahedralize}; +use fieldcraft_mesh::hex::{HexOptions, Surface, hex_mesh}; +use fieldcraft_mesh::size::UniformSize; +use fieldcraft_mesh::surface::{BoundarySurface, SurfaceOptions}; pub const FILE: &str = "docs/meshing/corpus.md"; /// Chordal tolerance of the boundary surface, relative to the part's bounding-box diagonal. @@ -64,7 +64,7 @@ impl Stage for SurfaceVolume { } } -/// Boundary surface preparation (`modelcraft_mesh::surface`): welded, closed, manifold, outward +/// Boundary surface preparation (`fieldcraft_mesh::surface`): welded, closed, manifold, outward /// and free of self-intersections, or the first defects found, which keep the part from being /// volume meshed. struct BoundaryCheck; @@ -89,7 +89,7 @@ impl Stage for BoundaryCheck { } } -/// The constrained Delaunay tetrahedralization (`modelcraft_mesh::cdt`) of each body's boundary: +/// The constrained Delaunay tetrahedralization (`fieldcraft_mesh::cdt`) of each body's boundary: /// tetrahedra, Steiner points on boundary edges, cavities refilled by gift-wrapping, and the /// validity gate on the rounded mesh (every tetrahedron positive, volume equal to the boundary's). struct TetCdt; @@ -121,7 +121,7 @@ const HEX_RELATIVE_SIZE: f64 = 1.0 / 25.0; /// Characters of a "not meshed" reason kept in the table. const REASON_CHARS: usize = 240; -/// Structured all-hex meshing (`modelcraft_mesh::hex`): the method that meshed each body, the +/// Structured all-hex meshing (`fieldcraft_mesh::hex`): the method that meshed each body, the /// element count and the smallest scaled Jacobian of the hex8 mesh and of the hex20 mesh (mid-edge /// nodes on the geometry), or why no structured method fits. struct StructuredHex; diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 61d055c..11038d6 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "modelcraft-ui-egui" -description = "ModelCraft desktop interface (egui + wgpu): viewport, model tree, commands, status bar and the control channel's request handling" +name = "fieldcraft-ui-egui" +description = "FieldCraft desktop interface (egui + wgpu): viewport, model tree, commands, status bar and the control channel's request handling" version.workspace = true edition.workspace = true license.workspace = true @@ -9,12 +9,12 @@ license.workspace = true workspace = true [dependencies] -modelcraft-model = { workspace = true } -modelcraft-engine = { workspace = true } -modelcraft-render = { workspace = true } -modelcraft-explicit = { workspace = true } -modelcraft-geometry = { workspace = true } -modelcraft-decks = { workspace = true } +fieldcraft-model = { workspace = true } +fieldcraft-engine = { workspace = true } +fieldcraft-render = { workspace = true } +fieldcraft-explicit = { workspace = true } +fieldcraft-geometry = { workspace = true } +fieldcraft-decks = { workspace = true } egui = { workspace = true } craft-ui = { workspace = true } egui-wgpu = { workspace = true } diff --git a/crates/ui-egui/src/appbar.rs b/crates/ui-egui/src/appbar.rs index d121182..6fc5db6 100644 --- a/crates/ui-egui/src/appbar.rs +++ b/crates/ui-egui/src/appbar.rs @@ -61,7 +61,7 @@ fn button_style(t: &Tokens) -> IconButtonStyle { } } -/// ModelCraft's mark: the application icon's bracket (`assets/app-icon/icon-small.svg`) drawn in +/// FieldCraft's mark: the application icon's bracket (`assets/app-icon/icon-small.svg`) drawn in /// code from the SVG's own coordinates, without its tile, each face in the middle of its gradient. fn mark(p: &egui::Painter, c: egui::Pos2) { // The bracket spans y 141–883 in the SVG; draw it 22 points tall around its centre. @@ -142,7 +142,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) { let t = Tokens::get(app.ui.dark); let style = button_style(&t); let mut anchors: Vec<(Quick, Rect)> = Vec::new(); - egui::Panel::top("mc_appbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.app_bar)).show(ui, |ui| { + egui::Panel::top("fc_appbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.app_bar)).show(ui, |ui| { let r = ui.max_rect(); app.place("app_bar", r); if app.custom_titlebar || app.integrated_titlebar { @@ -154,7 +154,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) { let lay = bar_layout(r, app.integrated_titlebar && !fullscreen(ui.ctx()), captions, version_w); mark(ui.painter(), lay.mark); if let Some(at) = lay.name { - ui.painter().text(at, Align2::LEFT_CENTER, "ModelCraft", FontId::proportional(14.0), t.app_bar_text); + ui.painter().text(at, Align2::LEFT_CENTER, "FieldCraft", FontId::proportional(14.0), t.app_bar_text); } let (undo, redo) = app.session.undo_depth(); let busy = app.busy().is_some(); @@ -185,7 +185,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) { } // Solve: the one prominent action, a filled button with its label. let solve = lay.solve; - let resp = ui.interact(solve, ui.id().with("mc_solve"), Sense::click()); + let resp = ui.interact(solve, ui.id().with("fc_solve"), Sense::click()); let can = !app.session.model.steps().is_empty() && !busy; let fill = if resp.hovered() && can { t.accent.gamma_multiply(1.15) @@ -230,7 +230,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) { if tab_w > 40.0 { let tab = Rect::from_min_size(pos2(tab_x, r.top() + 5.0), vec2(tab_w, r.height() - 5.0)); let path = app.session.path.clone(); - craft_ui::tabs::Tab::new(egui::Id::new("mc_document_tab"), &label, true).show_at(ui, tab, |ui, resp| { + craft_ui::tabs::Tab::new(egui::Id::new("fc_document_tab"), &label, true).show_at(ui, tab, |ui, resp| { ui.painter().rect_filled(tab, CornerRadius { nw: 5, ne: 5, sw: 0, se: 0 }, t.toolbar); let clip = tab.shrink2(vec2(10.0, 0.0)); ui.painter().with_clip_rect(clip).text( @@ -297,7 +297,7 @@ fn quick_button( selected: bool, ) -> bool { let resp = ui - .scope_builder(egui::UiBuilder::new().max_rect(rect).id_salt(("mc_quick", key)), |ui| { + .scope_builder(egui::UiBuilder::new().max_rect(rect).id_salt(("fc_quick", key)), |ui| { ui.add_enabled_ui(enabled, |ui| { IconButton::new(tip, rect.size(), style).selected(selected).show(ui, |_, _, _| {}).on_hover_text(tip).on_disabled_hover_text(tip) }) @@ -326,7 +326,7 @@ fn help_menu(app: &mut ModelApp, ui: &mut egui::Ui) { ui.label("Left drag orbits (or pans or zooms: the navigation bar), right or middle drag pans, the wheel zooms, F or a double click fits."); ui.label(format!("S or {} searches every command; the command line at the bottom runs `id {{json}}`.", shortcut("K"))); ui.separator(); - ui.label(egui::RichText::new(format!("ModelCraft {}", crate::VERSION)).color(t.text_dim)); + ui.label(egui::RichText::new(format!("FieldCraft {}", crate::VERSION)).color(t.text_dim)); if let Some(n) = open { app.ui.menu = None; app.open_sample(n); diff --git a/crates/ui-egui/src/browser.rs b/crates/ui-egui/src/browser.rs index 182b619..c45563b 100644 --- a/crates/ui-egui/src/browser.rs +++ b/crates/ui-egui/src/browser.rs @@ -7,7 +7,7 @@ //! ATTRIBUTION.md. use egui::{Color32, FontId, Pos2, Rect, RichText, Sense, Stroke, Vec2, pos2, vec2}; -use modelcraft_model::Model; +use fieldcraft_model::Model; use serde::Serialize; use serde_json::{Value, json}; @@ -267,7 +267,7 @@ enum Act { pub fn browser(app: &mut ModelApp, ui: &mut egui::Ui) { let t = Tokens::get(app.ui.dark); let mut acts: Vec = Vec::new(); - egui::Panel::left("mc_browser") + egui::Panel::left("fc_browser") .exact_size(WIDTH) .resizable(false) .show_separator_line(true) @@ -276,7 +276,7 @@ pub fn browser(app: &mut ModelApp, ui: &mut egui::Ui) { app.place("browser", ui.max_rect()); ui.label(RichText::new("BROWSER").font(crate::theme::label_font(ui.ctx(), 10.5)).color(t.text_dim)); ui.add_space(4.0); - egui::ScrollArea::vertical().id_salt("mc_browser_scroll").auto_shrink([false, false]).show(ui, |ui| { + egui::ScrollArea::vertical().id_salt("fc_browser_scroll").auto_shrink([false, false]).show(ui, |ui| { ui.spacing_mut().item_spacing.y = 0.0; rows(app, ui, &mut acts); }); @@ -313,7 +313,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { draw_row( app, ui, - egui::Id::new("mc_row_root"), + egui::Id::new("fc_row_root"), &Row { icon: Some("ui.views"), label: &name, note: dirty.then(|| "unsaved".into()), ..Default::default() }, ); // Geometry. @@ -323,7 +323,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { let r = draw_row( app, ui, - egui::Id::new("mc_row_geometry"), + egui::Id::new("fc_row_geometry"), &Row { depth: 1, fold: Some(open(app, "geometry")), @@ -345,14 +345,14 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { draw_row( app, ui, - egui::Id::new("mc_row_design"), + egui::Id::new("fc_row_design"), &Row { depth: 2, icon: Some("geometry.open"), label: &label, note: Some(format!("{bodies} bodies")), ..Default::default() }, ); for (i, b) in d.bodies.iter().enumerate() { draw_row( app, ui, - egui::Id::new(("mc_row_body", i)), + egui::Id::new(("fc_row_body", i)), &Row { depth: 3, icon: Some("geometry.bodies"), label: &b.name, ..Default::default() }, ); } @@ -361,7 +361,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { draw_row( app, ui, - egui::Id::new(("mc_row_surface", i)), + egui::Id::new(("fc_row_surface", i)), &Row { depth: 2, icon: Some("geometry.import.mesh"), label: s, ..Default::default() }, ); } @@ -369,7 +369,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { draw_row( app, ui, - egui::Id::new("mc_row_no_geometry"), + egui::Id::new("fc_row_no_geometry"), &Row { depth: 2, label: "No geometry: Open Design", dim: true, ..Default::default() }, ); } @@ -379,7 +379,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { let r = draw_row( app, ui, - egui::Id::new("mc_row_mesh"), + egui::Id::new("fc_row_mesh"), &Row { depth: 1, fold: Some(open(app, "mesh")), @@ -399,7 +399,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { let r = draw_row( app, ui, - egui::Id::new("mc_row_nodes"), + egui::Id::new("fc_row_nodes"), &Row { depth: 2, eye: Some(app.ui.show_nodes), @@ -420,7 +420,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { draw_row( app, ui, - egui::Id::new(("mc_row_kind", kind.name())), + egui::Id::new(("fc_row_kind", kind.name())), &Row { depth: 2, swatch: Some(t.element(kind)), label: kind.name(), note: Some(format!("{n}")), ..Default::default() }, ); } @@ -441,7 +441,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { let r = draw_row( app, ui, - egui::Id::new(("mc_row_group", key)), + egui::Id::new(("fc_row_group", key)), &Row { depth: 1, fold: Some(open(app, key)), @@ -461,7 +461,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { let r = draw_row( app, ui, - egui::Id::new(("mc_row_def", kind.stem(), &name)), + egui::Id::new(("fc_row_def", kind.stem(), &name)), &Row { depth: 2, icon: Some(kind.icon()), label: &name, selected, ..Default::default() }, ); if r.double { @@ -478,7 +478,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { let r = draw_row( app, ui, - egui::Id::new("mc_row_results"), + egui::Id::new("fc_row_results"), &Row { depth: 1, fold: Some(open(app, "results")), @@ -499,7 +499,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { let r = draw_row( app, ui, - egui::Id::new(("mc_row_result", &step)), + egui::Id::new(("fc_row_result", &step)), &Row { depth: 2, icon: Some("results.deformed"), label: &label, selected, dim: !current, ..Default::default() }, ); if r.clicked { @@ -511,14 +511,14 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec) { #[cfg(test)] mod tests { - use modelcraft_engine::Session; + use fieldcraft_engine::Session; use super::*; #[test] fn definitions_read_back_as_their_parameters() { let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::cantilever()).unwrap(); + app.run_script(&fieldcraft_engine::sample::cantilever()).unwrap(); let m = &app.session.model; assert_eq!(Def::Step.names(m), ["Load", "Modes"]); let steel = definition(m, Def::Material, "Steel").unwrap(); @@ -537,9 +537,9 @@ mod tests { Def::FluidDomain, ] { for verb in ["create", "edit", "delete"] { - assert!(modelcraft_engine::find_command(&kind.command(verb)).is_some(), "{kind:?} {verb}"); + assert!(fieldcraft_engine::find_command(&kind.command(verb)).is_some(), "{kind:?} {verb}"); } - assert!(modelcraft_engine::catalogue::find(kind.icon()).is_some()); + assert!(fieldcraft_engine::catalogue::find(kind.icon()).is_some()); } } } diff --git a/crates/ui-egui/src/capture.rs b/crates/ui-egui/src/capture.rs index 59ba549..0217143 100644 --- a/crates/ui-egui/src/capture.rs +++ b/crates/ui-egui/src/capture.rs @@ -292,7 +292,7 @@ impl Canvas { #[cfg(test)] mod tests { - use modelcraft_engine::Session; + use fieldcraft_engine::Session; use super::*; @@ -331,7 +331,7 @@ mod tests { #[test] fn the_whole_window_is_captured_with_panels_and_viewport() { let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::script()).unwrap(); + app.run_script(&fieldcraft_engine::sample::script()).unwrap(); let image = app.capture(size(900.0, 600.0)).unwrap(); assert_eq!(image.size, [900, 600]); let t = Tokens::dark(); diff --git a/crates/ui-egui/src/context.rs b/crates/ui-egui/src/context.rs index bdf199e..bc2ca03 100644 --- a/crates/ui-egui/src/context.rs +++ b/crates/ui-egui/src/context.rs @@ -24,13 +24,13 @@ pub fn panel(app: &mut ModelApp, ui: &mut egui::Ui) { return; } let t = Tokens::get(app.ui.dark); - egui::Panel::right("mc_context") + egui::Panel::right("fc_context") .exact_size(WIDTH) .resizable(false) .frame(egui::Frame::NONE.fill(t.dialog_bg).inner_margin(egui::Margin::symmetric(8, 8))) .show(ui, |ui| { app.place("context", ui.max_rect()); - egui::ScrollArea::vertical().id_salt("mc_context_scroll").auto_shrink([false, false]).show(ui, |ui| { + egui::ScrollArea::vertical().id_salt("fc_context_scroll").auto_shrink([false, false]).show(ui, |ui| { ui.set_width(WIDTH - 16.0); if app.dialog.is_some() { form(app, ui, &t); @@ -61,7 +61,7 @@ pub fn panel(app: &mut ModelApp, ui: &mut egui::Ui) { /// The open command's parameters as JSON, with the command's parameter documentation; OK runs it. fn form(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens) { let Some(d) = app.dialog.as_mut() else { return }; - let Some(spec) = modelcraft_engine::find_command(d.command) else { + let Some(spec) = fieldcraft_engine::find_command(d.command) else { app.dialog = None; return; }; @@ -134,7 +134,7 @@ fn properties(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, kind: Def, name }); }); ui.add_space(4.0); - egui::Grid::new("mc_properties").num_columns(2).spacing(vec2(10.0, 3.0)).show(ui, |ui| { + egui::Grid::new("fc_properties").num_columns(2).spacing(vec2(10.0, 3.0)).show(ui, |ui| { for (k, text) in rows.iter() { ui.label(RichText::new(k).color(t.text_dim)); ui.add(egui::Label::new(text).wrap()); diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index b528826..434f202 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -8,7 +8,7 @@ use std::sync::mpsc::Sender; -use modelcraft_render::StandardView; +use fieldcraft_render::StandardView; use serde_json::{Value, json}; use crate::capture::CaptureSize; @@ -79,9 +79,9 @@ fn render_dim(p: &Value, key: &str, default: usize) -> Result { None | Some(Value::Null) => Ok(default), Some(v) => v .as_f64() - .filter(|x| x.fract() == 0.0 && (1.0..=modelcraft_render::MAX_DIM as f64).contains(x)) + .filter(|x| x.fract() == 0.0 && (1.0..=fieldcraft_render::MAX_DIM as f64).contains(x)) .map(|x| x as usize) - .ok_or_else(|| format!("`{key}` must be a whole number of pixels in 1..={}", modelcraft_render::MAX_DIM)), + .ok_or_else(|| format!("`{key}` must be a whole number of pixels in 1..={}", fieldcraft_render::MAX_DIM)), } } @@ -102,7 +102,7 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) -> wrap(app.run_script(steps).map(|()| json!({"journal": app.session.journal.len()}))) } "engine.commands" => { - ok(Value::Array(modelcraft_engine::commands().iter().map(|c| json!({"id": c.id, "label": c.label, "params": c.params})).collect())) + ok(Value::Array(fieldcraft_engine::commands().iter().map(|c| json!({"id": c.id, "label": c.label, "params": c.params})).collect())) } "ui.inspect" => ok(json!({ "ui": app.ui, @@ -303,7 +303,7 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) -> .take(1000) .map(|v| { let c = |i: usize| v.get(i).and_then(Value::as_f64).unwrap_or(f64::NAN); - crate::viewport::to_screen(app, modelcraft_render::Vec3::new(c(0), c(1), c(2))).map_or(Value::Null, |q| json!([q.x, q.y])) + crate::viewport::to_screen(app, fieldcraft_render::Vec3::new(c(0), c(1), c(2))).map_or(Value::Null, |q| json!([q.x, q.y])) }) .collect(); ok(json!(pts)) @@ -384,12 +384,12 @@ pub fn capture_to(app: &mut ModelApp, size: CaptureSize, path: Option<&str>) -> } } -/// Save a screenshot as PNG: at `path`, or `modelcraft-shot.png` in the temporary folder. +/// Save a screenshot as PNG: at `path`, or `fieldcraft-shot.png` in the temporary folder. pub fn save_screenshot(image: &egui::ColorImage, path: Option<&str>) -> Value { let [w, h] = image.size; let rgba: Vec = image.pixels.iter().flat_map(|c| c.to_array()).collect(); - let Some(png) = modelcraft_render::png(w, h, &rgba) else { return json!({"ok": false, "error": "could not encode the screenshot"}) }; - let path = path.map(str::to_string).unwrap_or_else(|| std::env::temp_dir().join("modelcraft-shot.png").to_string_lossy().to_string()); + let Some(png) = fieldcraft_render::png(w, h, &rgba) else { return json!({"ok": false, "error": "could not encode the screenshot"}) }; + let path = path.map(str::to_string).unwrap_or_else(|| std::env::temp_dir().join("fieldcraft-shot.png").to_string_lossy().to_string()); match std::fs::write(&path, &png) { Ok(()) => json!({"ok": true, "result": {"path": path, "width": w, "height": h}}), Err(e) => json!({"ok": false, "error": format!("{path}: {e}")}), @@ -398,7 +398,7 @@ pub fn save_screenshot(image: &egui::ColorImage, path: Option<&str>) -> Value { #[cfg(test)] mod tests { - use modelcraft_engine::Session; + use fieldcraft_engine::Session; use super::*; @@ -413,12 +413,12 @@ mod tests { #[test] fn engine_view_and_settings_methods() { let mut app = ModelApp::new(Session::default()); - let r = call(&mut app, "engine.script", json!({"commands": modelcraft_engine::sample::script()})); + let r = call(&mut app, "engine.script", json!({"commands": fieldcraft_engine::sample::script()})); assert_eq!(r["ok"], true, "{r}"); let r = call(&mut app, "engine.execute", json!({"command": "model.info"})); assert_eq!(r["result"]["elements"]["hex8"], 64); assert_eq!(call(&mut app, "engine.execute", json!({"command": "no.such"}))["ok"], false); - assert!(call(&mut app, "engine.commands", json!({}))["result"].as_array().is_some_and(|a| a.len() == modelcraft_engine::commands().len())); + assert!(call(&mut app, "engine.commands", json!({}))["result"].as_array().is_some_and(|a| a.len() == fieldcraft_engine::commands().len())); let r = call(&mut app, "ui.set", json!({"style": "wireframe", "show_nodes": true})); assert_eq!(r["result"]["style"], "wireframe"); assert_eq!(app.ui.style, crate::Style::Wireframe); @@ -445,8 +445,8 @@ mod tests { #[test] fn ui_render_writes_a_png_of_the_model() { let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::script()).unwrap(); - let path = std::env::temp_dir().join(format!("modelcraft-render-{}.png", std::process::id())); + app.run_script(&fieldcraft_engine::sample::script()).unwrap(); + let path = std::env::temp_dir().join(format!("fieldcraft-render-{}.png", std::process::id())); let r = call(&mut app, "ui.render", json!({"path": path.to_string_lossy(), "width": 120, "height": 80})); assert_eq!(r["ok"], true, "{r}"); let bytes = std::fs::read(&path).unwrap(); @@ -458,8 +458,8 @@ mod tests { #[test] fn ui_render_refuses_hostile_sizes() { let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::script()).unwrap(); - let path = std::env::temp_dir().join(format!("modelcraft-render-hostile-{}.png", std::process::id())); + app.run_script(&fieldcraft_engine::sample::script()).unwrap(); + let path = std::env::temp_dir().join(format!("fieldcraft-render-hostile-{}.png", std::process::id())); let path = path.to_string_lossy(); for (w, h) in [ (json!(100_000), json!(100_000)), @@ -526,7 +526,7 @@ mod tests { let r = call(&mut app, "ui.toolbar", json!({})); assert_eq!(r["result"]["tab"], "mesh", "{r}"); let entries = r["result"]["entries"].as_array().unwrap(); - assert_eq!(entries.len(), modelcraft_engine::catalogue::CATALOGUE.len()); + assert_eq!(entries.len(), fieldcraft_engine::catalogue::CATALOGUE.len()); let soon = entries.iter().find(|e| e["id"] == "mesh.order.quadratic").unwrap(); assert_eq!((soon["coming_soon"].as_str(), soon["command"].is_null()), (Some("M2"), true)); assert_eq!(call(&mut app, "ui.set", json!({"tab": "nope"}))["ok"], false); @@ -542,7 +542,7 @@ mod tests { #[test] fn ui_capture_saves_the_whole_window() { let mut app = ModelApp::new(Session::default()); - let path = std::env::temp_dir().join(format!("modelcraft-capture-{}.png", std::process::id())); + let path = std::env::temp_dir().join(format!("fieldcraft-capture-{}.png", std::process::id())); let r = call(&mut app, "ui.capture", json!({"path": path.to_string_lossy(), "width": 640, "height": 400, "scale": 1})); assert_eq!( (r["ok"].as_bool(), r["result"]["width"].as_u64(), r["result"]["offscreen"].as_bool()), @@ -559,7 +559,7 @@ mod tests { #[test] fn screenshots_are_saved_as_png() { let image = egui::ColorImage::filled([4, 3], egui::Color32::RED); - let path = std::env::temp_dir().join(format!("modelcraft-shot-{}.png", std::process::id())); + let path = std::env::temp_dir().join(format!("fieldcraft-shot-{}.png", std::process::id())); let r = save_screenshot(&image, Some(&path.to_string_lossy())); assert_eq!(r["ok"], true, "{r}"); assert!(std::fs::read(&path).unwrap().starts_with(&[0x89, b'P', b'N', b'G'])); diff --git a/crates/ui-egui/src/files.rs b/crates/ui-egui/src/files.rs index 6534f83..bd019d9 100644 --- a/crates/ui-egui/src/files.rs +++ b/crates/ui-egui/src/files.rs @@ -16,7 +16,7 @@ use std::path::{Path, PathBuf}; use std::sync::mpsc::{Receiver, TryRecvError}; -use modelcraft_engine::Session; +use fieldcraft_engine::Session; use serde_json::{Value, json}; use crate::ModelApp; @@ -44,18 +44,18 @@ pub struct Services { pub recent_path: Option, } -const MODEL: Filter = Filter { name: "ModelCraft model", extensions: &["modelcraft"] }; +const MODEL: Filter = Filter { name: "FieldCraft model", extensions: &["fieldcraft"] }; const DESIGN: Filter = Filter { name: "SolidCraft design", extensions: &["solidcraft"] }; const MESH: Filter = Filter { name: "Face-tagged mesh (3MF)", extensions: &["3mf", "3MF"] }; -const ANY: Filter = Filter { name: "Models, designs and tagged meshes", extensions: &["modelcraft", "solidcraft", "3mf", "3MF"] }; +const ANY: Filter = Filter { name: "Models, designs and tagged meshes", extensions: &["fieldcraft", "solidcraft", "3mf", "3MF"] }; -/// The Open picker's filters: everything ModelCraft opens, then each type. +/// The Open picker's filters: everything FieldCraft opens, then each type. pub const OPEN_FILTERS: &[Filter] = &[ANY, MODEL, DESIGN, MESH]; /// What opening a file does with it, from its extension. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Kind { - /// A `.modelcraft` model replaces the session (`file.open`). + /// A `.fieldcraft` model replaces the session (`file.open`). Model, /// A `.solidcraft` design becomes the geometry (`geometry.open`). Design, @@ -64,11 +64,11 @@ pub enum Kind { } impl Kind { - /// The kind of a file by its extension (any case); `None` for a file ModelCraft does not open. + /// The kind of a file by its extension (any case); `None` for a file FieldCraft does not open. pub fn of(path: &str) -> Option { let ext = Path::new(path).extension()?.to_str()?.to_ascii_lowercase(); match ext.as_str() { - "modelcraft" => Some(Kind::Model), + "fieldcraft" => Some(Kind::Model), "solidcraft" => Some(Kind::Design), "3mf" => Some(Kind::Mesh), _ => None, @@ -162,9 +162,9 @@ impl Recent { /// A command running in the background on a copy of the session: a file opening, a solve. pub struct Loading { - /// What the window shows while it runs ("Opening beam.modelcraft"). + /// What the window shows while it runs ("Opening beam.fieldcraft"). pub title: String, - /// The status when it succeeded ("Opened beam.modelcraft"). + /// The status when it succeeded ("Opened beam.fieldcraft"). done: String, /// The file it opens, for the recent list. recent: Option, @@ -177,7 +177,7 @@ pub struct Loading { /// What waits for an answer about unsaved changes. #[derive(Clone, Debug, PartialEq)] pub enum Replace { - /// A `.modelcraft` file. + /// A `.fieldcraft` file. File(String), /// A sample (`file.sample`). Sample(&'static str), @@ -187,7 +187,7 @@ impl Replace { fn name(&self) -> String { match self { Replace::File(p) => file_name(p), - Replace::Sample(n) => modelcraft_engine::sample::find(n).map_or_else(|| (*n).to_string(), |s| format!("the {} sample", s.label)), + Replace::Sample(n) => fieldcraft_engine::sample::find(n).map_or_else(|| (*n).to_string(), |s| format!("the {} sample", s.label)), } } } @@ -239,7 +239,7 @@ fn off_thread(name: String, work: impl FnOnce() + Send + 'static) { /// Run `command` on `session` off the window thread. fn spawn(session: Session, command: &'static str, params: Value) -> Receiver<(Box, Result)> { let (tx, rx) = std::sync::mpsc::channel(); - off_thread(format!("modelcraft {command}"), move || { + off_thread(format!("fieldcraft {command}"), move || { let mut s = session; let r = s.execute(command, ¶ms).map_err(|e| e.to_string()); let _ = tx.send((Box::new(s), r)); @@ -253,7 +253,7 @@ impl ModelApp { model_name(&self.session) } - /// What is running in the background ("Opening beam.modelcraft", "Solving"), if anything. + /// What is running in the background ("Opening beam.fieldcraft", "Solving"), if anything. pub fn busy(&self) -> Option<&str> { self.loading.as_ref().map(|l| l.title.as_str()) } @@ -302,7 +302,7 @@ impl ModelApp { /// replacing unsaved changes asks first. pub fn open_path(&mut self, path: &str) { let Some(kind) = Kind::of(path) else { - self.set_status(format!("{path}: ModelCraft opens .modelcraft models, .solidcraft designs and .3mf tagged meshes"), true); + self.set_status(format!("{path}: FieldCraft opens .fieldcraft models, .solidcraft designs and .3mf tagged meshes"), true); return; }; if kind == Kind::Model && self.has_unsaved_work() { @@ -326,7 +326,7 @@ impl ModelApp { /// Help › Samples (and the empty viewport): open a sample model, asking first when that /// would discard unsaved changes. pub fn open_sample(&mut self, name: &str) { - let Some(sample) = modelcraft_engine::sample::find(name) else { + let Some(sample) = fieldcraft_engine::sample::find(name) else { self.set_status(format!("no sample `{name}`"), true); return; }; @@ -338,7 +338,7 @@ impl ModelApp { } fn load_sample(&mut self, name: &str) { - let Some(sample) = modelcraft_engine::sample::find(name) else { return }; + let Some(sample) = fieldcraft_engine::sample::find(name) else { return }; if self.run("file.sample", json!({"name": sample.name})).is_ok() { self.model_changed(); self.fit_view(); @@ -392,7 +392,7 @@ impl ModelApp { Some((session, Ok(reply))) => { // The session it replaces may be large: it is freed off the window thread. let old = std::mem::replace(&mut self.session, *session); - off_thread("modelcraft drop".into(), move || drop(old)); + off_thread("fieldcraft drop".into(), move || drop(old)); self.model_changed(); if l.fit { self.fit_view(); @@ -452,8 +452,8 @@ impl ModelApp { self.open_form("file.save_as", json!({})); return false; }; - let Some(path) = pick(&format!("{}.modelcraft", self.model_name()), &[MODEL]) else { return false }; - let path = with_extension(path, "modelcraft"); + let Some(path) = pick(&format!("{}.fieldcraft", self.model_name()), &[MODEL]) else { return false }; + let path = with_extension(path, "fieldcraft"); let saved = self.run("file.save_as", json!({"path": path})).is_ok(); self.saved_to_recent(saved) } @@ -461,7 +461,7 @@ impl ModelApp { /// Write Deck: pick where to write the model as a `format` deck. Without a picker the /// command's form opens with the format filled in. pub fn write_deck(&mut self, format: &str) { - let Some(f) = modelcraft_decks::format(format) else { + let Some(f) = fieldcraft_decks::format(format) else { self.set_status(format!("no {format} deck writer yet"), true); return; }; @@ -564,10 +564,10 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) { if hovering || app.loading.is_some() { let text = match app.loading.as_ref() { Some(l) => format!("{}…", l.title), - None => "Drop to open: .modelcraft models, .solidcraft designs, .3mf tagged meshes".into(), + None => "Drop to open: .fieldcraft models, .solidcraft designs, .3mf tagged meshes".into(), }; let mut cancel = false; - egui::Area::new(egui::Id::new("mc_loading")).order(egui::Order::Foreground).anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)).show( + egui::Area::new(egui::Id::new("fc_loading")).order(egui::Order::Foreground).anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)).show( ctx, |ui| { egui::Frame::popup(ui.style()).fill(t.overlay).inner_margin(egui::Margin::symmetric(16, 12)).show(ui, |ui| { @@ -593,7 +593,7 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) { match app.prompt.clone() { Some(Prompt::Unsaved(next)) => { let mut answer = None; - crate::frame::window(ctx, "Unsaved changes", crate::frame::Width::Normal).id(egui::Id::new("mc_unsaved")).show(ctx, |ui| { + crate::frame::window(ctx, "Unsaved changes", crate::frame::Width::Normal).id(egui::Id::new("fc_unsaved")).show(ctx, |ui| { ui.label(format!("Save the changes to {} before opening {}?", app.model_name(), next.name())); ui.label(egui::RichText::new("Without saving, they are lost.").color(t.text_dim)); ui.add_space(6.0); @@ -619,11 +619,11 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) { Some(Prompt::Recover) => { let entries = app.recover_list(ctx); let (mut recover, mut discard, mut close) = (None, None, false); - crate::frame::window(ctx, "Recover", crate::frame::Width::Wide).id(egui::Id::new("mc_recover")).show(ctx, |ui| { + crate::frame::window(ctx, "Recover", crate::frame::Width::Wide).id(egui::Id::new("fc_recover")).show(ctx, |ui| { if entries.is_empty() { ui.label(egui::RichText::new("No autosaved models to recover.").color(t.text_dim)); } - egui::Grid::new("mc_recover_grid").num_columns(3).spacing(egui::vec2(10.0, 4.0)).show(ui, |ui| { + egui::Grid::new("fc_recover_grid").num_columns(3).spacing(egui::vec2(10.0, 4.0)).show(ui, |ui| { for e in &entries { let id = e.get("id").and_then(Value::as_str).unwrap_or_default().to_string(); let name = e.get("path").and_then(Value::as_str).map_or_else(|| "Untitled".to_string(), file_name); @@ -648,15 +648,15 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) { app.recover(&id); } else if let Some(id) = discard { let _ = app.run("file.recovery.discard", json!({"id": id})); - ctx.data_mut(|d| d.remove::>(egui::Id::new("mc_recover_entries"))); + ctx.data_mut(|d| d.remove::>(egui::Id::new("fc_recover_entries"))); } else if close || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { app.prompt = None; - ctx.data_mut(|d| d.remove::>(egui::Id::new("mc_recover_entries"))); + ctx.data_mut(|d| d.remove::>(egui::Id::new("fc_recover_entries"))); } } Some(Prompt::Samples) => { let (mut open, mut close) = (None, false); - crate::frame::window(ctx, "Samples", crate::frame::Width::Normal).id(egui::Id::new("mc_samples")).show(ctx, |ui| { + crate::frame::window(ctx, "Samples", crate::frame::Width::Normal).id(egui::Id::new("fc_samples")).show(ctx, |ui| { sample_rows(ui, &t, &mut open); ui.add_space(6.0); close = ui.button("Close").clicked(); @@ -675,7 +675,7 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) { /// One row per sample: its name, what it shows and an Open button; `open` gets the one clicked. pub fn sample_rows(ui: &mut egui::Ui, t: &crate::theme::Tokens, open: &mut Option<&'static str>) { let width = ui.available_width().clamp(280.0, 420.0); - for sample in modelcraft_engine::sample::SAMPLES { + for sample in fieldcraft_engine::sample::SAMPLES { ui.horizontal(|ui| { ui.vertical(|ui| { ui.set_width(width - 70.0); @@ -693,7 +693,7 @@ pub fn sample_rows(ui: &mut egui::Ui, t: &crate::theme::Tokens, open: &mut Optio impl ModelApp { /// The recover window's entries, read from disk once while it is open. fn recover_list(&mut self, ctx: &egui::Context) -> Vec { - let key = egui::Id::new("mc_recover_entries"); + let key = egui::Id::new("fc_recover_entries"); if let Some(v) = ctx.data(|d| d.get_temp::>(key)) { return v; } @@ -739,7 +739,7 @@ pub fn file_menu(app: &mut ModelApp, ui: &mut egui::Ui) { } ui.separator(); crate::frame::menu_heading(ui, &t, "WRITE DECK"); - for c in modelcraft_engine::catalogue::CATALOGUE.iter().filter(|c| c.panel == "Decks") { + for c in fieldcraft_engine::catalogue::CATALOGUE.iter().filter(|c| c.panel == "Decks") { let format = c.preset().get("format").and_then(Value::as_str).map(str::to_string); let soon = c.milestone().map(|m| format!("soon · {m}")); let enabled = format.is_some() && soon.is_none(); @@ -771,7 +771,7 @@ mod tests { use super::*; fn temp(name: &str) -> PathBuf { - let dir = std::env::temp_dir().join(format!("modelcraft-files-{}-{name}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-files-{}-{name}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); dir @@ -787,26 +787,26 @@ mod tests { #[test] fn file_types_dispatch_to_their_command() { - assert_eq!(Kind::of("a/b.modelcraft"), Some(Kind::Model)); + assert_eq!(Kind::of("a/b.fieldcraft"), Some(Kind::Model)); assert_eq!(Kind::of("B.SOLIDCRAFT"), Some(Kind::Design)); assert_eq!(Kind::of("part.3MF").map(Kind::command), Some("geometry.import.mesh")); assert_eq!(Kind::of("part.step"), None); assert_eq!(Kind::of("noext"), None); - assert_eq!(with_extension("a/b".into(), "modelcraft"), "a/b.modelcraft"); - assert_eq!(with_extension("a/b.MODELCRAFT".into(), "modelcraft"), "a/b.MODELCRAFT"); + assert_eq!(with_extension("a/b".into(), "fieldcraft"), "a/b.fieldcraft"); + assert_eq!(with_extension("a/b.FIELDCRAFT".into(), "fieldcraft"), "a/b.FIELDCRAFT"); } /// Open (picker) → background open → the model, the recent list; Save As (picker) and Save. #[test] fn the_pickers_open_and_save_models() { let dir = temp("models"); - let saved = dir.join("beam.modelcraft").to_string_lossy().to_string(); + let saved = dir.join("beam.fieldcraft").to_string_lossy().to_string(); let mut first = ModelApp::new(Session::default()); - first.run_script(&modelcraft_engine::sample::script()).unwrap(); + first.run_script(&fieldcraft_engine::sample::script()).unwrap(); // Save As through the save picker: the extension is added, the file becomes the model's. let bare = dir.join("beam").to_string_lossy().to_string(); first.services.pick_save = Some(Box::new(move |name: &str, f: &[Filter]| { - assert_eq!((name, f.first().map(|f| f.extensions)), ("Untitled.modelcraft", Some(&["modelcraft"][..]))); + assert_eq!((name, f.first().map(|f| f.extensions)), ("Untitled.fieldcraft", Some(&["fieldcraft"][..]))); Some(bare.clone()) })); first.start("file.save_as").unwrap(); @@ -821,12 +821,12 @@ mod tests { app.start("file.open").unwrap(); assert!(app.dialog.is_none(), "a picker, not the form"); assert_eq!(offered.borrow()[0][0], MODEL, "Open Model offers models first"); - assert_eq!(app.busy(), Some("Opening beam.modelcraft")); + assert_eq!(app.busy(), Some("Opening beam.fieldcraft")); app.wait_loaded(); assert_eq!(app.session.model.elements().len(), first.session.model.elements().len()); assert_eq!(app.session.path.as_deref(), Some(saved.as_str())); assert_eq!(app.recent.paths, vec![saved.clone()]); - assert_eq!(app.status.as_ref().map(|s| (s.text.as_str(), s.error)), Some(("Opened beam.modelcraft", false))); + assert_eq!(app.status.as_ref().map(|s| (s.text.as_str(), s.error)), Some(("Opened beam.fieldcraft", false))); // Save goes to the model's own file without asking. app.services.pick_save = Some(Box::new(|_: &str, _: &[Filter]| -> Option { panic!("Save asked for a file") })); app.run("mesh.node.add", serde_json::json!({"label": 99999, "xyz": [0, 0, 0]})).unwrap(); @@ -861,7 +861,7 @@ mod tests { #[test] fn opening_a_model_over_unsaved_changes_asks_first() { let dir = temp("unsaved"); - let path = dir.join("other.modelcraft").to_string_lossy().to_string(); + let path = dir.join("other.fieldcraft").to_string_lossy().to_string(); let mut other = ModelApp::new(Session::default()); other.run("mesh.node.add", serde_json::json!({"label": 5, "xyz": [0, 0, 0]})).unwrap(); other.run("file.save_as", serde_json::json!({"path": path})).unwrap(); @@ -884,7 +884,7 @@ mod tests { let dir = temp("decks"); let target = dir.join("cantilever").to_string_lossy().to_string(); let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::cantilever()).unwrap(); + app.run_script(&fieldcraft_engine::sample::cantilever()).unwrap(); let t = target.clone(); app.services.pick_save = Some(Box::new(move |name: &str, f: &[Filter]| { assert_eq!((name, f.first().map(|f| f.extensions)), ("Untitled.inp", Some(&["inp"][..]))); @@ -904,17 +904,17 @@ mod tests { let mut r = Recent::load(Some(file.clone())); assert!(r.paths.is_empty()); for i in 0..12 { - r.add(&format!("/m/{i}.modelcraft")); + r.add(&format!("/m/{i}.fieldcraft")); } - r.add("/m/3.modelcraft"); + r.add("/m/3.fieldcraft"); let again = Recent::load(Some(file.clone())); assert_eq!(again.paths.len(), MAX_RECENT); - assert_eq!(again.paths.first().map(String::as_str), Some("/m/3.modelcraft")); - assert_eq!(again.paths.iter().filter(|p| *p == "/m/3.modelcraft").count(), 1); + assert_eq!(again.paths.first().map(String::as_str), Some("/m/3.fieldcraft")); + assert_eq!(again.paths.iter().filter(|p| *p == "/m/3.fieldcraft").count(), 1); std::fs::write(&file, b"{not json").unwrap(); assert!(Recent::load(Some(file.clone())).paths.is_empty()); - std::fs::write(&file, br#"["/x/a.modelcraft", "/x/b.exe", ""]"#).unwrap(); - assert_eq!(Recent::load(Some(file)).paths, vec!["/x/a.modelcraft".to_string()]); + std::fs::write(&file, br#"["/x/a.fieldcraft", "/x/b.exe", ""]"#).unwrap(); + assert_eq!(Recent::load(Some(file)).paths, vec!["/x/a.fieldcraft".to_string()]); let _ = std::fs::remove_dir_all(&dir); } @@ -934,7 +934,7 @@ mod tests { app.wait_loaded(); assert_eq!(app.status.as_ref().map(|s| s.text.as_str()), Some("Solved 2 steps")); app.logic(&egui::Context::default()); - assert_eq!(app.ui.tab, modelcraft_engine::catalogue::Tab::Results); + assert_eq!(app.ui.tab, fieldcraft_engine::catalogue::Tab::Results); assert!(app.plot().is_some()); // Over unsaved changes, opening a sample asks first. app.run("mesh.node.add", serde_json::json!({"label": 900000, "xyz": [0, 0, 0]})).unwrap(); diff --git a/crates/ui-egui/src/frame.rs b/crates/ui-egui/src/frame.rs index 10e9bf2..9a4c3d8 100644 --- a/crates/ui-egui/src/frame.rs +++ b/crates/ui-egui/src/frame.rs @@ -93,7 +93,7 @@ fn popup( if !open { return None; } - let id = egui::Id::new(("mc_menu", key)); + let id = egui::Id::new(("fc_menu", key)); let shown = egui::Popup::new(id, ui.ctx().clone(), anchor, ui.layer_id()).kind(egui::PopupKind::Menu).open_bool(&mut open).close_behavior(close).show( |ui| { diff --git a/crates/ui-egui/src/gpu.rs b/crates/ui-egui/src/gpu.rs index d0333f6..3c6b2db 100644 --- a/crates/ui-egui/src/gpu.rs +++ b/crates/ui-egui/src/gpu.rs @@ -16,7 +16,7 @@ use std::ops::Range; use std::sync::{Arc, Mutex}; use egui_wgpu::wgpu; -use modelcraft_render::{Rgb, Scene}; +use fieldcraft_render::{Rgb, Scene}; /// Triangle vertex: position (3 × f32), normal (3 × f32), sRGB colour (4 × u8). pub const TRI_SIZE: usize = 28; @@ -141,7 +141,7 @@ fn vs_tri(@location(0) p: vec3, @location(1) n: vec3, @location(2) c: } /// Two-sided lighting: a key light above the eye, a fill from the eye and a small highlight -/// (modelcraft_render::shade computes the same on the CPU). +/// (fieldcraft_render::shade computes the same on the CPU). @fragment fn fs_tri(i: TOut) -> @location(0) vec4 { let v = normalize(u.back.xyz); @@ -235,9 +235,9 @@ pub struct GpuTarget { impl Resources { fn new(device: &wgpu::Device, t: GpuTarget) -> Self { let module = - device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("mc_viewport"), source: wgpu::ShaderSource::Wgsl(SHADER.into()) }); + device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("fc_viewport"), source: wgpu::ShaderSource::Wgsl(SHADER.into()) }); let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("mc_viewport"), + label: Some("fc_viewport"), entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, @@ -246,18 +246,18 @@ impl Resources { }], }); let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("mc_viewport"), + label: Some("fc_viewport"), bind_group_layouts: &[Some(&bgl)], immediate_size: 0, }); let uniform = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("mc_viewport_u"), + label: Some("fc_viewport_u"), size: UNIFORM_SIZE, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let bind = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("mc_viewport"), + label: Some("fc_viewport"), layout: &bgl, entries: &[wgpu::BindGroupEntry { binding: 0, resource: uniform.as_entire_binding() }], }); @@ -294,9 +294,9 @@ impl Resources { }) }; Resources { - tri: pipeline("mc_tris", "vs_tri", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::Less, true), None), + tri: pipeline("fc_tris", "vs_tri", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::Less, true), None), line: pipeline( - "mc_lines", + "fc_lines", "vs_line", "fs_line", line_layout(), @@ -321,8 +321,8 @@ impl Resources { self.tris.clear(); self.lines.clear(); let max = device.limits().max_buffer_size; - self.tris = upload(device, queue, "mc_tris", &sc.tris, TRI_SIZE, 3, max); - self.lines = upload(device, queue, "mc_lines", &sc.lines, LINE_SIZE, 1, max); + self.tris = upload(device, queue, "fc_tris", &sc.tris, TRI_SIZE, 3, max); + self.lines = upload(device, queue, "fc_lines", &sc.lines, LINE_SIZE, 1, max); self.key = Some(key); } } @@ -433,7 +433,7 @@ fn uniform_bytes(cb: &ViewportCallback, linear: bool) -> Vec { #[cfg(test)] mod tests { use egui_wgpu::wgpu::naga; - use modelcraft_render::{SceneLine, ScenePoint, SceneTri, Vec3}; + use fieldcraft_render::{SceneLine, ScenePoint, SceneTri, Vec3}; use super::*; diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index 523da14..79b200b 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -132,7 +132,7 @@ pub fn paint_tinted(app: &mut crate::ModelApp, ui: &egui::Ui, id: &'static str, #[cfg(test)] mod tests { use super::*; - use modelcraft_engine::catalogue::CATALOGUE; + use fieldcraft_engine::catalogue::CATALOGUE; #[test] fn every_catalogue_entry_has_an_icon_and_every_icon_an_entry() { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index ccaebe0..90c92f0 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1,6 +1,6 @@ -//! The ModelCraft egui front end. +//! The FieldCraft egui front end. //! -//! A thin shell over [`modelcraft_engine::Session`]: panels read engine state and act through +//! A thin shell over [`fieldcraft_engine::Session`]: panels read engine state and act through //! commands ([`ModelApp::run`]); nothing here owns model data. The window follows the Craft //! family's layout (SolidCraft, VectorCraft): //! @@ -45,9 +45,9 @@ use std::hash::{Hash, Hasher}; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; -use modelcraft_engine::Session; -use modelcraft_engine::catalogue::{self, Tab, Workspace}; -use modelcraft_render::{Aabb3, Camera, DisplayMesh, Scene, StandardView}; +use fieldcraft_engine::Session; +use fieldcraft_engine::catalogue::{self, Tab, Workspace}; +use fieldcraft_render::{Aabb3, Camera, DisplayMesh, Scene, StandardView}; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; @@ -185,9 +185,9 @@ struct PlotKey { /// The result's identity: every solve stores a new one. result: usize, current: bool, - view: modelcraft_engine::results::ResultsView, + view: fieldcraft_engine::results::ResultsView, /// The mode and field frame drawn (an animated explicit step changes frames). - pick: modelcraft_engine::results::Pick, + pick: fieldcraft_engine::results::Pick, frame: Option, } @@ -227,7 +227,7 @@ pub struct ModelApp { /// Whether results were shown last frame (the Results tab comes up when they appear). results_shown: bool, /// Tessellated design bodies, kept while the same design is open. - bodies: (Option>, Vec), + bodies: (Option>, Vec), /// The frame-independent part of the results plot (scale, contour) and what it was built for. prepared: (Option, results::Prepared), /// The built scene and its key. @@ -274,7 +274,7 @@ pub struct ModelApp { /// The timeline's items and the journal state they were built for. timeline: Option<((u64, usize, (usize, usize)), std::sync::Arc>)>, /// The browser's element counts and the model revision they were counted at. - element_counts: Option<(u64, Vec<(modelcraft_model::ElementKind, usize)>)>, + element_counts: Option<(u64, Vec<(fieldcraft_model::ElementKind, usize)>)>, /// The selected definition's property rows and what they were read for. properties: Option<((u64, browser::Def, String), std::sync::Arc>)>, /// The search's query and its results. @@ -415,7 +415,7 @@ impl ModelApp { }, None => (id, json!({})), }; - let spec = modelcraft_engine::find_command(command).ok_or_else(|| format!("unknown command `{command}`"))?; + let spec = fieldcraft_engine::find_command(command).ok_or_else(|| format!("unknown command `{command}`"))?; if self.file_action(spec.id, &preset, interactive) { self.dialog = None; return Ok(()); @@ -432,7 +432,7 @@ impl ModelApp { /// Open a command's parameter form with `params` filled in. pub fn open_form(&mut self, command: &str, params: Value) { - if let Some(spec) = modelcraft_engine::find_command(command) { + if let Some(spec) = fieldcraft_engine::find_command(command) { self.dialog = Some(ParamsDialog { command: spec.id, text: params.to_string(), error: None }); } } @@ -472,7 +472,7 @@ impl ModelApp { let triple = |v: [f64; 3]| format!("{}, {}, {}", results::number(v[0]), results::number(v[1]), results::number(v[2])); let mut lines = vec![format!("Node {node}")]; if let Some(u) = vector("displacement") { - lines.push(format!("U {} |U| {}", triple(u), results::number(modelcraft_engine::results::magnitude(u)))); + lines.push(format!("U {} |U| {}", triple(u), results::number(fieldcraft_engine::results::magnitude(u)))); } if let Some(rf) = vector("reaction").filter(|r| r.iter().any(|c| *c != 0.0)) { lines.push(format!("RF {}", triple(rf))); @@ -481,13 +481,13 @@ impl ModelApp { lines.push(format!("Von Mises {}", results::number(m))); } if let Some(v) = vector("velocity") { - lines.push(format!("V {} |V| {}", triple(v), results::number(modelcraft_engine::results::magnitude(v)))); + lines.push(format!("V {} |V| {}", triple(v), results::number(fieldcraft_engine::results::magnitude(v)))); } if let Some(shape) = vector("shape") { lines.push(format!("Mode {} shape {}", pick.mode, triple(shape))); } let contour = self.plot().and_then(|p| p.contour.as_ref().map(|c| (c.field, c.at(p.result.nodes(), node)))); - if let Some((field, v)) = contour.filter(|(f, _)| *f != modelcraft_engine::results::Field::VonMises) { + if let Some((field, v)) = contour.filter(|(f, _)| *f != fieldcraft_engine::results::Field::VonMises) { lines.push(format!("{} {}", field.label(), results::number(v))); } self.set_status(lines.join(" "), false); @@ -615,10 +615,10 @@ impl ModelApp { } /// A CPU render of the viewport's scene with the current camera, as PNG bytes. - pub fn render_png(&mut self, w: usize, h: usize) -> Result, modelcraft_render::RenderError> { + pub fn render_png(&mut self, w: usize, h: usize) -> Result, fieldcraft_render::RenderError> { let cam = self.cam; let (_, scene) = self.scene(); - modelcraft_render::render_png(scene, &cam, w, h) + fieldcraft_render::render_png(scene, &cam, w, h) } /// Frame the whole model. @@ -850,12 +850,12 @@ const PERSPECTIVE_FOV: f64 = 0.6; /// A command's name as people see it (its label). pub fn command_label(id: &str) -> &str { - modelcraft_engine::find_command(id).map_or(id, |c| c.label) + fieldcraft_engine::find_command(id).map_or(id, |c| c.label) } #[cfg(test)] mod tests { - use modelcraft_render::Vec3; + use fieldcraft_render::Vec3; use super::*; @@ -910,7 +910,7 @@ mod tests { #[test] fn the_sample_fills_the_view_and_renders() { let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::script()).unwrap(); + app.run_script(&fieldcraft_engine::sample::script()).unwrap(); let d = app.display().clone(); assert!(!d.tris.is_empty() && !d.edges.is_empty()); // Fitted: the model's centre is the camera target. @@ -921,10 +921,10 @@ mod tests { #[test] fn the_open_design_and_imported_surfaces_are_drawn() { - let dir = std::env::temp_dir().join(format!("modelcraft-ui-design-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("fieldcraft-ui-design-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let design = dir.join("plate.solidcraft"); - let bytes = modelcraft_geometry::sample::plate_design(&modelcraft_geometry::sample::Plate::default()).unwrap(); + let bytes = fieldcraft_geometry::sample::plate_design(&fieldcraft_geometry::sample::Plate::default()).unwrap(); std::fs::write(&design, bytes).unwrap(); let mut app = ModelApp::new(Session::default()); app.run("geometry.open", json!({"path": design.to_string_lossy()})).unwrap(); @@ -952,17 +952,17 @@ mod tests { #[test] fn solving_shows_the_result_on_the_results_tab() { let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::cantilever()).unwrap(); + app.run_script(&fieldcraft_engine::sample::cantilever()).unwrap(); app.start("solve.static").unwrap(); app.run_dialog().unwrap(); let view = app.session.results_view.clone().unwrap(); - assert_eq!((view.step.as_str(), view.contour, view.deformed), ("Load", Some(modelcraft_engine::results::Field::VonMises), true)); + assert_eq!((view.step.as_str(), view.contour, view.deformed), ("Load", Some(fieldcraft_engine::results::Field::VonMises), true)); app.logic(&egui::Context::default()); assert_eq!(app.ui.tab, Tab::Results); app.run("solve.frequency", json!({})).unwrap(); app.run("results.deformed", json!({"step": "Modes"})).unwrap(); let view = app.session.results_view.clone().unwrap(); - assert_eq!((view.step.as_str(), view.mode, view.contour), ("Modes", 1, Some(modelcraft_engine::results::Field::Displacement))); + assert_eq!((view.step.as_str(), view.mode, view.contour), ("Modes", 1, Some(fieldcraft_engine::results::Field::Displacement))); // The plot draws contour bands: more triangles than the model's faces. let faces = app.display().tris.len(); assert!(app.scene().1.tris.len() > faces); @@ -984,7 +984,7 @@ mod tests { let (w, h) = (400, 200); let cam = app.cam; let radius = app.scene().1.radius; - let canvas = modelcraft_render::render(app.scene().1, &cam, w, h).unwrap(); + let canvas = fieldcraft_render::render(app.scene().1, &cam, w, h).unwrap(); let pixel = |p: Vec3| { let (x, y, _) = cam.to_screen(p, w as f64, h as f64, radius).unwrap(); let i = (y as usize * w + x as usize) * 4; @@ -1027,13 +1027,13 @@ mod tests { #[test] fn explicit_frames_are_drawn_and_animated() { let mut app = ModelApp::new(Session::default()); - app.run_script(&modelcraft_engine::sample::impact()).unwrap(); + app.run_script(&fieldcraft_engine::sample::impact()).unwrap(); app.run("solve.explicit", json!({})).unwrap(); // The last frame at true scale: the bar has moved back off the wall's side (x < 0 is // the wall, so nothing passes it) and carries stress contours. let last = app.plot().unwrap(); assert_eq!(last.frame().map(|(k, _, n)| (k, n)), Some((10, 11))); - assert!(last.contour.as_ref().is_some_and(|c| c.field == modelcraft_engine::results::Field::VonMises)); + assert!(last.contour.as_ref().is_some_and(|c| c.field == fieldcraft_engine::results::Field::VonMises)); assert!(last.display.bounds.min.x > -1e-6, "{:?}", last.display.bounds); // Frame 0 is the undeformed bar at rest on its initial velocity. app.run("results.frame", json!({"frame": 0})).unwrap(); diff --git a/crates/ui-egui/src/palette.rs b/crates/ui-egui/src/palette.rs index efbb8ed..3347360 100644 --- a/crates/ui-egui/src/palette.rs +++ b/crates/ui-egui/src/palette.rs @@ -7,7 +7,7 @@ //! `crates/ui-egui/src/palette.rs` (MIT OR Apache-2.0), see ATTRIBUTION.md. use egui::{Align2, FontId, Rect, RichText, Sense, Vec2, pos2, vec2}; -use modelcraft_engine::catalogue::{self, CATALOGUE}; +use fieldcraft_engine::catalogue::{self, CATALOGUE}; use serde_json::Value; use crate::ModelApp; @@ -80,7 +80,7 @@ pub fn submit(app: &mut ModelApp, line: &str) { Some(i) => (line.get(..i).unwrap_or(line), line.get(i..).unwrap_or("").trim()), None => (line, ""), }; - if modelcraft_engine::find_command(id).is_some() || catalogue::find(id).is_some() { + if fieldcraft_engine::find_command(id).is_some() || catalogue::find(id).is_some() { if rest.is_empty() { let _ = app.start(id); } else { @@ -104,7 +104,7 @@ pub fn submit(app: &mut ModelApp, line: &str) { /// The command line, the status and the model's size, above the timeline. pub fn command_line(app: &mut ModelApp, ui: &mut egui::Ui) { let t = Tokens::get(app.ui.dark); - egui::Panel::bottom("mc_command_line") + egui::Panel::bottom("fc_command_line") .exact_size(HEIGHT) .frame(egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(8, 3))) .show(ui, |ui| { @@ -113,7 +113,7 @@ pub fn command_line(app: &mut ModelApp, ui: &mut egui::Ui) { ui.label(RichText::new("›").strong().color(t.accent)); let r = ui.add( egui::TextEdit::singleline(&mut app.command_line) - .id(egui::Id::new("mc_command_line_text")) + .id(egui::Id::new("fc_command_line_text")) .hint_text(r#"Type a command, e.g. mesh.node.add {"label": 1, "xyz": [0, 0, 0]} — or press S to search"#) .desired_width(440.0), ); @@ -149,7 +149,7 @@ pub fn popup(app: &mut ModelApp, ctx: &egui::Context) { let mut open = true; let center = ctx.content_rect().center(); let r = crate::frame::window(ctx, "Search commands", crate::frame::Width::Normal) - .id(egui::Id::new("mc_search")) + .id(egui::Id::new("fc_search")) .pivot(Align2::CENTER_TOP) .fixed_pos(pos2(center.x, 140.0)) .fixed_size(vec2(440.0, 380.0)) @@ -157,7 +157,7 @@ pub fn popup(app: &mut ModelApp, ctx: &egui::Context) { .show(ctx, |ui| { let r = ui.add( egui::TextEdit::singleline(&mut app.palette_text) - .id(egui::Id::new("mc_search_text")) + .id(egui::Id::new("fc_search_text")) .hint_text("command name, id or what it does") .desired_width(f32::INFINITY), ); @@ -221,7 +221,7 @@ pub fn popup(app: &mut ModelApp, ctx: &egui::Context) { #[cfg(test)] mod tests { - use modelcraft_engine::Session; + use fieldcraft_engine::Session; use super::*; diff --git a/crates/ui-egui/src/results.rs b/crates/ui-egui/src/results.rs index 0a8315c..3ad29ad 100644 --- a/crates/ui-egui/src/results.rs +++ b/crates/ui-egui/src/results.rs @@ -1,17 +1,17 @@ //! The results display in the viewport: what the engine's [`ResultsView`] asks for, drawn on the //! model's display data. The deformed shape moves every element by its nodes' displacements //! (or a mode shape) times the scale; contours split the faces into flat-coloured bands -//! ([`modelcraft_render::contour`]); animation steps the deformation through a fixed number of +//! ([`fieldcraft_render::contour`]); animation steps the deformation through a fixed number of //! frames per cycle (an explicit step's animation runs through its field frames, contours //! following). A plot of a stale result (the model changed since the solve) is drawn the //! same way and marked. use std::sync::Arc; -use modelcraft_engine::results::{Field, Kind, Pick, ResultsView, displacements, field_values, magnitude, range}; -use modelcraft_engine::{Session, StepResult}; -use modelcraft_render::contour::{Bands, band_color, split_triangle}; -use modelcraft_render::{DisplayMesh, Scene, SceneTri, Vec3}; +use fieldcraft_engine::results::{Field, Kind, Pick, ResultsView, displacements, field_values, magnitude, range}; +use fieldcraft_engine::{Session, StepResult}; +use fieldcraft_render::contour::{Bands, band_color, split_triangle}; +use fieldcraft_render::{DisplayMesh, Scene, SceneTri, Vec3}; use crate::Style; use crate::theme::{Tokens, rgb}; @@ -227,7 +227,7 @@ pub(crate) mod tests { /// The sample cantilever, solved (static step `Load`, frequency step `Modes`). pub(crate) fn solved_cantilever() -> Session { let mut s = Session::default(); - s.run_script(&modelcraft_engine::sample::cantilever()).unwrap(); + s.run_script(&fieldcraft_engine::sample::cantilever()).unwrap(); s.execute("solve.static", &json!({})).unwrap(); s.execute("solve.frequency", &json!({})).unwrap(); s diff --git a/crates/ui-egui/src/results_panel.rs b/crates/ui-egui/src/results_panel.rs index 2d8e339..5d914b9 100644 --- a/crates/ui-egui/src/results_panel.rs +++ b/crates/ui-egui/src/results_panel.rs @@ -3,8 +3,8 @@ //! panel (right), which run the `results.*` commands. use egui::{Align2, Color32, FontId, Rect, RichText, Stroke, pos2, vec2}; -use modelcraft_engine::results::{Field, Kind}; -use modelcraft_explicit::{Energies, ExplicitResult}; +use fieldcraft_engine::results::{Field, Kind}; +use fieldcraft_explicit::{Energies, ExplicitResult}; use serde_json::{Value, json}; use crate::ModelApp; @@ -76,7 +76,7 @@ fn legend(plot: &Plot, painter: &egui::Painter, rect: Rect, t: &Tokens) { for k in (0..l.bands.count).rev() { let row = (l.bands.count - 1 - k) as f32; let r = Rect::from_min_size(pos2(origin.x + 10.0, y + row * SWATCH.y), SWATCH); - let c = modelcraft_render::contour::band_color(&stops, k, l.bands.count); + let c = fieldcraft_render::contour::band_color(&stops, k, l.bands.count); painter.rect_filled(r, 0.0, Color32::from_rgb(c.0, c.1, c.2)); if k + 1 == l.bands.count { painter.text(pos2(r.right() + 6.0, r.top()), Align2::LEFT_CENTER, number(l.bands.level(k + 1)), font.clone(), t.overlay_text); @@ -111,9 +111,9 @@ fn probe(app: &ModelApp, plot: &Plot, painter: &egui::Painter, t: &Tokens) { pub fn controls(app: &mut ModelApp, plot: &Plot, ui: &mut egui::Ui, t: &Tokens) { let mut run: Vec<(&'static str, Value)> = Vec::new(); let steps: Vec<(String, bool)> = app.session.result_steps().map(|(n, c)| (n.to_string(), c)).collect(); - egui::Grid::new("mc_results_grid").num_columns(2).spacing(vec2(8.0, 4.0)).show(ui, |ui| { + egui::Grid::new("fc_results_grid").num_columns(2).spacing(vec2(8.0, 4.0)).show(ui, |ui| { ui.label("Step"); - egui::ComboBox::from_id_salt("mc_results_step").selected_text(&plot.view.step).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("fc_results_step").selected_text(&plot.view.step).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| { for (name, current) in &steps { let label = if *current { name.clone() } else { format!("{name} (stale)") }; if ui.selectable_label(*name == plot.view.step, label).clicked() && *name != plot.view.step { @@ -124,7 +124,7 @@ pub fn controls(app: &mut ModelApp, plot: &Plot, ui: &mut egui::Ui, t: &Tokens) ui.end_row(); ui.label("Contours"); let shown = plot.view.contour.map_or("None", Field::label); - egui::ComboBox::from_id_salt("mc_results_field").selected_text(shown).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("fc_results_field").selected_text(shown).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| { if ui.selectable_label(plot.view.contour.is_none(), "None").clicked() { run.push(("results.contour", json!({"field": "none"}))); } @@ -165,7 +165,7 @@ pub fn controls(app: &mut ModelApp, plot: &Plot, ui: &mut egui::Ui, t: &Tokens) } else if let Kind::Frequency(f) = plot.result.kind() { ui.separator(); ui.label(RichText::new("Modes").color(t.text_dim)); - egui::ScrollArea::vertical().id_salt("mc_results_modes").max_height(150.0).show(ui, |ui| { + egui::ScrollArea::vertical().id_salt("fc_results_modes").max_height(150.0).show(ui, |ui| { for (i, m) in f.modes.iter().enumerate() { let mode = i + 1; if ui.selectable_label(plot.view.mode == mode, format!("{mode} {} Hz", number(m.frequency))).clicked() { @@ -236,7 +236,7 @@ fn explicit(ui: &mut egui::Ui, e: &ExplicitResult, plot: &Plot, t: &Tokens, run: painter.line_segment([pos2(x, rect.top()), pos2(x, rect.bottom())], Stroke::new(1.0, t.text_dim)); let i = h.time.iter().enumerate().min_by(|a, b| (a.1 - now).abs().total_cmp(&(b.1 - now).abs())).map_or(0, |(i, _)| i); let Some(at_now) = h.energies.get(i) else { return }; - egui::Grid::new("mc_results_energies").num_columns(2).spacing(vec2(8.0, 2.0)).show(ui, |ui| { + egui::Grid::new("fc_results_energies").num_columns(2).spacing(vec2(8.0, 2.0)).show(ui, |ui| { for (name, color, f) in &series { ui.label(RichText::new(*name).color(*color)); ui.label(number(f(at_now))); @@ -257,7 +257,7 @@ fn reactions_table(ui: &mut egui::Ui, r: &Value, t: &Tokens) { ui.label(f(2)); ui.end_row(); }; - egui::Grid::new("mc_results_reactions").num_columns(4).spacing(vec2(6.0, 2.0)).show(ui, |ui| { + egui::Grid::new("fc_results_reactions").num_columns(4).spacing(vec2(6.0, 2.0)).show(ui, |ui| { for h in ["Set", "Fx", "Fy", "Fz"] { ui.label(RichText::new(h).small().color(t.text_dim)); } diff --git a/crates/ui-egui/src/scene.rs b/crates/ui-egui/src/scene.rs index 23cc336..0a200be 100644 --- a/crates/ui-egui/src/scene.rs +++ b/crates/ui-egui/src/scene.rs @@ -5,7 +5,7 @@ //! the model has no element faces; once it is meshed, as boundaries only, so the mesh is not //! hidden behind coincident faces. -use modelcraft_render::{Aabb3, Camera, DisplayMesh, Scene, SceneLine, ScenePoint, SceneTri}; +use fieldcraft_render::{Aabb3, Camera, DisplayMesh, Scene, SceneLine, ScenePoint, SceneTri}; use serde::{Deserialize, Serialize}; use crate::theme::{Tokens, rgb}; @@ -99,7 +99,7 @@ pub fn radius(b: &Aabb3, cam: &Camera) -> f64 { #[cfg(test)] mod tests { - use modelcraft_model::{ElementKind, Model}; + use fieldcraft_model::{ElementKind, Model}; use super::*; diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 1d203a2..62bef66 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -6,8 +6,8 @@ //! ATTRIBUTION.md. Widget states are coloured with craft-ui's `WidgetPalette`. use egui::{Color32, Stroke}; -use modelcraft_model::ElementKind; -use modelcraft_render::Rgb; +use fieldcraft_model::ElementKind; +use fieldcraft_render::Rgb; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Tokens { diff --git a/crates/ui-egui/src/timeline.rs b/crates/ui-egui/src/timeline.rs index 1651e36..8a35629 100644 --- a/crates/ui-egui/src/timeline.rs +++ b/crates/ui-egui/src/timeline.rs @@ -123,7 +123,7 @@ fn transport_glyph(p: &egui::Painter, c: egui::Pos2, what: &str, col: Color32) { pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) { let t = Tokens::get(app.ui.dark); let mut roll: Option = None; - egui::Panel::bottom("mc_timeline").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.timeline)).show(ui, |ui| { + egui::Panel::bottom("fc_timeline").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.timeline)).show(ui, |ui| { let r = ui.max_rect(); app.place("timeline", r); let p = ui.painter().clone(); @@ -140,7 +140,7 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) { ] { let br = Rect::from_center_size(pos2(x + 11.0, r.center().y), Vec2::splat(22.0)); let enabled = target != done && target >= oldest && target <= done + redo; - let resp = ui.interact(br, ui.id().with(("mc_transport", what)), if enabled { Sense::click() } else { Sense::hover() }); + let resp = ui.interact(br, ui.id().with(("fc_transport", what)), if enabled { Sense::click() } else { Sense::hover() }); if resp.hovered() && enabled { p.rect_filled(br, 3.0, t.hover); } @@ -165,7 +165,7 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) { return; } ui.scope_builder(UiBuilder::new().max_rect(strip), |ui| { - egui::ScrollArea::horizontal().id_salt("mc_timeline_scroll").stick_to_right(true).auto_shrink([false, false]).show(ui, |ui| { + egui::ScrollArea::horizontal().id_salt("fc_timeline_scroll").stick_to_right(true).auto_shrink([false, false]).show(ui, |ui| { let marker_at = list.iter().position(|i| i.start >= done).unwrap_or(list.len()); let width = list.len() as f32 * STRIDE + 12.0; let (area, _) = ui.allocate_exact_size(vec2(width, r.height()), Sense::hover()); @@ -185,10 +185,10 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) { } let undone = item.start >= done; let reachable = item.end >= oldest; - let resp = ui.interact(br, ui.id().with(("mc_journal", k)), if reachable { Sense::click() } else { Sense::hover() }); + let resp = ui.interact(br, ui.id().with(("fc_journal", k)), if reachable { Sense::click() } else { Sense::hover() }); let bg = if resp.hovered() && reachable { t.hover } else { t.timeline_item }; p.rect(br, 3.0, bg, Stroke::new(1.0, t.border), egui::StrokeKind::Inside); - let icon = modelcraft_engine::catalogue::entry_of(&item.command).map_or("ui.views", |c| c.id); + let icon = fieldcraft_engine::catalogue::entry_of(&item.command).map_or("ui.views", |c| c.id); let tint = if undone || !reachable { Color32::from_white_alpha(110) } else { Color32::WHITE }; icons::paint_tinted(app, ui, icon, br.shrink(4.0), Look::of(!undone), tint); if item.count() > 1 { @@ -222,7 +222,7 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) { #[cfg(test)] mod tests { - use modelcraft_engine::Session; + use fieldcraft_engine::Session; use super::*; diff --git a/crates/ui-egui/src/titlebar.rs b/crates/ui-egui/src/titlebar.rs index f16566e..eabd01c 100644 --- a/crates/ui-egui/src/titlebar.rs +++ b/crates/ui-egui/src/titlebar.rs @@ -170,7 +170,7 @@ pub fn resize_zones(ui: &mut Ui) { #[cfg(test)] mod tests { use egui::{Event, Pos2, RawInput, ViewportId}; - use modelcraft_engine::Session; + use fieldcraft_engine::Session; use super::*; diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index 754c15e..9aed1de 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -1,5 +1,5 @@ //! The ribbon: the workspace switcher, the workspace's tabs, and the current tab's panels, all -//! from the capability catalogue (`modelcraft_engine::catalogue`). +//! from the capability catalogue (`fieldcraft_engine::catalogue`). //! //! Each panel shows its ready entries as icon buttons (up to [`MAX_BUTTONS`]; a panel with none //! ready shows its first entry, greyed with its milestone) over its label with a caret. The label @@ -13,7 +13,7 @@ //! (MIT OR Apache-2.0), see ATTRIBUTION.md; the tabs are craft-ui `Tab`s. use egui::{Align2, Color32, FontId, Rect, Sense, Stroke, Vec2, pos2, vec2}; -use modelcraft_engine::catalogue::{self, Capability, Tab, Workspace}; +use fieldcraft_engine::catalogue::{self, Capability, Tab, Workspace}; use serde::Serialize; use crate::ModelApp; @@ -115,7 +115,7 @@ fn badge(p: &egui::Painter, t: &Tokens, corner: egui::Pos2, m: &str) { pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) { let t = Tokens::get(app.ui.dark); let mut start: Option<&'static str> = None; - egui::Panel::top("mc_toolbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.toolbar)).show(ui, |ui| { + egui::Panel::top("fc_toolbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.toolbar)).show(ui, |ui| { let r = ui.max_rect(); app.place("ribbon", r); app.toolbar_rects.clear(); @@ -129,7 +129,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) { let w = 12.0 + painter.layout_no_wrap(label.clone(), label_font(ui.ctx(), 12.0), t.text).size().x.max(28.0); let tr = Rect::from_min_size(pos2(x, r.top() + 2.0), vec2(w, 22.0)); let active = app.ui.tab == tab; - let resp = craft_ui::tabs::Tab::new(egui::Id::new(("mc_tab", panel_key(tab, ""))), &label, active) + let resp = craft_ui::tabs::Tab::new(egui::Id::new(("fc_tab", panel_key(tab, ""))), &label, active) .focus_stroke(Stroke::new(1.0, t.accent)) .show_at(ui, tr, |ui, resp| { let p = ui.painter(); @@ -179,7 +179,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) { if collapsed { // One button: the panel's first button's icon over a caret; it opens the drop-down. let br = Rect::from_center_size(pos2(px + width / 2.0, top + 21.0), Vec2::splat(BUTTON)); - let resp = ui.interact(br, ui.id().with(("mc_collapsed", &key)), Sense::click()); + let resp = ui.interact(br, ui.id().with(("fc_collapsed", &key)), Sense::click()); if resp.hovered() { painter.rect_filled(br, 4.0, t.hover); } @@ -232,7 +232,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) { } else { Rect::from_center_size(pos2(px + width / 2.0, top + 51.0), vec2(width, lh)) }; - let lresp = ui.interact(lr, ui.id().with(("mc_panel", &key)), Sense::click()); + let lresp = ui.interact(lr, ui.id().with(("fc_panel", &key)), Sense::click()); let open = app.ui.menu.as_deref() == Some(key.as_str()); if lresp.hovered() || open { painter.rect_filled(lr, 3.0, t.hover); @@ -253,7 +253,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) { } if !overflow.is_empty() { let mr = Rect::from_min_size(pos2(px.min(r.right() - MORE_WIDTH), top + 4.0), vec2(MORE_WIDTH - 2.0, 52.0)); - let resp = ui.interact(mr, ui.id().with("mc_more"), Sense::click()); + let resp = ui.interact(mr, ui.id().with("fc_more"), Sense::click()); if resp.hovered() || app.ui.menu.as_deref() == Some("more") { painter.rect_filled(mr, 4.0, t.hover); } @@ -281,7 +281,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) { /// it opens the list of workspaces. fn workspace_switcher(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, r: Rect) { let ws = Rect::from_min_max(pos2(r.left() + 6.0, r.top() + 4.0), pos2(r.left() + 104.0, r.bottom() - 5.0)); - let resp = ui.interact(ws, ui.id().with("mc_workspace"), Sense::click()); + let resp = ui.interact(ws, ui.id().with("fc_workspace"), Sense::click()); let open = app.ui.menu.as_deref() == Some("workspace"); ui.painter().rect(ws, 4.0, if resp.hovered() || open { t.hover } else { t.field }, Stroke::new(1.0, t.border), egui::StrokeKind::Inside); let w = app.ui.workspace; @@ -320,7 +320,7 @@ fn workspace_switcher(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, r: Rect /// One entry's button; returns the id to start when it was clicked. fn button(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, c: &'static Capability, br: Rect) -> Option<&'static str> { let enabled = c.command().is_some(); - let resp = ui.interact(br, ui.id().with(("mc_button", c.id)), if enabled { Sense::click() } else { Sense::hover() }); + let resp = ui.interact(br, ui.id().with(("fc_button", c.id)), if enabled { Sense::click() } else { Sense::hover() }); app.toolbar_rects.push((c.id, br, Fit::Full)); let running = app.dialog.as_ref().is_some_and(|d| c.command() == Some(d.command)); if running { diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index 1cadcbe..c431efc 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -7,7 +7,7 @@ //! follow SolidCraft's `viewport.rs` (MIT OR Apache-2.0), see ATTRIBUTION.md. use egui::{Color32, Pos2, Rect, Sense, Stroke, Vec2, pos2, vec2}; -use modelcraft_render::{StandardView, Vec3}; +use fieldcraft_render::{StandardView, Vec3}; use crate::gpu::{GpuScene, ViewportCallback}; use crate::icons::{self, Look}; @@ -144,14 +144,14 @@ fn paint_gpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: R fn paint_cpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: Rect) { let ppp = ui.ctx().pixels_per_point(); // A window larger than the renderer's limit is drawn smaller and scaled up. - let (w, h) = modelcraft_render::fit_size((rect.width() * ppp) as usize, (rect.height() * ppp) as usize); + let (w, h) = fieldcraft_render::fit_size((rect.width() * ppp) as usize, (rect.height() * ppp) as usize); let size = [w, h]; let cam = app.cam; let key = app.scene().0; let fresh = app.viewport.cpu.as_ref().is_some_and(|(_, k, c, s)| *k == key && *c == cam && *s == size); // A scene the renderer refused is reported once and not retried until it changes. if !fresh && app.viewport.cpu_failed != Some(key) { - let c = match modelcraft_render::render(&app.scene.1, &cam, w, h) { + let c = match fieldcraft_render::render(&app.scene.1, &cam, w, h) { Ok(c) => c, Err(e) => { app.viewport.cpu = None; @@ -167,7 +167,7 @@ fn paint_cpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: R tex.set(image, egui::TextureOptions::LINEAR); tex } - None => ui.ctx().load_texture("mc_viewport_cpu", image, egui::TextureOptions::LINEAR), + None => ui.ctx().load_texture("fc_viewport_cpu", image, egui::TextureOptions::LINEAR), }; app.viewport.cpu = Some((tex, key, cam, size)); } @@ -184,7 +184,7 @@ const AXIS_RADIUS: f32 = 11.0; fn view_along(d: Vec3) -> StandardView { let mut best = (StandardView::Front, f64::NEG_INFINITY); for v in StandardView::ALL.into_iter().filter(|v| *v != StandardView::Iso) { - let mut c = modelcraft_render::Camera::default(); + let mut c = fieldcraft_render::Camera::default(); c.set_view(v); let score = c.back().dot(d); if score > best.1 { @@ -231,7 +231,7 @@ fn axis_view(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) { painter.circle_stroke(*at, AXIS_RADIUS, Stroke::new(if hot { 2.0 } else { 1.0 }, if hot { t.accent } else { ink })); painter.text(*at, egui::Align2::CENTER_CENTER, *name, egui::FontId::proportional(11.0), ink); } - let resp = ui.interact(area, ui.id().with("mc_axis_view"), Sense::click()); + let resp = ui.interact(area, ui.id().with("fc_axis_view"), Sense::click()); if let Some(i) = hovered && let Some((name, d, ..)) = tips.get(i) { @@ -242,7 +242,7 @@ fn axis_view(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) { } } let home = Rect::from_center_size(pos2(c.x - 44.0, c.y - 44.0), Vec2::splat(18.0)); - let hr = ui.interact(home, ui.id().with("mc_axis_home"), Sense::click()); + let hr = ui.interact(home, ui.id().with("fc_axis_home"), Sense::click()); if hr.hovered() { painter.rect_filled(home.expand(2.0), 3.0, t.hover); } @@ -276,7 +276,7 @@ fn nav_bar(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) { let mut style_button = None; for (i, (key, icon, tip)) in NAV.iter().enumerate() { let br = Rect::from_min_size(pos2(bar.left() + 5.0 + i as f32 * 30.0, bar.top() + 2.0), Vec2::splat(26.0)); - let resp = ui.interact(br, ui.id().with(("mc_nav", *key)), Sense::click()); + let resp = ui.interact(br, ui.id().with(("fc_nav", *key)), Sense::click()); let active = match *key { "orbit" => app.nav == NavMode::Orbit, "pan" => app.nav == NavMode::Pan, diff --git a/docs/adr/0001-libraries.md b/docs/adr/0001-libraries.md index eb02f63..727d4f8 100644 --- a/docs/adr/0001-libraries.md +++ b/docs/adr/0001-libraries.md @@ -4,29 +4,29 @@ Status: accepted (2026-10-13) ## Context -ModelCraft is MIT OR Apache-2.0. Everything it links must allow that, and the meshing and solver +FieldCraft is MIT OR Apache-2.0. Everything it links must allow that, and the meshing and solver algorithms, where the obvious open-source implementations are GPL-family, must come from papers. ## Decision **Geometry: SolidCraft**, as git dependencies pinned by rev (`solidcraft-kernel`, `solidcraft-geom`, `solidcraft-doc`, `solidcraft-io`; MIT OR Apache-2.0, built on the Apache-2.0 -truck crates). SolidCraft is the primary geometry source: ModelCraft opens SolidCraft designs and +truck crates). SolidCraft is the primary geometry source: FieldCraft opens SolidCraft designs and meshes from SolidCraft's tessellation, and reads face-tagged meshes SolidCraft exports. Other CAD exchange files (STEP, which SolidCraft's kernel already reads) can be added as secondary inputs -through the same crate. Only `modelcraft-geometry` may depend on SolidCraft. +through the same crate. Only `fieldcraft-geometry` may depend on SolidCraft. **Meshing: our own**, implemented from published papers (`docs/meshing/literature.md`). Robust predicates follow Shewchuk's adaptive-precision arithmetic (the paper; his reference `predicates.c` is public domain). A permissive crate may be adopted instead after review; record -it here. *Decided 2026-10-13:* our own implementation, `modelcraft_mesh::predicates`, written from +it here. *Decided 2026-10-13:* our own implementation, `fieldcraft_mesh::predicates`, written from Shewchuk 1997, Devillers–Teillaud 2011 and Attene 2020 (no `unsafe`, deterministic, wasm-safe, a validated coordinate domain instead of Shewchuk's overflow/underflow caveat, and indirect predicates that `robust` lacks). `robust` (georust, MIT OR Apache-2.0) and `num-bigint` / `num-rational` / `num-traits` (MIT OR Apache-2.0) are dev-dependencies only: differential test -oracles, never linked into ModelCraft. +oracles, never linked into FieldCraft. -**Linear algebra: our own.** *Decided 2026-10-13 (implicit solver task):* `modelcraft-solver` +**Linear algebra: our own.** *Decided 2026-10-13 (implicit solver task):* `fieldcraft-solver` has its own sparse symmetric storage, nested-dissection ordering (George 1973, by coordinate bisection), up-looking sparse Cholesky (Davis 2006, elimination trees per Liu 1990) and shift-invert block Lanczos with full reorthogonalization (Ericsson–Ruhe 1980, Grimes–Lewis–Simon @@ -44,7 +44,7 @@ parallelism task):* the factorization is now our own supernodal left-looking Cho 1994), multithreaded with a fixed operation order (below). Its rounding differs from the old up-looking factor in the last digits; every oracle fixture stays within its band. -**Threads: rayon** (MIT OR Apache-2.0), in `modelcraft-parallel` only (an L0 crate with no +**Threads: rayon** (MIT OR Apache-2.0), in `fieldcraft-parallel` only (an L0 crate with no workspace dependencies; *decided 2026-10-13*). The solvers keep the bit-for-bit rule on any number of threads by construction: work is cut into blocks whose sizes are constants of the algorithm, never derived from the thread count; each floating-point value is computed by one task @@ -57,9 +57,9 @@ per time increment (10⁵–10⁷ increments), so a persistent pool is needed, a re-implement rayon's. rayon was already in the shipped dependency tree (THIRD-PARTY-LICENSES.txt unchanged). In the browser build (`wasm32`) the crate does not depend on rayon and runs the same blocks one after another, with the same results. The thread count is a session and `solve.*` -parameter (`threads`, 1–256) whose default is `MODELCRAFT_THREADS` or the available cores. Cost if +parameter (`threads`, 1–256) whose default is `FIELDCRAFT_THREADS` or the available cores. Cost if this is wrong: rayon's pool is a process-wide resource and nested pools add latency; replacing it -touches only `modelcraft-parallel`. +touches only `fieldcraft-parallel`. **Allowed:** MIT, BSD-2/3, Apache-2.0, ISC, Zlib, Unicode, MPL-2.0 (as an unmodified dependency), CC0 and public domain. @@ -106,8 +106,8 @@ Thingi10K is not used either (owner decision 2026-10-13: authoring tools unrecor corpus is our own:** parts built with SolidCraft (scripts or designs authored in SolidCraft, committed as their recipes so the geometry is regenerated, not stored) and analytic shapes. Recipes are SolidCraft command scripts run through `solidcraft-engine` (same pinned rev), a -dependency of `modelcraft-geometry` only behind its non-default `scripts` feature, which the test -tooling (`modelcraft-testkit`) enables; the shipped library opens designs without it. +dependency of `fieldcraft-geometry` only behind its non-default `scripts` feature, which the test +tooling (`fieldcraft-testkit`) enables; the shipped library opens designs without it. **Commercial products** (Abaqus, Nastran, LS-DYNA, LS-PrePost, HyperMesh, ANSA, Femap, Patran, ANSYS) are never run or scripted, and their input files, text and figures are never copied. Deck @@ -120,5 +120,5 @@ docs/verification.md). - The tet and hex meshers are significant original work; the literature survey decides the order and the methods. -- SolidCraft and ModelCraft move together: bumping the pinned rev bumps the repeated +- SolidCraft and FieldCraft move together: bumping the pinned rev bumps the repeated `[patch.crates-io]` entries in the same commit. diff --git a/docs/architecture.md b/docs/architecture.md index 8fd5b69..4df04e0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture -ModelCraft turns geometry into finite element models, writes them for the solvers engineers +FieldCraft turns geometry into finite element models, writes them for the solvers engineers already use, and solves linear problems itself. ``` @@ -18,33 +18,33 @@ crates in strictly lower layers. | Layer | Crate | Role | Status | |---|---|---|---| -| L0 | `modelcraft-model` | Solver-neutral FE model: nodes and elements by label, their geometry tags, sets, materials, sections, couplings, BCs, loads and steps, the model check and the resolved view the writers read. Knows no file format and no geometry type. | started | -| L0 | `modelcraft-parallel` | Deterministic data parallelism for the solvers: fixed work partitions on a rayon pool, results in block order, the thread-count setting (`MODELCRAFT_THREADS`, `threads`); sequential in the browser. No workspace dependencies. | started | -| L1 | `modelcraft-geometry` | The only door to SolidCraft: opens `.solidcraft` designs (rebuilt from their timeline, bodies with persistent face names), B-rep bodies, measures, tessellation into a face-tagged `SurfaceMesh`; face-tagged 3MF files read into the same type without the kernel (docs/formats/tagged-mesh.md). No SolidCraft type in its API. | started | -| L1 | `modelcraft-mesh` | Surface preparation (weld, watertightness, feature edges), robust predicates, Delaunay tetrahedralization and refinement, Voronoi-based optimization, hex meshing, quality metrics. | started | -| L1 | `modelcraft-fem` | Element kernels shared by every structural solver: the element table (tet4, tet10, wedge6, hex8 full and incompatible modes, hex20 full and reduced), shape functions, quadrature, element stiffness, consistent and lumped mass, body and pressure loads, stresses and their extrapolation, isotropic and orthotropic elasticity, the uniform-strain (one-point) hex8 and tet4 kinematics, hourglass shape vectors and element frequency bounds of the explicit solver, objective stress rates and J2/Johnson–Cook return mapping. Pure functions, no solver loop. | started | -| L1 | `modelcraft-decks` | Abaqus input, Nastran bulk data and LS-DYNA keyword readers and writers, to and from `model`. Text out, no file system; `deck.write` saves it. | started | -| L1 | `modelcraft-render` | Display data without a GPU dependency: the visible faces, element edges and nodes of a model, the orbit camera, a CPU rasterizer for headless snapshots, geometry surfaces with their face boundaries, deformed shapes and banded contour plots of nodal results, picking. | started | -| L2 | `modelcraft-solver` | Implicit solver: assembly from the resolved model, boundary conditions, sparse direct solution, linear static and modal analysis, stresses and reactions. | started | -| L2 | `modelcraft-explicit` | Explicit dynamics: central-difference time integration with lumped mass, one-point hex8 with hourglass control, nodal-pressure tet4, bulk viscosity, rigid walls, large deformation with objective rates, plasticity, damage and erosion, energy account, histories and field frames. | started | -| L2 | `modelcraft-lbm` | Lattice Boltzmann flow solver on the CPU: D3Q19 two-relaxation-time scheme, bounce-back and slip walls, Zou–He inlets and outlets, obstacles voxelised from the face-tagged surface with exact predicates, momentum-exchange forces per face. Uses `mesh` for the predicates and the boundary surface. | started | -| L3 | `modelcraft-fsi` | Fluid–structure coupling between the L2 solvers (later). | planned | -| L4 | `modelcraft-doc` | The document: geometry, mesh, model, results, history. | planned | -| L5 | `modelcraft-io` | `.modelcraft` files (docs/formats/modelcraft.md): text model definition, binary mesh, geometry references with content hashes, the journal; atomic saves; autosave storage for crash recovery; read and write roots that confine an automation client's file access (`roots`). Bytes in, bytes out; the file system only in `fs`, `recovery` and `roots`. Result files later. | started | -| L6 | `modelcraft-engine` | Commands (`CommandSpec`), `Session::execute` with rollback and the panic guard, scripts; every file a command names checked against the session's roots (`Session::file_access`); headless pictures of the session (`view`: model, geometry, deformed shape and contours on the CPU rasterizer). | started | -| L7 | `modelcraft-ui-egui`, `modelcraft-mcp` | Desktop UI in the Craft family's layout (title bar with quick actions, workspace ribbon from the catalogue, browser, viewport with navigation bar and orientation gizmo, context panel, command search and command line, journal timeline; native file pickers as host services, background opening and solving; results display with legend and probe; offscreen capture of the whole window) and control channel requests (docs/control-protocol.md); MCP server over stdio (docs/mcp.md): tools over engine commands, a headless session or the running app through its control channel, read and write roots. | ui, mcp started | -| testkit | `modelcraft-testkit` | Test tooling, a dev-dependency only: the meshing test corpus (`tests/corpus/*/recipe.json`, analytic shapes) and its runner (`cargo xtask corpus`). | started | -| apps | `modelcraft`, `modelcraft-cli`, `modelcraft-web` | Desktop, command line, browser. | desktop, cli started | +| L0 | `fieldcraft-model` | Solver-neutral FE model: nodes and elements by label, their geometry tags, sets, materials, sections, couplings, BCs, loads and steps, the model check and the resolved view the writers read. Knows no file format and no geometry type. | started | +| L0 | `fieldcraft-parallel` | Deterministic data parallelism for the solvers: fixed work partitions on a rayon pool, results in block order, the thread-count setting (`FIELDCRAFT_THREADS`, `threads`); sequential in the browser. No workspace dependencies. | started | +| L1 | `fieldcraft-geometry` | The only door to SolidCraft: opens `.solidcraft` designs (rebuilt from their timeline, bodies with persistent face names), B-rep bodies, measures, tessellation into a face-tagged `SurfaceMesh`; face-tagged 3MF files read into the same type without the kernel (docs/formats/tagged-mesh.md). No SolidCraft type in its API. | started | +| L1 | `fieldcraft-mesh` | Surface preparation (weld, watertightness, feature edges), robust predicates, Delaunay tetrahedralization and refinement, Voronoi-based optimization, hex meshing, quality metrics. | started | +| L1 | `fieldcraft-fem` | Element kernels shared by every structural solver: the element table (tet4, tet10, wedge6, hex8 full and incompatible modes, hex20 full and reduced), shape functions, quadrature, element stiffness, consistent and lumped mass, body and pressure loads, stresses and their extrapolation, isotropic and orthotropic elasticity, the uniform-strain (one-point) hex8 and tet4 kinematics, hourglass shape vectors and element frequency bounds of the explicit solver, objective stress rates and J2/Johnson–Cook return mapping. Pure functions, no solver loop. | started | +| L1 | `fieldcraft-decks` | Abaqus input, Nastran bulk data and LS-DYNA keyword readers and writers, to and from `model`. Text out, no file system; `deck.write` saves it. | started | +| L1 | `fieldcraft-render` | Display data without a GPU dependency: the visible faces, element edges and nodes of a model, the orbit camera, a CPU rasterizer for headless snapshots, geometry surfaces with their face boundaries, deformed shapes and banded contour plots of nodal results, picking. | started | +| L2 | `fieldcraft-solver` | Implicit solver: assembly from the resolved model, boundary conditions, sparse direct solution, linear static and modal analysis, stresses and reactions. | started | +| L2 | `fieldcraft-explicit` | Explicit dynamics: central-difference time integration with lumped mass, one-point hex8 with hourglass control, nodal-pressure tet4, bulk viscosity, rigid walls, large deformation with objective rates, plasticity, damage and erosion, energy account, histories and field frames. | started | +| L2 | `fieldcraft-lbm` | Lattice Boltzmann flow solver on the CPU: D3Q19 two-relaxation-time scheme, bounce-back and slip walls, Zou–He inlets and outlets, obstacles voxelised from the face-tagged surface with exact predicates, momentum-exchange forces per face. Uses `mesh` for the predicates and the boundary surface. | started | +| L3 | `fieldcraft-fsi` | Fluid–structure coupling between the L2 solvers (later). | planned | +| L4 | `fieldcraft-doc` | The document: geometry, mesh, model, results, history. | planned | +| L5 | `fieldcraft-io` | `.fieldcraft` files (docs/formats/fieldcraft.md): text model definition, binary mesh, geometry references with content hashes, the journal; atomic saves; autosave storage for crash recovery; read and write roots that confine an automation client's file access (`roots`). Bytes in, bytes out; the file system only in `fs`, `recovery` and `roots`. Result files later. | started | +| L6 | `fieldcraft-engine` | Commands (`CommandSpec`), `Session::execute` with rollback and the panic guard, scripts; every file a command names checked against the session's roots (`Session::file_access`); headless pictures of the session (`view`: model, geometry, deformed shape and contours on the CPU rasterizer). | started | +| L7 | `fieldcraft-ui-egui`, `fieldcraft-mcp` | Desktop UI in the Craft family's layout (title bar with quick actions, workspace ribbon from the catalogue, browser, viewport with navigation bar and orientation gizmo, context panel, command search and command line, journal timeline; native file pickers as host services, background opening and solving; results display with legend and probe; offscreen capture of the whole window) and control channel requests (docs/control-protocol.md); MCP server over stdio (docs/mcp.md): tools over engine commands, a headless session or the running app through its control channel, read and write roots. | ui, mcp started | +| testkit | `fieldcraft-testkit` | Test tooling, a dev-dependency only: the meshing test corpus (`tests/corpus/*/recipe.json`, analytic shapes) and its runner (`cargo xtask corpus`). | started | +| apps | `fieldcraft`, `fieldcraft-cli`, `fieldcraft-web` | Desktop, command line, browser. | desktop, cli started | Rules beyond the layer order: - Nothing below L7 uses egui, eframe, winit, wgpu or rfd. -- Only `modelcraft-geometry` uses `solidcraft-*`, pinned by rev in the workspace `Cargo.toml`. +- Only `fieldcraft-geometry` uses `solidcraft-*`, pinned by rev in the workspace `Cargo.toml`. SolidCraft's `[patch.crates-io]` (its fixed truck crates) does not reach dependents, so the workspace repeats it from the same rev; bump both together. The same goes for SolidCraft's patched `egui-wgpu` (MSAA clamped to what the device supports, 1x on OpenGL adapters, so the desktop app's 4x never fails pipeline validation at start). - Everything below the apps builds for `wasm32-unknown-unknown`. -- `modelcraft-geometry`'s non-default `scripts` feature builds designs from SolidCraft command +- `fieldcraft-geometry`'s non-default `scripts` feature builds designs from SolidCraft command scripts (`script_design`, through `solidcraft-engine`) for the test corpus; the library opens designs without it. @@ -56,7 +56,7 @@ Rules beyond the layer order: them), so a load on a named face follows it when the design is edited and opened again. Bodies of components are placed in the world through their occurrences (one body per placement), as SolidCraft displays and exports them. -2. **Surface preparation** (`modelcraft-mesh::surface`, `mesh.surface.check`). Weld the tessellation's per-face +2. **Surface preparation** (`fieldcraft-mesh::surface`, `mesh.surface.check`). Weld the tessellation's per-face vertex copies (at twice SolidCraft's linear tolerance), check the surface is closed, manifold, consistently and outwardly oriented (voids inwards) and free of self-intersections (exact triangle–triangle tests), and find feature edges (face boundaries, sharp dihedral angles). Defects come back located by triangle, vertex and @@ -64,7 +64,7 @@ Rules beyond the layer order: 3. **Tetrahedral meshing.** Bowyer–Watson Delaunay on Shewchuk's adaptive predicates (symbolic perturbation for degeneracies), boundary recovery by the robust constrained Delaunay method of Diazzi et al. 2023 (implicit Steiner points on indirect predicates, gift-wrapping facet - recovery; `modelcraft-mesh::cdt`, command `mesh.tet.generate`), so every boundary triangle and + recovery; `fieldcraft-mesh::cdt`, command `mesh.tet.generate`), so every boundary triangle and its B-rep face tag survives. Then constrained Delaunay refinement to a size field, ODT smoothing interleaved as in Tournois et al. 2009, and the Klingner–Shewchuk 2007 improvement schedule against slivers. @@ -75,12 +75,12 @@ Rules beyond the layer order: frame-field/integer-grid-map methods stay research. Validity uses a certified trilinear Jacobian check (Marschner et al. 2020), not corner Jacobians alone. 5. **Structured hex and face meshing.** Faces get all-quad meshes on shared, prescribed edge - nodes (`modelcraft-mesh::quad`): transfinite interpolation for four-sided faces (Gordon and + nodes (`fieldcraft-mesh::quad`): transfinite interpolation for four-sided faces (Gordon and Hall 1973), and for free faces a half-resolution constrained Delaunay triangulation, recombination (Lee and Lo 1994) and one uniform split that lands on the boundary nodes, then smoothing and untangling; every quad is checked with exact predicates. One `SizeField` trait - (`modelcraft-mesh::size`) sets interior sizes and edge divisions, with even counts per loop. - Mapped and swept hex build on these face meshes (`modelcraft-mesh::hex`, `mesh.hex.generate`): + (`fieldcraft-mesh::size`) sets interior sizes and edge divisions, with even counts per loop. + Mapped and swept hex build on these face meshes (`fieldcraft-mesh::hex`, `mesh.hex.generate`): the B-rep topology is recovered from the face-tagged surface; a body is extruded (straight multi-level sweep: the levels' outlines imprinted into one cross-section, cut at reflex corners, regions mapped, O-gridded or free-meshed, then stacked), revolved (the same in a rotating @@ -91,7 +91,7 @@ Rules beyond the layer order: result passes the certified validity gate plus tiling and fidelity checks. A body no method fits gets each method's reason. Without geometry, `mesh.block` - (`modelcraft-mesh::block`) meshes one six-sided block from its eight corners and optional + (`fieldcraft-mesh::block`) meshes one six-sided block from its eight corners and optional circular or elliptical edges by the same edge-based transfinite interpolation, as hex8, hex20, tet4/tet10 (Kuhn's six tetrahedra per cell) or wedge6, with grading; blocks join where their nodes coincide, face to face (mismatched divisions or kinds are refused), and their named @@ -102,7 +102,7 @@ Rules beyond the layer order: ## Model definition -`modelcraft-model` holds the analysis model in no solver's vocabulary (the writers map it to +`fieldcraft-model` holds the analysis model in no solver's vocabulary (the writers map it to element names, cards and keywords): - **Geometry tags.** Geometry is referred to by name only: a body and, inside it, the persistent @@ -144,27 +144,27 @@ element names, cards and keywords): ## Implicit solver -`modelcraft-solver` solves a model's linear static and frequency steps (`solve.static`, +`fieldcraft-solver` solves a model's linear static and frequency steps (`solve.static`, `solve.frequency`; `solve.probe` reads the stored results). Methods and papers are cited in the module docs; ADR 0001 records why the linear algebra is our own. 1. **System.** `Model::resolve()`; each solid section's elements take their material's - elasticity matrix and the section's formulation (`modelcraft_fem::element::ELEMENT_TYPES`, one + elasticity matrix and the section's formulation (`fieldcraft_fem::element::ELEMENT_TYPES`, one row per element kind: tet4, tet10, wedge6, hex8, hex20); 2D sections (plane stress, plane - strain, axisymmetric) take the 2D table (`modelcraft_fem::plane::PLANE_TYPES`: tri3, tri6, + strain, axisymmetric) take the 2D table (`fieldcraft_fem::plane::PLANE_TYPES`: tri3, tri6, quad4, quad8), chosen by the section kind, and the model then has two degrees of freedom per node: the 2D elements declare `ElementDofs::new(DofSet::PLANE)` (Ux, Uy) to the `DofLayout`. A 2D model lies in the XY plane (axisymmetric: x the radius, Y the axis; nodes on the axis are held radially), is loaded on element edges, and cannot mix with solids or couplings yet. Rod - sections' rod2 and rod3 elements are trusses (`modelcraft_fem::truss`). Point masses, rotary + sections' rod2 and rod3 elements are trusses (`fieldcraft_fem::truss`). Point masses, rotary inertia and springs (`Resolved::masses`, `springs`, expanded per node by the model) assemble on the unknowns their nodes are given (`discrete::needs`, `discrete::dof_of`); dashpots are ignored with a warning (no velocities in static and frequency steps). Every reference point a coupling or a discrete element uses gets six degrees of freedom (translations, rotations) after the mesh's. Shell sections make quad4 (MITC4) and tri3 (MITC3) flat shells with a drilling - rotation (`modelcraft_fem::shell`; quad8 and tri6 shells are "not supported yet"), beam + rotation (`fieldcraft_fem::shell`; quad8 and tri6 shells are "not supported yet"), beam sections two-node Timoshenko beams with the profile's section properties and the section's - orientation vector (`modelcraft_fem::beam`); both declare `ElementDofs::new(DofSet::STRUCTURAL)` + orientation vector (`fieldcraft_fem::beam`); both declare `ElementDofs::new(DofSet::STRUCTURAL)` (`system::Structural`), so their nodes carry translations and rotations and the solids and trusses sharing those nodes add into the translations. Shells and beams need isotropic materials until material orientations exist, and are 3D only. The step's boundary conditions @@ -226,14 +226,14 @@ source, separate from the analytic gate): the Scordelis–Lo roof and the pinche ## Explicit solver -`modelcraft-explicit` integrates a model's `explicit_dynamic` steps in time (`solve.explicit`; +`fieldcraft-explicit` integrates a model's `explicit_dynamic` steps in time (`solve.explicit`; `solve.history` and `solve.probe` read the stored histories and frames). Methods and papers are cited in the module docs and in docs/solvers/literature.md (Parts H and J). Both structural solvers -number their degrees of freedom with `modelcraft_fem::DofLayout` (docs/fem.md). +number their degrees of freedom with `fieldcraft_fem::DofLayout` (docs/fem.md). 1. **System.** `Model::resolve()`; hex8 sections with reduced integration become one-point uniform-strain hexahedra with Flanagan–Belytschko hourglass control, tet4 sections Bonet–Burton - average nodal pressure tetrahedra (`modelcraft_fem::uniform` holds their kinematics), rod2 and + average nodal pressure tetrahedra (`fieldcraft_fem::uniform` holds their kinematics), rod2 and rod3 trusses (`discrete`). Point masses add to the lumped mass; springs and dashpots (on the previous half increment's velocity) add internal and viscous forces and their own stable increment. Other element kinds and formulations, couplings, reference points, rotary inertia, @@ -277,7 +277,7 @@ cantilever's free-vibration period against `solve.frequency`. ## Lattice Boltzmann solver -`modelcraft-lbm` solves a model's fluid steps (`solve.fluid`; `solve.probe` reads velocity and +`fieldcraft-lbm` solves a model's fluid steps (`solve.fluid`; `solve.probe` reads velocity and pressure at points and wall force histories). Papers are cited in the module docs and in `docs/solvers/literature.md` (Part I). @@ -322,7 +322,7 @@ journal replays as a script. Each journalled command is also an undo step (the s `edit.redo` puts it back, so the journal always rebuilds the current model. History is bounded by step count and by an estimate of the memory the kept states hold (oldest steps go first). Commands of kind `Session` (`file.*`, `edit.*`) are never journalled. `file.save` and `file.open` -write and read `.modelcraft` files; the desktop app autosaves unsaved work for `file.recover`. +write and read `.fieldcraft` files; the desktop app autosaves unsaved work for `file.recover`. ## Test corpus @@ -330,10 +330,10 @@ Meshers are measured on our own parts (`docs/adr/0001-libraries.md`, test geomet a SolidCraft command script in `tests/corpus//recipe.json` (the file `solidcraft-cli run` takes, plus a `corpus` object with its categories and, when it has one, its closed-form volume) with a one-line `README.md`; analytic shapes (sphere, torus, cylinder, thick-walled pipe, plate -with a hole) are generated in code. `modelcraft-testkit` runs a recipe in SolidCraft's engine, +with a hole) are generated in code. `fieldcraft-testkit` runs a recipe in SolidCraft's engine, saves the design and opens it through `open_design`, the path a person's design takes, so no geometry is stored. `cargo xtask corpus` (a release build of the testkit's `corpus` binary) -builds every part, tessellates it and runs each stage of `modelcraft_testkit::runner`, writing +builds every part, tessellates it and runs each stage of `fieldcraft_testkit::runner`, writing docs/meshing/corpus.md. A mesher joins by implementing `runner::Stage` (its columns, and a run over the part's tessellated surfaces) and adding itself to `runner::stages()`. @@ -343,6 +343,6 @@ over the part's tessellated surfaces) and adding itself to `runner::stages()`. own model of each problem as a command recipe (`recipe.json`, optionally a SolidCraft `design.json`), its cited reference values with tolerances and reasons (`expected.json`), and our frozen results (`fixture.json`). `cargo xtask oracle` runs the testkit's `oracle` binary -(`modelcraft_testkit::oracle`): one `Session` per case, quantities read by JSON pointer from step +(`fieldcraft_testkit::oracle`): one `Session` per case, quantities read by JSON pointer from step outputs or commands such as `solve.probe {"at": [[x, y, z]]}`, compared with the reference and the fixture; it writes docs/oracle.md. CI runs it as its own job. diff --git a/docs/control-protocol.md b/docs/control-protocol.md index d511139..38e701e 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -1,13 +1,13 @@ # Control protocol -`modelcraft --control PORT` (or `MODELCRAFT_CONTROL_PORT=PORT`) listens on `127.0.0.1:PORT` for +`fieldcraft --control PORT` (or `FIELDCRAFT_CONTROL_PORT=PORT`) listens on `127.0.0.1:PORT` for JSON lines. Each request is one line `{"id": …, "method": "…", "params": {…}}`; each reply is one line `{"id": …, "ok": true, "result": …}` or `{"id": …, "ok": false, "error": "…"}`. Requests are handled on the UI thread between frames; pointer and keyboard input is injected as real egui events, one pointer event per frame. The protocol has the same shape as SolidCraft's. ```text -modelcraft --sample --control 7070 # or --sample cantilever | impact +fieldcraft --sample --control 7070 # or --sample cantilever | impact {"id":1,"method":"engine.execute","params":{"command":"model.info","params":{}}} → {"id":1,"ok":true,"result":{"elements":{"hex8":64,"tet4":162},"nodes":199}} {"id":2,"method":"ui.render","params":{"path":"/tmp/model.png","width":900,"height":600}} @@ -28,7 +28,7 @@ checked and clamped (drag steps, wheel lines, window size); `ui.render` refuses | `engine.script` | `commands`: `[{command, params}, …]` | stops at the first error; `journal`: commands that changed the model so far | | `engine.commands` | — | every command: `id`, `label`, `params` (the parameter documentation) | -Commands never open forms or file pickers when run this way. `modelcraft-cli commands` prints the +Commands never open forms or file pickers when run this way. `fieldcraft-cli commands` prints the same registry. `file.sample {name}` opens a sample (`parts`, `cantilever`, `impact`) and `solve.run {}` solves every step; the Results workspace comes up when a result appears. @@ -53,8 +53,8 @@ same registry. `file.sample {name}` opens a sample (`parts`, `cantilever`, `impa | `ui.worldToScreen` | `points`: `[[x, y, z]…]` (at most 1000) | screen points in the viewport drawn last frame (null when not on screen) | | `ui.resize` | `width`, `height` | window inner size in points | | `ui.fullscreen` | `on?` (default true) | into or out of full screen; on macOS the title bar's left group moves into the traffic lights' room while there are none | -| `ui.screenshot` | `path?` | PNG of the window, taken a few frames later; waits for a presented frame; when none comes within 8 seconds (a hidden window, a locked screen) it answers with `ui.capture`'s offscreen picture (`"offscreen": true`). Without `path`: `modelcraft-shot.png` in the temporary folder | -| `ui.capture` | `path?`, `width?`, `height?` (points; default the window's), `scale?` (pixels per point, 0.5–4; default the window's) | PNG of the whole window (title bar, ribbon, open menus and search, panels, forms and the viewport) drawn offscreen, without fade-in animations: the UI pass runs in a separate egui context and is rasterised on the CPU, so it works with the window hidden, minimised or the screen locked. At most 8192 pixels a side and 16 megapixels. Without `path`: `modelcraft-shot.png` in the temporary folder | +| `ui.screenshot` | `path?` | PNG of the window, taken a few frames later; waits for a presented frame; when none comes within 8 seconds (a hidden window, a locked screen) it answers with `ui.capture`'s offscreen picture (`"offscreen": true`). Without `path`: `fieldcraft-shot.png` in the temporary folder | +| `ui.capture` | `path?`, `width?`, `height?` (points; default the window's), `scale?` (pixels per point, 0.5–4; default the window's) | PNG of the whole window (title bar, ribbon, open menus and search, panels, forms and the viewport) drawn offscreen, without fade-in animations: the UI pass runs in a separate egui context and is rasterised on the CPU, so it works with the window hidden, minimised or the screen locked. At most 8192 pixels a side and 16 megapixels. Without `path`: `fieldcraft-shot.png` in the temporary folder | | `ui.render` | `path`, `width?` (default 1280), `height?` (default 800): whole pixels, each 1–8192 and at most 16 megapixels in all | CPU render of the model with the current camera and view settings (no window or GPU needed). A size out of range, or a scene over 20 million primitives, is an error | | `app.quit` | — | closes the window | diff --git a/docs/elements.md b/docs/elements.md index b5646aa..19ec97a 100644 --- a/docs/elements.md +++ b/docs/elements.md @@ -1,6 +1,6 @@ # Element library -ModelCraft's finite element library (`crates/fem`), family by family, with status. The scope is +FieldCraft's finite element library (`crates/fem`), family by family, with status. The scope is modelled on what a full CAE code offers (Abaqus's element library is the reference for breadth); names, formulations and tests are our own, written from the published literature. Deck writers map each of our elements to each solver's name (see `crates/decks`). Simple elements first: 1D and 2D, @@ -32,7 +32,7 @@ hex8/20/27 with first and second derivatives). |---|---|---| | Truss, 2-node (2D and 3D) | Axial members, cables (compression allowed) | done (rod2: implicit, in plane models with their two unknowns per node; explicit, corotational under nonlinear geometry; Abaqus T3D2 / T2D2, Nastran CROD, LS-DYNA truss beam (3D models)) | | Truss, 3-node (2D and 3D) | Curved axial members | done (rod3: implicit, plane models too; explicit with small displacements; Abaqus T3D3 / T2D3; no Nastran or LS-DYNA counterpart) | -| Beam, 2-node Timoshenko, 3D | Frames, stiffeners; shear deformable | done (exact interdependent interpolation, profile properties, orientation vector; `modelcraft_fem::beam`) | +| Beam, 2-node Timoshenko, 3D | Frames, stiffeners; shear deformable | done (exact interdependent interpolation, profile properties, orientation vector; `fieldcraft_fem::beam`) | | Beam, 2-node Euler–Bernoulli (cubic), 2D and 3D | Slender frames; exact for end-loaded prismatic members | planned (1D elements II) | | Beam, 3-node Timoshenko (quadratic), 2D and 3D | Curved and stocky beams | planned (1D elements II) | | Beam, 2D (planar frames) | In-plane frames with 3 DOFs per node | planned (1D elements II) | @@ -57,7 +57,7 @@ hex8/20/27 with first and second derivatives). | Element | Use | Status | |---|---|---| -| Shell quad4 (MITC4), triangle (locking-free) | Thin and thick shells | done (flat MITC4 and MITC3 with a Hughes–Brezzi drilling rotation; `modelcraft_fem::shell`) | +| Shell quad4 (MITC4), triangle (locking-free) | Thin and thick shells | done (flat MITC4 and MITC3 with a Hughes–Brezzi drilling rotation; `fieldcraft_fem::shell`) | | Shell quad8 / tri6 | Curved thick shells | planned | | Continuum shells (8-node, on swept hex) | Thick-to-thin shells, composites through thickness | planned (M8) | | Membranes (tri3, quad4, quad8) | Fabrics, inflatables, skins without bending | planned | diff --git a/docs/features.md b/docs/features.md index bfa3880..8c32ea8 100644 --- a/docs/features.md +++ b/docs/features.md @@ -1,7 +1,7 @@ # Features Generated from the capability catalogue (`crates/engine/src/catalogue.rs`) by -`cargo run -p modelcraft-cli -- features > docs/features.md`; a test fails when it is out of date. +`cargo run -p fieldcraft-cli -- features > docs/features.md`; a test fails when it is out of date. Every entry is a toolbar button. **Ready** entries run their command; **coming soon** entries are 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. @@ -121,8 +121,8 @@ the control channel and AI agents over MCP (docs/mcp.md) run the same commands. | Decks | Abaqus Input — Write an Abaqus input file (.inp) | ready | `deck.write {"format":"abaqus"}` | | Decks | Nastran Deck — Write Nastran bulk data (SOL 101, 103) | ready | `deck.write {"format":"nastran"}` | | Decks | LS-DYNA Deck — Write an LS-DYNA keyword file | ready | `deck.write {"format":"lsdyna"}` | -| Decks | Read Deck — Read a deck, keeping keywords ModelCraft does not know | coming soon (M9) | | -| Implicit | Static — Linear static (or coupled temperature–displacement) solve of a step with ModelCraft's own solver; heat transfer in the drop-down | ready | `solve.static`, `solve.heat` | +| Decks | Read Deck — Read a deck, keeping keywords FieldCraft does not know | coming soon (M9) | | +| Implicit | Static — Linear static (or coupled temperature–displacement) solve of a step with FieldCraft's own solver; heat transfer in the drop-down | ready | `solve.static`, `solve.heat` | | Implicit | Frequency — Natural frequencies and mode shapes of a frequency step | ready | `solve.frequency` | | Explicit | Explicit — Transient dynamics of an explicit step by central differences (contact and erosion coming in M10) | ready | `solve.explicit` | | Explicit | Blast & Impact — EOS-driven fluids with fluid–structure contact (ALE or SPH) | coming soon (M14) | | @@ -153,10 +153,10 @@ the control channel and AI agents over MCP (docs/mcp.md) run the same commands. | Panel | Capability | Status | Command | |---|---|---|---| | Model | Model Info — Node and element counts and the model summary | ready | `model.info` | -| File | Open Model — Open a .modelcraft model | ready | `file.open` | +| File | Open Model — Open a .fieldcraft model | ready | `file.open` | | File | Samples — Open a sample model, ready to solve | ready | `file.sample` | -| File | Save — Save the model to its .modelcraft file | ready | `file.save` | -| File | Save As — Save the model to a new .modelcraft file | ready | `file.save_as` | +| File | Save — Save the model to its .fieldcraft file | ready | `file.save` | +| File | Save As — Save the model to a new .fieldcraft file | ready | `file.save_as` | | File | Recover — Open a model autosaved by an app that crashed or closed with unsaved changes | ready | `file.recover`, `file.recovery.list`, `file.recovery.discard` | | History | Undo — Undo the last command that changed the model | ready | `edit.undo` | | History | Redo — Redo the command undone last | ready | `edit.redo` | diff --git a/docs/fem.md b/docs/fem.md index 02aa909..ff24d24 100644 --- a/docs/fem.md +++ b/docs/fem.md @@ -39,13 +39,13 @@ Lookups: `node(label)` / `slot(label)` (a slot is a position: mesh nodes `0..nod `point_slot(k)`), `dof(slot, kind)`, `range(slot)`, `kinds(slot)`, `locate(dof) -> (slot, kind)`, `element_dofs(slots, kinds)`, `describe(dof)`, `stride()` (uniform layouts). -Trusses declare `modelcraft_fem::truss::DOFS` (translations, on `Basis::Line2`/`Line3`). +Trusses declare `fieldcraft_fem::truss::DOFS` (translations, on `Basis::Line2`/`Line3`). Point masses, springs and dashpots are not mesh elements: the implicit solver asks the layout -for what each needs at its nodes (`modelcraft_solver::discrete::needs`: translations for a mass +for what each needs at its nodes (`fieldcraft_solver::discrete::needs`: translations for a mass or a translational spring, rotations for an inertia or a torsional spring; a reference point they use is a point with six) and finds its unknowns with `discrete::dof_of`. -Solvers: `modelcraft_solver::system::Mesh::layout` numbers the implicit system (mesh nodes, +Solvers: `fieldcraft_solver::system::Mesh::layout` numbers the implicit system (mesh nodes, then six per coupled reference point); assembly (`sparse::add_block(k, layout, slots, kinds, data)`, `add_scalar`, `add_diagonal`) works for any mix of kinds per node, so a solid sharing nodes with a shell adds its translation block into the shell nodes' six-unknown blocks. @@ -108,7 +108,7 @@ A heat conduction element carries one temperature per node (`heat::HEAT_DOFS`, k `heat::Conduction` gives the conductivity ∫ ∇Nᵀ k ∇N dV (full rule), the capacity ∫ ρc N Nᵀ dV and a source ∫ N Q dV (mass rule), and the heat flux −k∇T at the integration points; `heat::face_weights` and `face_film` integrate surface flux and convection on tri3/tri6/quad4/quad8 -faces. The solver (`modelcraft_solver::heat`) numbers the temperatures with +faces. The solver (`fieldcraft_solver::heat`) numbers the temperatures with `DofLayout::uniform(…, DofSet::TEMPERATURE)` and assembles with the same `sparse::add_block`. Thermal strain ε_th = α (T − T_ref) along the material axes enters a solid as the load diff --git a/docs/formats/modelcraft.md b/docs/formats/fieldcraft.md similarity index 83% rename from docs/formats/modelcraft.md rename to docs/formats/fieldcraft.md index 4abf0a3..1e424cb 100644 --- a/docs/formats/modelcraft.md +++ b/docs/formats/fieldcraft.md @@ -1,26 +1,26 @@ -# The `.modelcraft` file (format `modelcraft/1`) +# The `.fieldcraft` file (format `fieldcraft/1`) -A `.modelcraft` file holds one model: its definition, its mesh, the geometry it is built on and -the commands that built it. It is written and read by `modelcraft-io` (`write`, `read`; bytes in, +A `.fieldcraft` file holds one model: its definition, its mesh, the geometry it is built on and +the commands that built it. It is written and read by `fieldcraft-io` (`write`, `read`; bytes in, bytes out, so the browser build uses the same code) and by the commands `file.save`, `file.save_as` and `file.open`. ## Package The file is a ZIP package with exactly four parts, in this order, all stored uncompressed (so any -unzip tool reads them: `unzip -p bracket.modelcraft model.json`): +unzip tool reads them: `unzip -p bracket.fieldcraft model.json`): | Part | Content | |---|---| -| `modelcraft.json` | Manifest: `format` (`"modelcraft/1"`), `generator` (`"ModelCraft 0.1.0"`), `geometry`, and `parts`: the byte size and SHA-256 of each other part. | +| `fieldcraft.json` | Manifest: `format` (`"fieldcraft/1"`), `generator` (`"FieldCraft 0.1.0"`), `geometry`, and `parts`: the byte size and SHA-256 of each other part. | | `model.json` | The model definition: unit system, sets, materials, sections, reference points, couplings, boundary conditions, loads and steps. Pretty-printed JSON with a fixed key order (maps sorted by name), so a change shows as a line diff. Its form is the model's own JSON form, which commands take as parameters. | | `mesh.bin` | Nodes, elements and geometry tags, binary (below). | -| `journal.json` | The commands that built the model, `[{"command": id, "params": {...}}, ...]`, one per line. It is a script `modelcraft-cli run` replays. | +| `journal.json` | The commands that built the model, `[{"command": id, "params": {...}}, ...]`, one per line. It is a script `fieldcraft-cli run` replays. | The same model gives the same bytes on every platform: entry times are fixed (1980-01-01), permissions are fixed, and every map is written in sorted order. To diff two versions with git, -add `*.modelcraft diff=modelcraft` to `.gitattributes` and -`git config diff.modelcraft.textconv "unzip -p"`; the definition and the journal show line by +add `*.fieldcraft diff=fieldcraft` to `.gitattributes` and +`git config diff.fieldcraft.textconv "unzip -p"`; the definition and the journal show line by line, the mesh part as binary. ## Geometry @@ -68,8 +68,8 @@ Every file is treated as hostile: allocation is larger than the input. - Only the four parts, each once. The ZIP CRC and the manifest's size and SHA-256 are checked for every part. -- A `format` other than `modelcraft/1` is refused: a later `modelcraft/N` as a version error, any - other value as not a ModelCraft file. Unknown fields are errors. +- A `format` other than `fieldcraft/1` is refused: a later `fieldcraft/N` as a version error, any + other value as not a FieldCraft file. Unknown fields are errors. - `mesh.bin`: counts are capped (50 million nodes and elements, one million entities) and checked against the bytes present before anything is read; labels must ascend; unknown kinds and trailing bytes are refused. @@ -87,8 +87,8 @@ refused. ## Autosave and recovery The desktop app autosaves every 30 seconds while there are unsaved changes, into the recovery -folder (`MODELCRAFT_RECOVERY_DIR`, else `ModelCraft/recovery` in the per-user data folder): -`.modelcraft`, `.json` (the model's own path, time, counts) and `.lock`, held while +folder (`FIELDCRAFT_RECOVERY_DIR`, else `FieldCraft/recovery` in the per-user data folder): +`.fieldcraft`, `.json` (the model's own path, time, counts) and `.lock`, held while the app runs. Saving (or undoing back to the saved state) drops the autosave. After a crash, the next launch reports the entries; `file.recovery.list` lists them, `file.recover` opens one as unsaved work under its original path, and `file.recovery.discard` deletes them. diff --git a/docs/formats/tagged-mesh.md b/docs/formats/tagged-mesh.md index ecb36eb..a7b7030 100644 --- a/docs/formats/tagged-mesh.md +++ b/docs/formats/tagged-mesh.md @@ -1,8 +1,8 @@ # Face-tagged meshes (3MF with B-rep face tags) -Besides opening a `.solidcraft` design, geometry can enter ModelCraft as a triangulated boundary +Besides opening a `.solidcraft` design, geometry can enter FieldCraft as a triangulated boundary surface written by another program, with each triangle tagged by the B-rep face it lies on. -ModelCraft reads it into a `SurfaceMesh` (`modelcraft_geometry::tagged_mesh`, command +FieldCraft reads it into a `SurfaceMesh` (`fieldcraft_geometry::tagged_mesh`, command `geometry.import.mesh`) without involving the kernel. ## The format is SolidCraft's @@ -20,10 +20,10 @@ in the SolidCraft repository. In short: - Coordinates are written in shortest round-trip `f64` form, so they read back exactly. - Within an object, either every triangle is tagged or none is. -ModelCraft follows that specification and does not extend it. Because the format is 3MF, +FieldCraft follows that specification and does not extend it. Because the format is 3MF, untagged 3MF from any other program goes through the same reader, with one face per object. -## What ModelCraft's reader does +## What FieldCraft's reader does - **Surfaces.** Every build item becomes one `TaggedSurface`: a `SurfaceMesh` with its name and face count. Lengths are converted to millimetres from `unit`. The surface is named after its @@ -42,7 +42,7 @@ untagged 3MF from any other program goes through the same reader, with one face - Each part is at most 1 GB uncompressed, in at most 10 000 zip entries. - At most 20 million vertices and 20 million triangles, counted over every placement. - At most 10 000 build items, and XML nesting at most 256 deep. - - Coordinates must be finite and within ±10⁹ mm (`modelcraft_model::MAX_COORD`), both as read + - Coordinates must be finite and within ±10⁹ mm (`fieldcraft_model::MAX_COORD`), both as read and once placed. - Every vertex index must exist. - **Errors.** Any violation is an error that names the problem. Nothing panics. diff --git a/docs/mcp.md b/docs/mcp.md index 525f5bf..bf2199b 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1,22 +1,22 @@ -# ModelCraft and AI agents (MCP) +# FieldCraft and AI agents (MCP) -ModelCraft speaks the [Model Context Protocol](https://modelcontextprotocol.io) over stdio +FieldCraft speaks the [Model Context Protocol](https://modelcontextprotocol.io) over stdio (JSON-RPC 2.0, one message per line; protocol revisions 2025-06-18, 2025-03-26 and 2024-11-05). An agent builds and solves models the way a person does: every tool runs engine commands, the same ones the UI, scripts, the command line and the control channel run, and never opens a -dialog or a file picker. The server is `crates/mcp` (`modelcraft-mcp`); `modelcraft-cli mcp` +dialog or a file picker. The server is `crates/mcp` (`fieldcraft-mcp`); `fieldcraft-cli mcp` runs it. ## Start it ```sh # Headless (the default): an in-process session, no window; screenshots are CPU renders. -modelcraft-cli mcp -modelcraft-cli mcp --headless +fieldcraft-cli mcp +fieldcraft-cli mcp --headless # Bridged to the running app, so you can watch the agent work: -modelcraft --control 7070 & -modelcraft-cli mcp --connect 127.0.0.1:7070 +fieldcraft --control 7070 & +fieldcraft-cli mcp --connect 127.0.0.1:7070 ``` With `--connect`, an app that cannot be reached when the server starts gives a headless session @@ -27,21 +27,21 @@ both must run on the same computer. ### Claude Code ```sh -claude mcp add modelcraft -- /path/to/modelcraft-cli mcp +claude mcp add fieldcraft -- /path/to/fieldcraft-cli mcp # bridged to a running app: -claude mcp add modelcraft -- /path/to/modelcraft-cli mcp --connect 127.0.0.1:7070 +claude mcp add fieldcraft -- /path/to/fieldcraft-cli mcp --connect 127.0.0.1:7070 # kept to one project folder: -claude mcp add modelcraft -- /path/to/modelcraft-cli mcp --automation-read-root /work/bracket --automation-write-root /work/bracket/out +claude mcp add fieldcraft -- /path/to/fieldcraft-cli mcp --automation-read-root /work/bracket --automation-write-root /work/bracket/out ``` -Build the binary first (`cargo build --release -p modelcraft-cli`; it is -`target/release/modelcraft-cli`). The same setup as a project `.mcp.json`, or in another client's +Build the binary first (`cargo build --release -p fieldcraft-cli`; it is +`target/release/fieldcraft-cli`). The same setup as a project `.mcp.json`, or in another client's configuration (Claude Desktop's `claude_desktop_config.json`): ```json { "mcpServers": { - "modelcraft": { "command": "/path/to/modelcraft-cli", "args": ["mcp"] } + "fieldcraft": { "command": "/path/to/fieldcraft-cli", "args": ["mcp"] } } } ``` @@ -49,13 +49,13 @@ configuration (Claude Desktop's `claude_desktop_config.json`): ## Confining file access ```sh -modelcraft-cli mcp --automation-read-root /work/bracket --automation-write-root /work/bracket/out +fieldcraft-cli mcp --automation-read-root /work/bracket --automation-write-root /work/bracket/out ``` The two flags keep the agent to the folders you give it: - Every file a command reads must lie inside the **read root**: `open`, `file.open`, - `geometry.open`, `geometry.import.mesh`, the design and meshes a `.modelcraft` file opens + `geometry.open`, `geometry.import.mesh`, the design and meshes a `.fieldcraft` file opens again, `file.recovery.list` and `file.recover` with a `dir`. - Every file a command writes must lie inside the **write root**: `save`, `file.save` and `file.save_as` (also a save to the model's own file), `write_deck` and `deck.write`, @@ -96,7 +96,7 @@ software on the computer. | `batch` | `commands: [{command, params}]` | Run steps in order, stop at the first error; the reply has every result and the failing step (`failed.index`, `failed.command`, `failed.error`). Steps before it stay applied (`undo` takes them back). | | `inspect_model` | — | `model.info` (counts, units, definitions), `model.activity` (the step table) and `geometry.bodies` (bodies with measures and persistent face names, which sets name as `{body, face}`). | | `check_model` | — | `model.check`: errors and warnings, each naming its item. | -| `open` | `path` | By extension: `.modelcraft` (`file.open`), `.solidcraft` (`geometry.open`), `.3mf` (`geometry.import.mesh`). | +| `open` | `path` | By extension: `.fieldcraft` (`file.open`), `.solidcraft` (`geometry.open`), `.3mf` (`geometry.import.mesh`). | | `save` | `path?` | `file.save`: to `path` or the model's own file. | | `write_deck` | `format` (abaqus, nastran, lsdyna), `path`, `steps?` | `deck.write`. | | `mesh` | `method`, `params?` | Runs the mesher (`tet`: `mesh.tet.generate`; `planar`: `mesh.planar`; the hex and block meshers as they land) and replies with its result, `model.info` and `mesh.quality`. | @@ -107,7 +107,7 @@ software on the computer. | `screenshot` | `view?`, `source?` (window, model), `width?`, `height?`, `path?` | A PNG image content block and what it shows. Headless: a CPU render framing the model, the geometry and the results display (deformed shape, contour bands). Bridged: the whole app window drawn offscreen (`ui.capture`, the default) or a model render with the app's camera (`ui.render`); `view` turns the app's camera first. | | `undo`, `redo` | — | `edit.undo`, `edit.redo`. | -Resources: `modelcraft://model` (`model.info`) and `modelcraft://commands` (the command catalog). +Resources: `fieldcraft://model` (`model.info`) and `fieldcraft://commands` (the command catalog). Errors: an unknown tool, or arguments that do not match the tool's schema (a missing or unknown argument, a wrong type, a value outside an enum), are JSON-RPC `-32602` errors; an engine failure diff --git a/docs/meshing/literature.md b/docs/meshing/literature.md index 0c29137..f6a6a94 100644 --- a/docs/meshing/literature.md +++ b/docs/meshing/literature.md @@ -1,4 +1,4 @@ -# Volume meshing literature survey for ModelCraft (tet → hex) +# Volume meshing literature survey for FieldCraft (tet → hex) Compiled 2026-10-13. Scope: watertight triangulated B-rep tessellation in, then (1) tetrahedral mesh by Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant meshes. @@ -110,7 +110,7 @@ Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant --- -**A0.5 Boundary surface preparation (implemented in `modelcraft-mesh::surface`).** **Verdict: BUILD ON.** +**A0.5 Boundary surface preparation (implemented in `fieldcraft-mesh::surface`).** **Verdict: BUILD ON.** - **Guigue and Devillers 2003.** P. Guigue, O. Devillers. *Fast and Robust Triangle–Triangle Overlap Test Using Orientation Predicates.* Journal of Graphics Tools 8(1):25–32, 2003. Triangle overlap decided from `orient3d` / `orient2d` signs alone, so it is exact on the A0.1 predicates. We use the equivalent edge–triangle reduction (two @@ -151,7 +151,7 @@ Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant - Hilbert index: J. Skilling, *Programming the Hilbert curve*, AIP Conf. Proc. 707:381–387, 2004, DOI [10.1063/1.1751381](https://doi.org/10.1063/1.1751381). - **Code.** For a permissive reference, see geogram `delaunay_3d.cpp` (BSD). -- **Implemented** in `modelcraft-mesh::delaunay` (ghost vertex for the hull, Devillers–Teillaud perturbation for +- **Implemented** in `fieldcraft-mesh::delaunay` (ghost vertex for the hull, Devillers–Teillaud perturbation for cospherical points, exact verifier). **A1.2 Marot, Pellerin, Remacle 2019, "One machine, one minute, three billion tetrahedra".** **Verdict: reference (later).** @@ -238,7 +238,7 @@ Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant - **Complexity.** High: about 8–12k lines of Rust including the predicate generator. It is the single most valuable investment in the tet pipeline. - **Code.** `MarcoAttene/CDT` is **GPL, or LGPL by build option** (README): FORBIDDEN. -- **Implemented** in `modelcraft-mesh::cdt` from the paper only, with these choices: +- **Implemented** in `fieldcraft-mesh::cdt` from the paper only, with these choices: - Steiner points are LNC points (§4.3) on the original segment, `a + t (b − a)` with `t` a double; the in-sphere test on them is exact (interval filter, then exact dyadic integers) and perturbed with the same lexicographic lift as the Delaunay step, so Bowyer–Watson insertion of Steiner points stays consistent with it. @@ -785,7 +785,7 @@ Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant - **HexaLab** (MIT, https://github.com/mlivesu/HexaLab, https://www.hexalab.net) is READ OK. It is a hex quality viewer whose metrics match the literature's. -**B6.6 Element validity and quality metrics (implemented in `modelcraft-mesh::quality`).** **Verdict: BUILD ON.** +**B6.6 Element validity and quality metrics (implemented in `fieldcraft-mesh::quality`).** **Verdict: BUILD ON.** - **Johnen, Remacle, Geuzaine 2013.** *Geometrical validity of curvilinear finite elements.* J. Comput. Phys. 233:359–372, 2013. The Jacobian determinant in Bernstein form is bounded by its coefficients, equals them at the vertices and converges under @@ -803,7 +803,7 @@ Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant > Shell (curved-face) quad meshing — advancing front in (u,v) with a cross field, recombination and cleanup — is surveyed separately in [shell-quad-literature.md](shell-quad-literature.md). - (implemented in `modelcraft-mesh::quad`) + (implemented in `fieldcraft-mesh::quad`) The face meshers under mapped and swept hex meshing and shell meshing. All quads on prescribed boundary nodes, so faces sharing an edge share its nodes. DOIs below were checked through @@ -878,7 +878,7 @@ input points), Steiner points kept half the local spacing from the boundary. --- -### B8. Mapped and swept hexahedral meshing (implemented in `modelcraft-mesh::hex`) +### B8. Mapped and swept hexahedral meshing (implemented in `fieldcraft-mesh::hex`) `mesh.hex.generate` (and `mesh.hex.sweep`, `mesh.hex.mapped`). Every method below is implemented from its paper; no code was read. DOIs marked "checked" were confirmed on 2026-10-13 through a repository @@ -939,14 +939,14 @@ Collision Detection*, 2005, §5.1.5). ## Part C: Datasets and benchmarks -| Dataset | Content | Licence | Usable for ModelCraft? | +| Dataset | Content | Licence | Usable for FieldCraft? | |---|---|---|---| | **Thingi10K**: Zhou & Jacobson 2016, arXiv https://arxiv.org/abs/1605.04797; site https://ten-thousand-models.appspot.com; tooling https://github.com/Thingi10K/Thingi10K | 10,000 STL meshes. 45% self-intersecting, 50% non-solid. The 4,408 "valid" subset is the standard tet-meshing robustness benchmark (fTetWild, Diazzi 2023). | Tooling Apache-2.0. **Each model has its own licence** (10 licences, including CC-BY-NC and GPL). Filter by the `license` field. | **Conditionally.** Use CC-BY / CC0 / public-domain models for robustness testing. **Provenance risk:** Thingiverse records no authoring tool, and many uploads were modelled in commercial CAD. Under the "no test data produced with commercial CAD products" rule this is an **owner decision**. | | **HexMe**: DOI 10.5281/zenodo.6642020, https://hexme.algohex.eu | 189 tet meshes from 63 CAD models, with feature tags. | Dataset **CC-BY-4.0**. The source models come from the **ABC dataset (Onshape terms), GrabCAD (GrabCAD terms)**, MAMBO (Apache-2.0), and models made with **Gmsh and Siemens NX** (the paper's own statement). | **Only a filtered subset.** The ABC/Onshape, GrabCAD and Siemens NX models are produced with commercial CAD: exclude them. Gmsh-authored ones are acceptable as data. | | **MAMBO**: https://gitlab.com/franck.ledoux/mambo | STEP models: basic, simple, medium. Designed for hex block-meshing evaluation. | **Apache-2.0** | **Risky.** The STEP headers I sampled say `'Spatial InterOp 3D'` as originating system and authors `'Cubit 15.2'` / `'MGX'`. These are commercial or closed tools. Treat as commercial-CAD-produced unless the owner rules otherwise. | | **ABC**: Koch et al., CVPR 2019, DOI [10.1109/CVPR.2019.00983](https://doi.org/10.1109/CVPR.2019.00983), arXiv https://arxiv.org/abs/1812.06216, https://deep-geometry.github.io/abc-dataset/ | 1M CAD models (B-rep and meshes). | "Copyright of the CAD models is owned by their creators", under **Onshape Terms of Use**. The models come from Onshape. | **No.** Produced with a commercial CAD product, and the licence is not open. Skip. | -**Recommendation for test data.** Build ModelCraft's own corpus: +**Recommendation for test data.** Build FieldCraft's own corpus: - **Generated cases.** Parametric primitives plus booleans from our own kernel: - cubes and cylinders with holes; - fillets; diff --git a/docs/meshing/shell-quad-literature.md b/docs/meshing/shell-quad-literature.md index cb9b88e..0f4c758 100644 --- a/docs/meshing/shell-quad-literature.md +++ b/docs/meshing/shell-quad-literature.md @@ -1,11 +1,11 @@ -# Shell meshing literature survey for ModelCraft: quad and quad-dominant meshes of curved B-rep faces +# Shell meshing literature survey for FieldCraft: quad and quad-dominant meshes of curved B-rep faces Compiled 2026-10-13. Scope: all-quad and quad-dominant surface meshes of the curved faces (planes, cylinders, cones, spheres, tori, NURBS) of SolidCraft parts, meshed in each face's (u,v) parameter space with metric correction and conforming across shared B-rep edges, for shell analysis (MITC4-class shells now, continuum shells on swept hexes in M8). This extends `docs/meshing/literature.md` §B7, which compared paving, Q-Morph and Blossom-Quad for **planar** faces only and chose CDT → greedy recombination → -Catmull–Clark split (`modelcraft-mesh::quad`). +Catmull–Clark split (`fieldcraft-mesh::quad`). Same format as `docs/meshing/literature.md`: per paper, citation, DOI/URL, method, problem solved, robustness/limits, complexity, code + licence, verdict. Part P is the opinionated plan. @@ -126,7 +126,7 @@ robustness/limits, complexity, code + licence, verdict. Part P is the opinionate **S2.2 Owen and Saigal 2000, Surface mesh sizing control.** **Verdict: build on (size field on faces).** - **Citation.** IJNME 47(1–3):497–511, 2000. DOI [10.1002/(SICI)1097-0207(20000110/30)47:1/3<497::AID-NME781>3.0.CO;2-H](https://doi.org/10.1002/(SICI)1097-0207(20000110/30)47:1/3%3C497::AID-NME781%3E3.0.CO;2-H). *Read:* metadata. - **Method.** Automatic sizing on CAD surfaces from curvature and proximity, stored on a background mesh. -- **Use.** The curvature and proximity terms of ModelCraft's surface size field (P3.3). +- **Use.** The curvature and proximity terms of FieldCraft's surface size field (P3.3). **S2.3 Lo 1989; Lau, Lo, Lee 1997; Lo and Lau 1998: quadrilaterals over curved surfaces.** **Verdict: reference.** - **Citations.** @@ -193,7 +193,7 @@ robustness/limits, complexity, code + licence, verdict. Part P is the opinionate **S3.2 Borouchaki, George, Hecht, Laug, Saltel 1997, Delaunay mesh generation governed by metric specifications.** **Verdict: build on (only if non-conformal parametrizations need an anisotropic Delaunay kernel).** - **Citation.** Part I: FEAD 25(1–2):61–83, 1997, DOI [10.1016/S0168-874X(96)00057-1](https://doi.org/10.1016/S0168-874X(96)00057-1); Part II: FEAD 25:85–109, DOI [10.1016/S0168-874X(96)00065-0](https://doi.org/10.1016/S0168-874X(96)00065-0). *Read:* metadata. - **Method.** The Delaunay criterion evaluated in a local metric (anisotropic Bowyer–Watson cavity). -- **Caveat for ModelCraft.** A metric in-circle test is not an exact predicate of the input coordinates; it +- **Caveat for FieldCraft.** A metric in-circle test is not an exact predicate of the input coordinates; it may only decide *point placement* and *which flip to try*, never validity. Validity stays an exact `orient2d` sign in (u,v) (see P3.5). @@ -315,7 +315,7 @@ robustness/limits, complexity, code + licence, verdict. Part P is the opinionate - **Complexity.** Same as frontal-Delaunay triangulation, O(n log n); "tens of thousands of elements a minute" (§1) in 2010-era hardware. - **Why it matters here.** It is a marching front, but one whose collisions cannot fail, and it is a small - change to machinery ModelCraft already has (Bowyer–Watson, constrained recovery, recombination). + change to machinery FieldCraft already has (Bowyer–Watson, constrained recovery, recombination). **S5.2 Georgiadis, Beaufort, Lambrechts, Remacle 2017, High quality mesh generation using cross and asterisk fields.** **Verdict: reference.** - **Citation.** arXiv , 2017. *Read:* abstract. @@ -346,7 +346,7 @@ robustness/limits, complexity, code + licence, verdict. Part P is the opinionate slow on faces over 100k vertices; acute corners (< 45°) need artificial singularities; size-map vs cross-field compatibility "still open". - **Why it matters here.** Strongest published evidence on CAD B-reps for any automatic all-quad method, and - it uses **the same tri → recombine → split guarantee ModelCraft already ships**, with better point placement + it uses **the same tri → recombine → split guarantee FieldCraft already ships**, with better point placement in front of it and cleanup behind it. It is the template for P. - **Data caveat.** Both datasets are excluded for our own tests (see `literature.md` Part C); we cite the numbers only. @@ -372,7 +372,7 @@ robustness/limits, complexity, code + licence, verdict. Part P is the opinionate - **Citation.** IJNME 89(9):1102–1119, 2012. DOI [10.1002/nme.3279](https://doi.org/10.1002/nme.3279). *Read:* metadata. - **Note.** Reberol 2021 replaced Blossom with a greedy, alignment-weighted matching followed by the split, which removes the "perfect matching may not exist" failure and the cubic-time matching. That supports keeping - ModelCraft's greedy matching. + FieldCraft's greedy matching. **S6.3 Ramaswami, Ramos, Toussaint 1998, Converting triangulations to quadrangulations.** **Verdict: reference (theory).** - **Citation.** CGTA 9(4):257–276, 1998. DOI [10.1016/S0925-7721(97)00019-9](https://doi.org/10.1016/S0925-7721(97)00019-9). @@ -619,14 +619,14 @@ cylinder displacement under the load 1.8248×10⁻⁵ (E = 3×10⁶, P = 1); pin --- -## Part P: Recommended plan for ModelCraft shell meshing (opinionated) +## Part P: Recommended plan for FieldCraft shell meshing (opinionated) ### P1. Position **I agree with a cross-field-guided advancing front in (u,v) with a metric and topological cleanup, with one change: the front advances *points over a Delaunay triangulation* (frontal-Delaunay in the cross-field L∞ norm, Remacle 2013), not quads (paving) and not triangle-to-quad transformations (Q-Morph).** All-quad comes -from the recombination + split ModelCraft already ships, and cleanup follows Reberol 2021. +from the recombination + split FieldCraft already ships, and cleanup follows Reberol 2021. Reasons: 1. **Closure cannot fail.** A frontal-Delaunay front always sits inside a valid constrained triangulation, so @@ -668,13 +668,13 @@ For each B-rep face set to be shell-meshed (a sheet body, or chosen faces of a s ### P3. Components -**P3.1 Chart abstraction** (`modelcraft-mesh::surface_mesh::chart` or a new `shell` module; trait in `mesh`, +**P3.1 Chart abstraction** (`fieldcraft-mesh::surface_mesh::chart` or a new `shell` module; trait in `mesh`, implementation in `geometry`, wired in `engine`, so `mesh` does not depend on `geometry`): `eval(u,v) → [f64;3]`, `derivs(u,v) → (S_u, S_v)` (second derivatives for curvature sizing), `project(p, hint) → (u,v)`, the face's loops as 3D node chains with their (u,v), periodicity and singular points. Native charts come from SolidCraft's kernel surfaces (truck's parametric surfaces already have parameter search, used in 5 SolidCraft kernel files); this needs a new **public** face-surface API in `solidcraft-kernel`, exposed -through `modelcraft-geometry` without leaking SolidCraft types. Handling: +through `fieldcraft-geometry` without leaking SolidCraft types. Handling: - **Periodic faces** (cylinder, torus, revolved): the seam edge appears twice in the loop; give its nodes u = 0 on one side and u = period on the other (unwrap by continuity along the loop). The domain becomes simply connected; seam nodes are the same 3D nodes, so conformity across the seam holds. @@ -772,7 +772,7 @@ ship. ### P6. Order of work 1. **Chart API** in `solidcraft-kernel` (public face-surface evaluation and parameter search) and - `modelcraft-geometry` (trait implementation, seams, poles). Done when the P5.1 tests pass. + `fieldcraft-geometry` (trait implementation, seams, poles). Done when the P5.1 tests pass. 2. **Port the shipped free mesher to (u,v) + metric**: half-resolution CDT and Ruppert refinement in the rescaled (u,v) with metric sizing, recombination, split, smoothing with 3D objective. This gives all-quad conforming meshes on curved faces early, and is the baseline. diff --git a/docs/oracle.md b/docs/oracle.md index 87bc55c..1970110 100644 --- a/docs/oracle.md +++ b/docs/oracle.md @@ -1,12 +1,12 @@ # Verification and validation oracle -Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the policy is [verification.md](verification.md). Every case is our own model, built with ModelCraft commands from the problem's published description, and compared with the reference value its `expected.json` cites, within the stated tolerance and for the stated reason. Accepted results are frozen in each case's `fixture.json`: a later change that moves one beyond its band (10⁻⁹ of its size) fails CI even while the reference still passes. No reference value was produced with a commercial product. +Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the policy is [verification.md](verification.md). Every case is our own model, built with FieldCraft commands from the problem's published description, and compared with the reference value its `expected.json` cites, within the stated tolerance and for the stated reason. Accepted results are frozen in each case's `fixture.json`: a later change that moves one beyond its band (10⁻⁹ of its size) fails CI even while the reference still passes. No reference value was produced with a commercial product. **117 of 123 quantities pass** (6 known failure(s)) in 35 active cases: 26 theory, 9 published benchmark, 0 experiment; 9 pending cases wait for capabilities still being built. ## Theory: closed-form solutions, patch tests and mesh invariants -| Case | Quantity | Reference | ModelCraft | Error | Tolerance | Result | Fixture | +| Case | Quantity | Reference | FieldCraft | Error | Tolerance | Result | Fixture | |---|---|---|---|---|---|---|---| | [bar-pressure-wave](../tests/oracle/bar-pressure-wave/) | displacement of the loaded end at t = 0.06 | -6.0000e-4 | -5.9980e-4 | 0.034% | 1% | pass | matches | | [bar-pressure-wave](../tests/oracle/bar-pressure-wave/) | particle velocity at x = 5, 20 elements behind the front, at t = 0.06 | -0.01 | -0.0098552 | 1.45% | 2% | pass | matches | @@ -108,7 +108,7 @@ Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the ## Published benchmarks -| Case | Quantity | Reference | ModelCraft | Error | Tolerance | Result | Fixture | +| Case | Quantity | Reference | FieldCraft | Error | Tolerance | Result | Fixture | |---|---|---|---|---|---|---|---| | [mh-curved-cantilever](../tests/oracle/mh-curved-cantilever/) | tip displacement along the in-plane load | 0.08734 | 0.076833 | -12% | 2% | known failure: 0.880 of the theory, grade C: straight-sided hex8 on six elements over 90° (MacNeal–Harder's HEXA(8): 0.880, Table 9 p. 15) | — | | [mh-curved-cantilever](../tests/oracle/mh-curved-cantilever/) | tip displacement along the out-of-plane load | 0.5022 | 0.40095 | -20.2% | 2% | known failure: 0.798 of the theory, grade D: out-of-plane bending with torsion on one element across (MacNeal–Harder's HEXA(8): 0.849) | — | diff --git a/docs/parity.md b/docs/parity.md index 604c9b7..49ac1c9 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -1,6 +1,6 @@ # Capability checklist (reference: Abaqus/CAE) -What an analyst does in a full CAE pre-processor, grouped by milestone, with ModelCraft's status. +What an analyst does in a full CAE pre-processor, grouped by milestone, with FieldCraft's status. Scope comes from the public documentation of Abaqus/CAE and public analyst discussion, described in our own words; nothing was taken from the product itself (AGENTS.md: no commercial products are run, no documentation text is copied). Status: **done**, **started**, **planned**. @@ -8,7 +8,7 @@ products are run, no documentation text is copied). Status: **done**, **started* ## Where we aim to be better -| # | Opportunity | Why analysts want it | ModelCraft approach | +| # | Opportunity | Why analysts want it | FieldCraft approach | |---|---|---|---| | 1 | Hex meshes without manual partitioning | Partitioning a part into sweepable cells by hand is the most cited chore | Sweep and mapped meshing with automatic decomposition that proposes SolidCraft splits (M5); octree all-hex for anything else (M6) | | 2 | Undo, redo, replay, crash recovery | No general undo; replay files are hard to reuse; corrupted model files | Every action is a journalled command with rollback, undo and redo; autosave with recovery after a crash | @@ -16,7 +16,7 @@ products are run, no documentation text is copied). Status: **done**, **started* | 4 | An API that is not a bundled scripting runtime | Version breakage, slow scripts, geometry picked by position | JSON commands over CLI, control channel and MCP | | 5 | Located diagnostics | Generic error messages | Errors name the element, face or keyword; `mesh.quality` lists failing elements | | 6 | Lossless deck round trip | Unsupported keywords dropped on import | Deck readers keep unknown keywords and write them back (M9) | -| 7 | Safe, diffable model files | Binary model databases that corrupt or lock to a version | `.modelcraft`: text, line-diffable definition and journal, checksummed binary mesh, versioned format tag, atomic saves with backup, autosave (M3, done) | +| 7 | Safe, diffable model files | Binary model databases that corrupt or lock to a version | `.fieldcraft`: text, line-diffable definition and journal, checksummed binary mesh, versioned format tag, atomic saves with backup, autosave (M3, done) | | 8 | Geometry checked before meshing | Slivers and tiny faces fail late | Surface preparation reports defects with their location (M1) | | 9 | No size caps, any solver | Node limits in learning editions; one solver per tool | Free, open source, three deck formats | | 10 | Clear step history | Loads and BCs propagate between steps implicitly | An explicit step × load/BC table (M3) | @@ -33,7 +33,7 @@ products are run, no documentation text is copied). Status: **done**, **started* | Mesh | Size field: global, per face and edge seeds, bias, curvature and proximity refinement | M2 | yes | planned | | Mesh | Free tet meshing (tet4, tet10 with mid-side nodes on the geometry) | M2 | | started | | Mesh | Quality metrics, certified validity, verify per set | M1–M2 | yes | done (metrics, gate) | -| Mesh | 2D quad meshing of faces | M5 | yes | started (`modelcraft_mesh::quad`; `mesh.planar`: 2D models of a polygon or a planar face, quad4/quad8/tri3/tri6, edges tagged by their neighbouring face) | +| Mesh | 2D quad meshing of faces | M5 | yes | started (`fieldcraft_mesh::quad`; `mesh.planar`: 2D models of a polygon or a planar face, quad4/quad8/tri3/tri6, edges tagged by their neighbouring face) | | Mesh | Mapped hex of six-sided regions | M5 | yes | done (`mesh.hex.mapped`) | | Mesh | Sweep hex (extrusions, revolutions) | M5 | yes | done (`mesh.hex.sweep`: multi-level extrusions, revolutions, one-to-one sweeps) | | Mesh | Automatic decomposition into sweepable and mappable regions | M5 | yes | planned | @@ -69,7 +69,7 @@ products are run, no documentation text is copied). Status: **done**, **started* | Results | Readers for public result formats (Nastran OP2, LS-DYNA d3plot, Abaqus `.fil`) | M9 | | planned | | Results | Mesh convergence studies | M9 | | planned | | App | Desktop viewport, model tree, commands, control channel | continuous | | done | -| App | Undo/redo on the command journal, autosave, `.modelcraft` file | M3 | | done | +| App | Undo/redo on the command journal, autosave, `.fieldcraft` file | M3 | | done | | App | MCP server, browser build | continuous | | planned | ## Deck notes for the writers @@ -84,14 +84,14 @@ products are run, no documentation text is copied). Status: **done**, **started* `crates/decks/src/abaqus.rs`). The step table is bridged to Abaqus's propagation: zero-valued boundary conditions active in every step go to the initial state, and steps restate their boundary conditions and loads with `OP=NEW` when the active set changes. -- Nastran (`modelcraft_decks::nastran`): entries with a real use large (16-column) fields, the rest +- Nastran (`fieldcraft_decks::nastran`): entries with a real use large (16-column) fields, the rest small fields; a real is exact when its shortest round-trip form fits 16 columns, else rounded to at least ten significant digits. Linear solids map full integration to `PSOLID` `TWO`/`THREE` + `FULL`, hex20 reduced to `TWO` + `FULL`, hex8 incompatible modes to `BUBBLE`; one-point reduced linear solids, hybrid solids, reduced-integration or one-point shells, body forces, gravity on part of the model, orthotropic beams and rods, and orthotropic shells off planes of constant Z are reported as unsupported. Static and frequency steps need separate decks (`deck.write` `steps`). -- LS-DYNA (`modelcraft_decks::lsdyna`, `deck.write {"format": "lsdyna"}`): long format (20-column +- LS-DYNA (`fieldcraft_decks::lsdyna`, `deck.write {"format": "lsdyna"}`): long format (20-column fields; every value with up to 13 significant digits, and 17-digit values from 0.01 to 1e17, are written exactly; the rest is rounded to at least 13 digits and counted). The 10-node tetrahedron follows the manual's figure: mid-side nodes 8, 9, 10 on edges 2-4, 3-4, 1-4 (the model has them diff --git a/docs/solvers/literature.md b/docs/solvers/literature.md index 39eaab7..852d060 100644 --- a/docs/solvers/literature.md +++ b/docs/solvers/literature.md @@ -1,4 +1,4 @@ -# Solver literature survey for ModelCraft: explicit dynamics, equations of state, lattice Boltzmann, FSI +# Solver literature survey for FieldCraft: explicit dynamics, equations of state, lattice Boltzmann, FSI Compiled 2026-10-13. Scope: three new solver families on top of the planned implicit linear solver (M6): (A) an explicit Lagrangian finite element solver of the LS-DYNA / Abaqus-Explicit class, (B) equations of state for @@ -560,7 +560,7 @@ JCP 232(1):100–117, 2013, DOI [10.1016/j.jcp.2012.07.023](https://doi.org/10.1 - **Method.** For each wall link, the fraction q ∈ (0, 1] of the link inside the fluid; linear or quadratic interpolation of the reflected population, second-order for curved walls. - **Needs from meshing.** q comes from ray–triangle intersection of the lattice link with the face-tagged boundary - surface — robust predicates (modelcraft-mesh) give the inside/outside decision; q itself is a constructed value. + surface — robust predicates (fieldcraft-mesh) give the inside/outside decision; q itself is a constructed value. - **Limits.** Not mass conservative; small q (< ~0.01) can destabilise: treat as q = 0 or use the Mei variant. - **Also.** R. Mei, L.-S. Luo, W. Shyy, JCP 155(2):307–330, 1999, DOI [10.1006/jcph.1999.6334](https://doi.org/10.1006/jcph.1999.6334) — the alternative curved-boundary scheme. **Reference.** @@ -794,7 +794,7 @@ IB is simpler with a deforming FE surface; bounce-back is better for rigid or sl - S. W. Attaway, M. W. Heinstein, J. W. Swegle. *Coupling of smooth particle hydrodynamics with the finite element method.* Nucl. Eng. Des. 150:199–205, 1994. DOI [10.1016/0029-5493(94)90136-8](https://doi.org/10.1016/0029-5493(94)90136-8). - C. Antoci, M. Gallati, S. Sibilla. *Numerical simulation of fluid–structure interaction by SPH.* Comput. Struct. 85(11–14):879–890, 2007. DOI [10.1016/j.compstruc.2007.01.002](https://doi.org/10.1016/j.compstruc.2007.01.002). - **Assessment.** SPH shares the explicit solver's whole stack (time step, EOS, Johnson–Cook, artificial viscosity, - contact via the same penalty), so it slots *into* `modelcraft-explicit` as another discretisation — the natural + contact via the same penalty), so it slots *into* `fieldcraft-explicit` as another discretisation — the natural home for hypervelocity impact, fragmentation and blast with JWL, which LBM cannot do. As a general fluid solver it is noisy (pressure), needs many particles for accuracy and has the tensile instability; LBM beats it for viscous low-Mach flow. Code: SPHinXsys (Apache-2.0), SPlisHSPlasH (MIT), PySPH (BSD-3-style), Cabana (BSD-3): READ OK; @@ -812,7 +812,7 @@ IB is simpler with a deforming FE surface; bounce-back is better for rigid or sl codes) but coordinates/displacements over long runs need f64 accumulation; that rules out a naive GPU port. - **Layering.** `xtask/src/layers.rs` lists `wgpu` in `UI_CRATES`: nothing below L5 may depend on it. A GPU backend therefore needs either an owner decision to allow `wgpu` in a dedicated compute crate below L5 (headless compute is - not UI), or a `modelcraft-gpu` crate at L5 that implements a backend trait the L1 solver crates define. I recommend + not UI), or a `fieldcraft-gpu` crate at L5 that implements a backend trait the L1 solver crates define. I recommend the second (no rule change): the solver crate defines a `LatticeBackend` trait and a CPU implementation; the GPU implementation lives at L5. **Owner decision either way.** - **Determinism.** The project requires bit-identical output on every platform. GPU f32 results differ from CPU and @@ -859,25 +859,25 @@ Implicit and explicit share element kinematics (shape functions, quadrature, B-m | Layer | Crate | Contents | |---|---|---| -| L0 | `modelcraft-model` | + materials, EOS, sections/formulations, explicit and fluid steps, contact, fluid domains, coupling interfaces (F3) | -| L1 | `modelcraft-fem` (new) | element kernels (shape functions, quadrature, B/B-bar, one-point hex with FB hourglass and Belytschko–Bindeman stabilisation, Bonet–Burton tet, BLT shell), constitutive updates (hypoelastic, objective rates, J2 radial return, Johnson–Cook, damage), EOS evaluation. Pure functions over `model` types; no solver loop. | -| L2 | `modelcraft-lbm` (new) | lattices, collision operators, BCs, voxelisation from the face-tagged surface and the octree from `mesh`\*, stepping on CPU, momentum-exchange forces, IB kernels; a backend trait for GPU | -| L2 | `modelcraft-solver` (moved up from L1) | implicit linear static/modal (M6), later nonlinear implicit; uses `fem` | -| L2 | `modelcraft-explicit` (new) | central-difference driver, time step, mass scaling, q, contact (search + penalty), erosion, energy accounting, output; uses `fem` (and later SPH) | -| L3 | `modelcraft-fsi` (new) | IB coupling, interface mapping, Aitken/IQN-ILS, subcycling, coupled step driver; uses `explicit`, `solver`, `lbm` | +| L0 | `fieldcraft-model` | + materials, EOS, sections/formulations, explicit and fluid steps, contact, fluid domains, coupling interfaces (F3) | +| L1 | `fieldcraft-fem` (new) | element kernels (shape functions, quadrature, B/B-bar, one-point hex with FB hourglass and Belytschko–Bindeman stabilisation, Bonet–Burton tet, BLT shell), constitutive updates (hypoelastic, objective rates, J2 radial return, Johnson–Cook, damage), EOS evaluation. Pure functions over `model` types; no solver loop. | +| L2 | `fieldcraft-lbm` (new) | lattices, collision operators, BCs, voxelisation from the face-tagged surface and the octree from `mesh`\*, stepping on CPU, momentum-exchange forces, IB kernels; a backend trait for GPU | +| L2 | `fieldcraft-solver` (moved up from L1) | implicit linear static/modal (M6), later nonlinear implicit; uses `fem` | +| L2 | `fieldcraft-explicit` (new) | central-difference driver, time step, mass scaling, q, contact (search + penalty), erosion, energy accounting, output; uses `fem` (and later SPH) | +| L3 | `fieldcraft-fsi` (new) | IB coupling, interface mapping, Aitken/IQN-ILS, subcycling, coupled step driver; uses `explicit`, `solver`, `lbm` | | L4… | `doc`, `io`, `engine`, then L5+ shifts up by two | results (time histories, field frames) in `doc`/`io` | -| L5+ | `modelcraft-gpu` (new, with the UI layer or an owner-approved compute layer) | wgpu implementations of the solver backend traits | +| L5+ | `fieldcraft-gpu` (new, with the UI layer or an owner-approved compute layer) | wgpu implementations of the solver backend traits | -\* `modelcraft-lbm` needs the octree and predicates from `modelcraft-mesh` (L1), so it sits at L2. **`lbm` at L2** next to `solver` and `explicit` (it may then use `mesh`'s octree and +\* `fieldcraft-lbm` needs the octree and predicates from `fieldcraft-mesh` (L1), so it sits at L2. **`lbm` at L2** next to `solver` and `explicit` (it may then use `mesh`'s octree and predicates), and `fem` at L1. That is one coherent rule: L1 = geometry and math kernels, L2 = solvers, L3 = coupling. Cost of this being wrong: renumbering the layer table is a mechanical edit to `xtask/src/layers.rs` and -`docs/architecture.md`; the risk is churn, not breakage. The alternative (one `modelcraft-solver` crate with +`docs/architecture.md`; the risk is churn, not breakage. The alternative (one `fieldcraft-solver` crate with `implicit`/`explicit`/`lbm`/`fsi` modules) needs no layer change, but a 50k-line crate compiles slowly and lets the LBM depend on FE internals by accident. If the owner prefers no renumbering, take the single-crate option and enforce module boundaries with `pub(crate)` discipline. This layout change is an owner decision. -### F3. Solver-neutral model extensions in `modelcraft-model` +### F3. Solver-neutral model extensions in `fieldcraft-model` - **Materials** (named, unit-consistent, no product numbering): density; isotropic elastic; J2 plasticity with isotropic/kinematic hardening (bilinear, power law, table); Johnson–Cook strength (A, B, n, C, m, ε̇₀, T_room, @@ -906,7 +906,7 @@ All of these must map to the LS-DYNA keyword and Abaqus input concepts M5 writes | # | Milestone | Needs from meshing | Verification (analytic first) | |---|---|---|---| -| X0 | `modelcraft-fem` extraction with M6: hex8/tet4 kinematics, elastic and J2 updates, shared with the implicit solver | M2 tets, generated boxes | patch tests, J2 uniaxial curve, rigid rotation | +| X0 | `fieldcraft-fem` extraction with M6: hex8/tet4 kinematics, elastic and J2 updates, shared with the implicit solver | M2 tets, generated boxes | patch tests, J2 uniaxial curve, rigid rotation | | X1 | Explicit core: central difference, lumped mass, element Δt (FB 1984), one-point hex8 + FB hourglass, Bonet–Burton tet4, artificial viscosity, energy accounting, prescribed BCs, initial velocity, history output | structured hex8 boxes generated in code (no M3 needed), M2 tets | bar wave (front position and amplitude), bar–rigid wall contact time 2L/c, energy balance, cantilever period vs M6 modal | | X2 | Large deformation materials: Hughes–Winget Jaumann, Flanagan–Taylor Green–Naghdi, J2 radial return, Johnson–Cook strength + damage, erosion | same | Dienes simple shear (both rates, closed form), Taylor bar (Taylor 1948 analytic estimate; experimental comparison only if the owner allows) | | X3 | Contact: BVH global search, node-to-segment penalty two-pass + friction, pinball for eroding surfaces, rigid walls | surface extraction of volume meshes (have it) | two bars colliding (momentum exchange, analytic), block sliding with friction on a slope (analytic), Hertz sphere-on-plane at low speed (analytic contact time and force) | @@ -956,7 +956,7 @@ the better pairing — this is why FSI should support both structure solvers. ## Part G: Implicit solver (M4), as implemented -The papers and books `modelcraft-fem` and `modelcraft-solver` are written from (cited again in +The papers and books `fieldcraft-fem` and `fieldcraft-solver` are written from (cited again in the module docs): - Elements: O. C. Zienkiewicz, R. L. Taylor, J. Z. Zhu, *The Finite Element Method: Its Basis @@ -1031,7 +1031,7 @@ the module docs): ## Part H: Explicit solver core (X1), as implemented -The papers and books `modelcraft-explicit` and `modelcraft_fem::uniform` are written from (cited +The papers and books `fieldcraft-explicit` and `fieldcraft_fem::uniform` are written from (cited again in the module docs): - Time integration, energy balance, element eigenvalue inequality: T. Belytschko, W. K. Liu, @@ -1065,7 +1065,7 @@ again in the module docs): ## Part I: Lattice Boltzmann core (M12, plan L1), as implemented -`modelcraft-lbm` is written from these papers and the book (cited again in its module docs). No +`fieldcraft-lbm` is written from these papers and the book (cited again in its module docs). No lattice Boltzmann package's code was opened. - Lattice, equilibrium, units, stream–collide, bounce-back, forcing and boundary conditions: @@ -1082,7 +1082,7 @@ lattice Boltzmann package's code was opened. lattice Boltzmann simulations*, J. Stat. Mech. 2010, P01018 (DOI not checked). - Stability limits (Mach ≤ 0.3, τ⁺ ≥ 0.505, runtime speed ≤ 0.5): Sterling & Chen 1996 and Lallemand & Luo 2000 (C1.5) for the shrinking stable region as τ → ½. -- Voxelisation: Shewchuk's exact `orient2d`/`orient3d` (`modelcraft_mesh::predicates`) with a +- Voxelisation: Shewchuk's exact `orient2d`/`orient3d` (`fieldcraft_mesh::predicates`) with a symbolic perturbation after H. Edelsbrunner, E. P. Mücke, *Simulation of Simplicity*, ACM TOG 9(1):66–104, 1990; link faces by nearest triangle, C. Ericson, *Real-Time Collision Detection*, Morgan Kaufmann 2005, §5.1.5. @@ -1095,7 +1095,7 @@ lattice Boltzmann package's code was opened. ## Part J: Large deformation and inelastic materials (X2), as implemented -The papers and books `modelcraft_fem::{rotation, plasticity}` and `modelcraft-explicit`'s X2 +The papers and books `fieldcraft_fem::{rotation, plasticity}` and `fieldcraft-explicit`'s X2 update are written from (cited again in the module docs): - Updated Lagrangian one-point elements, strain increments on the midpoint configuration: @@ -1128,7 +1128,7 @@ update are written from (cited again in the module docs): - **Conduction.** Galerkin form of ρc Ṫ = ∇·(k∇T) + Q with surface flux and convection: O. C. Zienkiewicz, R. L. Taylor, J. Z. Zhu, *The Finite Element Method: Its Basis and Fundamentals*, 7th ed., Butterworth-Heinemann 2013, §3.2; T. J. R. Hughes, *The Finite Element - Method*, Dover 2000, ch. 1. Kernels in `modelcraft_fem::heat`, solver in `modelcraft_solver::heat`. + Method*, Dover 2000, ch. 1. Kernels in `fieldcraft_fem::heat`, solver in `fieldcraft_solver::heat`. - **Time integration.** The generalized trapezoidal (θ) family, unconditionally stable for θ ≥ ½ (Hughes 2000, §8.2): backward Euler (θ = 1, the default) and Crank–Nicolson (θ = ½). - **Thermal strain.** Initial-strain load ∫ Bᵀ D ε_th dV and stress D(ε − ε_th) diff --git a/docs/verification.md b/docs/verification.md index 0e278fa..cabfad2 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -14,11 +14,11 @@ Rules: material, loads, boundary conditions). We never run a commercial product and never copy its input files, manual text or figures; only the published numbers are used, with a citation. - Each case lives in `tests/oracle//`: - - `recipe.json` — the model as ModelCraft commands (or a SolidCraft recipe for geometry); + - `recipe.json` — the model as FieldCraft commands (or a SolidCraft recipe for geometry); - `expected.json` — the reference values with `kind` (theory | experiment | benchmark), `source` (citation: document, table or figure, page), `quantity`, `value`, `tolerance` and the reason for that tolerance (mesh, measurement scatter, model differences); - - `fixture.json` — ModelCraft's accepted result, written by `cargo xtask oracle --bless ` + - `fixture.json` — FieldCraft's accepted result, written by `cargo xtask oracle --bless ` only after the case passes against `expected.json`. - `cargo xtask oracle` runs every case, compares with `expected.json` (pass/fail with error) and with `fixture.json` (any change beyond the fixture tolerance fails CI, even if the reference @@ -30,7 +30,7 @@ Rules: ## The case format -`cargo xtask oracle` builds `modelcraft-testkit`'s `oracle` binary (release) once and runs every +`cargo xtask oracle` builds `fieldcraft-testkit`'s `oracle` binary (release) once and runs every case through the command engine; the results are in [oracle.md](oracle.md). ``` @@ -40,7 +40,7 @@ cargo xtask oracle --bless nafems-le1 # freeze a passing case's results (or -- cargo xtask oracle --quick # skip cases marked "slow" ``` -- `recipe.json` is a script `modelcraft-cli run` also accepts: a JSON array of +- `recipe.json` is a script `fieldcraft-cli run` also accepts: a JSON array of `{"command", "params"}`; a step may carry an `"id"` so a quantity can read its output. Meshes come from `mesh.block` (mapped hex, tet or wedge blocks with arcs and elliptical edges, named faces, edges and vertices that sets select), `mesh.tet.generate`, or nodes and elements written @@ -56,7 +56,7 @@ cargo xtask oracle --quick # skip cases marked "slow" "quantities": [{"id": "syy_D", "quantity": "σ_yy at D (2, 0)", "measure": {"command": "solve.probe", "params": {"step": "Load", "at": [[2, 0, 0]]}, "pointer": "/nodes/0/stress/1"}, "value": 92.7, "tolerance": {"relative": 0.01}, "reason": "why this tolerance", - "known_failure": "optional: why ModelCraft misses the reference today"}]} + "known_failure": "optional: why FieldCraft misses the reference today"}]} ``` A measure reads a JSON pointer either from a recipe step's output (`"from": ""`) or from a command run after the recipe (`"command"`, `"params"`); an optional `"scale"` multiplies it. diff --git a/tests/oracle/bar-pressure-wave/fixture.json b/tests/oracle/bar-pressure-wave/fixture.json index 711c5e4..be9db37 100644 --- a/tests/oracle/bar-pressure-wave/fixture.json +++ b/tests/oracle/bar-pressure-wave/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "balance": { "band": 0.01, diff --git a/tests/oracle/bar-rigid-wall-impact/fixture.json b/tests/oracle/bar-rigid-wall-impact/fixture.json index 6a03a2d..f3c6c28 100644 --- a/tests/oracle/bar-rigid-wall-impact/fixture.json +++ b/tests/oracle/bar-rigid-wall-impact/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "ke": { "band": 1e-9, diff --git a/tests/oracle/cantilever-frequencies/fixture.json b/tests/oracle/cantilever-frequencies/fixture.json index 61a1c5f..3909cd8 100644 --- a/tests/oracle/cantilever-frequencies/fixture.json +++ b/tests/oracle/cantilever-frequencies/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "f1": { "band": 1.2770863695053732e-11, diff --git a/tests/oracle/cantilever-hex20/fixture.json b/tests/oracle/cantilever-hex20/fixture.json index 4c1d5f2..392c20f 100644 --- a/tests/oracle/cantilever-hex20/fixture.json +++ b/tests/oracle/cantilever-hex20/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "reaction": { "band": 1.0000000000401866e-9, diff --git a/tests/oracle/cantilever-hex8-incompatible/fixture.json b/tests/oracle/cantilever-hex8-incompatible/fixture.json index 0800363..2f3b5f0 100644 --- a/tests/oracle/cantilever-hex8-incompatible/fixture.json +++ b/tests/oracle/cantilever-hex8-incompatible/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "reaction": { "band": 1e-9, diff --git a/tests/oracle/cantilever-tet10/fixture.json b/tests/oracle/cantilever-tet10/fixture.json index 67fb136..a66ac0c 100644 --- a/tests/oracle/cantilever-tet10/fixture.json +++ b/tests/oracle/cantilever-tet10/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "reaction": { "band": 1.0000000000174362e-9, diff --git a/tests/oracle/cdt-cylinder/fixture.json b/tests/oracle/cdt-cylinder/fixture.json index 3a847e4..3e9889a 100644 --- a/tests/oracle/cdt-cylinder/fixture.json +++ b/tests/oracle/cdt-cylinder/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "invalid": { "band": 0.0, diff --git a/tests/oracle/cdt-stepped-block/fixture.json b/tests/oracle/cdt-stepped-block/fixture.json index d4a0ebe..a9e9727 100644 --- a/tests/oracle/cdt-stepped-block/fixture.json +++ b/tests/oracle/cdt-stepped-block/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "invalid": { "band": 0.0, diff --git a/tests/oracle/free-beam-frequencies/fixture.json b/tests/oracle/free-beam-frequencies/fixture.json index f154d4d..a7422f7 100644 --- a/tests/oracle/free-beam-frequencies/fixture.json +++ b/tests/oracle/free-beam-frequencies/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "f7": { "band": 8.126420309591101e-11, diff --git a/tests/oracle/j2-uniaxial-explicit/fixture.json b/tests/oracle/j2-uniaxial-explicit/fixture.json index 8df922f..a576c85 100644 --- a/tests/oracle/j2-uniaxial-explicit/fixture.json +++ b/tests/oracle/j2-uniaxial-explicit/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "plastic_strain": { "band": 1.967821782178218e-10, diff --git a/tests/oracle/kirsch-plate-plane-stress/fixture.json b/tests/oracle/kirsch-plate-plane-stress/fixture.json index 9b0b3ec..352edc7 100644 --- a/tests/oracle/kirsch-plate-plane-stress/fixture.json +++ b/tests/oracle/kirsch-plate-plane-stress/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "kt": { "band": 3.0028789592374985e-9, diff --git a/tests/oracle/kirsch-plate/fixture.json b/tests/oracle/kirsch-plate/fixture.json index ec95459..b6587d6 100644 --- a/tests/oracle/kirsch-plate/fixture.json +++ b/tests/oracle/kirsch-plate/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "kt": { "band": 3.0120654194508524e-9, diff --git a/tests/oracle/lame-cylinder-axisymmetric/fixture.json b/tests/oracle/lame-cylinder-axisymmetric/fixture.json index 1c59329..4be72b1 100644 --- a/tests/oracle/lame-cylinder-axisymmetric/fixture.json +++ b/tests/oracle/lame-cylinder-axisymmetric/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "axial": { "band": 2.0083926981151736e-8, diff --git a/tests/oracle/lame-cylinder-plane-strain/fixture.json b/tests/oracle/lame-cylinder-plane-strain/fixture.json index 95125cf..e99d33e 100644 --- a/tests/oracle/lame-cylinder-plane-strain/fixture.json +++ b/tests/oracle/lame-cylinder-plane-strain/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "hoop_a": { "band": 1.6666666666666668e-7, diff --git a/tests/oracle/lame-cylinder/fixture.json b/tests/oracle/lame-cylinder/fixture.json index f0382a1..f5cc707 100644 --- a/tests/oracle/lame-cylinder/fixture.json +++ b/tests/oracle/lame-cylinder/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "hoop_a": { "band": 1.6666666666666668e-7, diff --git a/tests/oracle/mh-curved-cantilever/fixture.json b/tests/oracle/mh-curved-cantilever/fixture.json index 6344008..8c39bab 100644 --- a/tests/oracle/mh-curved-cantilever/fixture.json +++ b/tests/oracle/mh-curved-cantilever/fixture.json @@ -1,4 +1,4 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": {} } diff --git a/tests/oracle/mh-solid-patch/fixture.json b/tests/oracle/mh-solid-patch/fixture.json index 1f8c715..0979ba4 100644 --- a/tests/oracle/mh-solid-patch/fixture.json +++ b/tests/oracle/mh-solid-patch/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 2.000000000000001e-6, diff --git a/tests/oracle/mh-straight-cantilever/fixture.json b/tests/oracle/mh-straight-cantilever/fixture.json index 717d24a..15ea06f 100644 --- a/tests/oracle/mh-straight-cantilever/fixture.json +++ b/tests/oracle/mh-straight-cantilever/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "extension": { "band": 3.0000000000000005e-14, diff --git a/tests/oracle/mh-thick-cylinder/fixture.json b/tests/oracle/mh-thick-cylinder/fixture.json index cb9f0b1..15b1217 100644 --- a/tests/oracle/mh-thick-cylinder/fixture.json +++ b/tests/oracle/mh-thick-cylinder/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "ur_1": { "band": 5.0399e-12, diff --git a/tests/oracle/nafems-fv32/fixture.json b/tests/oracle/nafems-fv32/fixture.json index 19411e3..44089d4 100644 --- a/tests/oracle/nafems-fv32/fixture.json +++ b/tests/oracle/nafems-fv32/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "f1": { "band": 4.4623e-8, diff --git a/tests/oracle/nafems-le1-plane-stress/fixture.json b/tests/oracle/nafems-le1-plane-stress/fixture.json index c9527d4..3b65566 100644 --- a/tests/oracle/nafems-le1-plane-stress/fixture.json +++ b/tests/oracle/nafems-le1-plane-stress/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "syy_D": { "band": 9.27e-8, diff --git a/tests/oracle/nafems-le1/fixture.json b/tests/oracle/nafems-le1/fixture.json index 34db2ba..122c150 100644 --- a/tests/oracle/nafems-le1/fixture.json +++ b/tests/oracle/nafems-le1/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "syy_D": { "band": 9.286850287265874e-8, diff --git a/tests/oracle/nafems-le10/fixture.json b/tests/oracle/nafems-le10/fixture.json index af70be2..2d8090b 100644 --- a/tests/oracle/nafems-le10/fixture.json +++ b/tests/oracle/nafems-le10/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "syy_D": { "band": 5.38e-9, diff --git a/tests/oracle/patch-hex20-reduced/fixture.json b/tests/oracle/patch-hex20-reduced/fixture.json index f2d49c4..75954b2 100644 --- a/tests/oracle/patch-hex20-reduced/fixture.json +++ b/tests/oracle/patch-hex20-reduced/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 1.0000000000000003e-9, diff --git a/tests/oracle/patch-hex20/fixture.json b/tests/oracle/patch-hex20/fixture.json index b5cd4e4..2885457 100644 --- a/tests/oracle/patch-hex20/fixture.json +++ b/tests/oracle/patch-hex20/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 1.0000000000000034e-9, diff --git a/tests/oracle/patch-hex8-incompatible/fixture.json b/tests/oracle/patch-hex8-incompatible/fixture.json index 28c9cb9..051057b 100644 --- a/tests/oracle/patch-hex8-incompatible/fixture.json +++ b/tests/oracle/patch-hex8-incompatible/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 1.0000000000000005e-9, diff --git a/tests/oracle/patch-hex8/fixture.json b/tests/oracle/patch-hex8/fixture.json index f19e55f..39440fe 100644 --- a/tests/oracle/patch-hex8/fixture.json +++ b/tests/oracle/patch-hex8/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 1e-9, diff --git a/tests/oracle/patch-tet10/fixture.json b/tests/oracle/patch-tet10/fixture.json index b77d95e..21fa60b 100644 --- a/tests/oracle/patch-tet10/fixture.json +++ b/tests/oracle/patch-tet10/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 1e-9, diff --git a/tests/oracle/patch-tet4/fixture.json b/tests/oracle/patch-tet4/fixture.json index 74d3959..c798512 100644 --- a/tests/oracle/patch-tet4/fixture.json +++ b/tests/oracle/patch-tet4/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 1e-9, diff --git a/tests/oracle/patch-wedge6/fixture.json b/tests/oracle/patch-wedge6/fixture.json index 50b5f45..1710f6a 100644 --- a/tests/oracle/patch-wedge6/fixture.json +++ b/tests/oracle/patch-wedge6/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sxx": { "band": 1.0000000000000007e-9, diff --git a/tests/oracle/pure-bending-coupling/fixture.json b/tests/oracle/pure-bending-coupling/fixture.json index d22eeb7..54094d3 100644 --- a/tests/oracle/pure-bending-coupling/fixture.json +++ b/tests/oracle/pure-bending-coupling/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "sigma_top": { "band": 1.5000000000000002e-8, diff --git a/tests/oracle/rotating-cylinder-axisymmetric/fixture.json b/tests/oracle/rotating-cylinder-axisymmetric/fixture.json index a2f4fe3..76a297c 100644 --- a/tests/oracle/rotating-cylinder-axisymmetric/fixture.json +++ b/tests/oracle/rotating-cylinder-axisymmetric/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "hoop_rim": { "band": 1.2872894568377547e-10, diff --git a/tests/oracle/rotating-disc-plane-stress/fixture.json b/tests/oracle/rotating-disc-plane-stress/fixture.json index 188a02a..17be2aa 100644 --- a/tests/oracle/rotating-disc-plane-stress/fixture.json +++ b/tests/oracle/rotating-disc-plane-stress/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "hoop_bore": { "band": 6.5312e-8, diff --git a/tests/oracle/scordelis-lo-roof-solid/fixture.json b/tests/oracle/scordelis-lo-roof-solid/fixture.json index beec7f1..0a04ed6 100644 --- a/tests/oracle/scordelis-lo-roof-solid/fixture.json +++ b/tests/oracle/scordelis-lo-roof-solid/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "uz": { "band": 3.024e-8, diff --git a/tests/oracle/tube-torsion-coupling/fixture.json b/tests/oracle/tube-torsion-coupling/fixture.json index 030f421..8c0d9e4 100644 --- a/tests/oracle/tube-torsion-coupling/fixture.json +++ b/tests/oracle/tube-torsion-coupling/fixture.json @@ -1,5 +1,5 @@ { - "note": "ModelCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", + "note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.", "values": { "shear": { "band": 1.0813925570161819e-6, diff --git a/tools/demo-video/README.md b/tools/demo-video/README.md index 179957a..4a5c151 100644 --- a/tools/demo-video/README.md +++ b/tools/demo-video/README.md @@ -16,10 +16,10 @@ It honours `CARGO_TARGET_DIR`. What it does: -1. Builds `modelcraft`, `modelcraft-cli` and the testkit's `corpus` tool (release). +1. Builds `fieldcraft`, `fieldcraft-cli` and the testkit's `corpus` tool (release). 2. Writes the corpus I-beam (`tests/corpus/i-beam`) as a `.solidcraft` design to a temporary folder with `corpus --part i-beam --design`. -3. Starts the app with `--control PORT` and `modelcraft-cli mcp --connect 127.0.0.1:PORT`, and +3. Starts the app with `--control PORT` and `fieldcraft-cli mcp --connect 127.0.0.1:PORT`, and speaks MCP (JSON-RPC over stdio) to it. Every step is an MCP tool call: `open` the design, `mesh` (`hex_sweep`, 4 mm), `batch` (steel, a solid section, the root clamp, a kinematic coupling of the free end to a reference point loaded with 1000 N), `check_model`, `solve` diff --git a/tools/demo-video/make.py b/tools/demo-video/make.py index afac164..2e1537e 100755 --- a/tools/demo-video/make.py +++ b/tools/demo-video/make.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Regenerate the README demo video: an I-beam cantilever analysed by an agent over MCP. -Every analysis step goes through the MCP server (`modelcraft-cli mcp --connect`), bridged to +Every analysis step goes through the MCP server (`fieldcraft-cli mcp --connect`), bridged to the desktop app, and every frame is the app window captured offscreen through the MCP `screenshot` tool (`source: window`). See README.md beside this file. @@ -54,7 +54,7 @@ def target_dir(): def build(): cargo = os.environ.get("CARGO", "cargo") - for args in (["-p", "modelcraft", "-p", "modelcraft-cli"], ["-p", "modelcraft-testkit", "--bin", "corpus"]): + for args in (["-p", "fieldcraft", "-p", "fieldcraft-cli"], ["-p", "fieldcraft-testkit", "--bin", "corpus"]): subprocess.run([cargo, "build", "--release", *args], cwd=ROOT, check=True) @@ -78,7 +78,7 @@ def wait_for_port(port, app, timeout=180.0): class Mcp: - """A JSON-RPC client of `modelcraft-cli mcp` over stdio.""" + """A JSON-RPC client of `fieldcraft-cli mcp` over stdio.""" def __init__(self, cli, port): self.proc = subprocess.Popen( @@ -258,7 +258,7 @@ def main(): if not args.no_build: build() bin_dir = target_dir() / "release" - work = Path(tempfile.mkdtemp(prefix="modelcraft-demo-")) + work = Path(tempfile.mkdtemp(prefix="fieldcraft-demo-")) frames_dir = args.frames.resolve() if args.frames else work / "frames" frames_dir.mkdir(parents=True, exist_ok=True) design = work / "ibeam.solidcraft" @@ -266,11 +266,11 @@ def main(): port = free_port() app_log = open(work / "app.log", "w") - app = subprocess.Popen([str(bin_dir / "modelcraft"), "--control", str(port)], stdout=app_log, stderr=subprocess.STDOUT) + app = subprocess.Popen([str(bin_dir / "fieldcraft"), "--control", str(port)], stdout=app_log, stderr=subprocess.STDOUT) mcp = None try: wait_for_port(port, app) - mcp = Mcp(bin_dir / "modelcraft-cli", port) + mcp = Mcp(bin_dir / "fieldcraft-cli", port) film = Film(mcp, frames_dir) result = analyse(mcp, film, design) encode(film, frames_dir, args.out / "ibeam-analysis.mp4", args.out / "ibeam-analysis.gif") diff --git a/xtask/src/app_icon.rs b/xtask/src/app_icon.rs index be3b07f..aa6303a 100644 --- a/xtask/src/app_icon.rs +++ b/xtask/src/app_icon.rs @@ -1,4 +1,4 @@ -//! `cargo xtask app-icon`: draw ModelCraft's application icon family with VectorCraft. +//! `cargo xtask app-icon`: draw FieldCraft's application icon family with VectorCraft. //! //! The mark is an L-shaped bracket with a bolt hole, drawn in isometric view and covered by a //! structured hexahedral mesh (an O-grid around the hole). The faces are separated by @@ -12,9 +12,9 @@ //! - `icon-small.vectorcraft`, `icon-small.svg`, `icon-small.png`: simplified artwork (no mesh, //! no floor grid, faces meeting edge to edge so the tones alone separate them) for 16–32 px. //! - `icon.iconset/` (sips), `icon.icns` (iconutil) and `icon.ico` (ImageMagick). -//! - `ModelCraft.icon/`: an Icon Composer document with separate floor-grid (Default and Dark), +//! - `FieldCraft.icon/`: an Icon Composer document with separate floor-grid (Default and Dark), //! part and mesh layers; their editable sources are `layers/*.vectorcraft`. -//! - `Assets.car`: Apple's actool compilation of `ModelCraft.icon`, when Xcode 26 or later is +//! - `Assets.car`: Apple's actool compilation of `FieldCraft.icon`, when Xcode 26 or later is //! installed (otherwise the step is skipped with a note and the checked-in catalog stays). //! //! macOS only (sips, iconutil); ImageMagick (`magick`) writes the Windows icon. @@ -498,10 +498,10 @@ fn composite(detail: Detail, appearance: Appearance) -> Result { layers.push(mesh_layer()?); } let title = match (detail, appearance) { - (Detail::Full, Appearance::Light) => "ModelCraft application icon", - (Detail::Full, Appearance::Dark) => "ModelCraft application icon (dark preview)", - (Detail::Small, Appearance::Light) => "ModelCraft application icon, small sizes", - (Detail::Small, Appearance::Dark) => "ModelCraft application icon, small sizes (dark preview)", + (Detail::Full, Appearance::Light) => "FieldCraft application icon", + (Detail::Full, Appearance::Dark) => "FieldCraft application icon (dark preview)", + (Detail::Small, Appearance::Light) => "FieldCraft application icon, small sizes", + (Detail::Small, Appearance::Dark) => "FieldCraft application icon, small sizes (dark preview)", }; Ok(Doc { title, layers }) } @@ -617,7 +617,7 @@ fn icon_json() -> Value { pub fn run(root: &Path) -> Result<(), String> { let dir = root.join("assets/app-icon"); let layers = dir.join("layers"); - let bundle = dir.join("ModelCraft.icon"); + let bundle = dir.join("FieldCraft.icon"); let assets = bundle.join("Assets"); let iconset = dir.join("icon.iconset"); let target = std::env::var_os("CARGO_TARGET_DIR").map_or_else(|| root.join("target"), PathBuf::from); @@ -639,10 +639,10 @@ pub fn run(root: &Path) -> Result<(), String> { // Icon Composer layers: the system supplies the backdrop. let single = |title, layer| Doc { title, layers: vec![layer] }; let layer_docs = [ - ("01-floor", single("ModelCraft icon floor grid", floor_layer(Appearance::Light))), - ("01-floor-dark", single("ModelCraft icon floor grid (Dark)", floor_layer(Appearance::Dark))), - ("02-part", single("ModelCraft icon part", part_layer(Detail::Full)?)), - ("03-mesh", single("ModelCraft icon mesh", mesh_layer()?)), + ("01-floor", single("FieldCraft icon floor grid", floor_layer(Appearance::Light))), + ("01-floor-dark", single("FieldCraft icon floor grid (Dark)", floor_layer(Appearance::Dark))), + ("02-part", single("FieldCraft icon part", part_layer(Detail::Full)?)), + ("03-mesh", single("FieldCraft icon mesh", mesh_layer()?)), ]; for (name, doc) in &layer_docs { vectorcraft(doc, &layers.join(format!("{name}.vectorcraft")), &[assets.join(format!("{name}.svg"))])?; @@ -682,7 +682,7 @@ pub fn run(root: &Path) -> Result<(), String> { "--minimum-deployment-target", "11.0", "--app-icon", - "ModelCraft", + "FieldCraft", "--output-partial-info-plist", path_str(&partial)?, "--output-format", @@ -692,9 +692,9 @@ pub fn run(root: &Path) -> Result<(), String> { match compiled { Ok(()) => { std::fs::copy(work.join("Assets.car"), dir.join("Assets.car")).map_err(|e| format!("Assets.car: {e}"))?; - println!("app-icon: compiled ModelCraft.icon into assets/app-icon/Assets.car"); + println!("app-icon: compiled FieldCraft.icon into assets/app-icon/Assets.car"); } - Err(e) => println!("app-icon: note: actool could not compile ModelCraft.icon, Assets.car left as it was ({e})"), + Err(e) => println!("app-icon: note: actool could not compile FieldCraft.icon, Assets.car left as it was ({e})"), } println!("app-icon: wrote assets/app-icon (dark previews in {})", work.display()); Ok(()) diff --git a/xtask/src/layers.rs b/xtask/src/layers.rs index 3ec4848..8c0271f 100644 --- a/xtask/src/layers.rs +++ b/xtask/src/layers.rs @@ -32,7 +32,7 @@ impl Class { } } -/// The layering table. Names are package names without the `modelcraft-` +/// The layering table. Names are package names without the `fieldcraft-` /// prefix. pub const TABLE: &[(&str, Class)] = &[ // L0: the solver-neutral model. @@ -58,7 +58,7 @@ pub const TABLE: &[(&str, Class)] = &[ ("mcp", Class::Layer(7)), ("testkit", Class::Testkit), // apps and tooling - ("modelcraft", Class::Exempt), + ("fieldcraft", Class::Exempt), ("cli", Class::Exempt), ("web", Class::Exempt), ("xtask", Class::Exempt), @@ -92,7 +92,7 @@ pub const TESTKIT_LAYER: u8 = UI_MIN_LAYER + 1; const APPS_LAYER: u8 = TESTKIT_LAYER + 1; pub fn short_name(pkg: &str) -> &str { - pkg.strip_prefix("modelcraft-").unwrap_or(pkg) + pkg.strip_prefix("fieldcraft-").unwrap_or(pkg) } pub fn classify(pkg: &str) -> Option { @@ -151,13 +151,13 @@ impl std::fmt::Display for Violation { write!(f, "{krate}: standalone crate must not depend on workspace crate {dep}") } Violation::TestkitAsNormalDep { krate } => { - write!(f, "{krate}: modelcraft-testkit may only be a dev-dependency") + write!(f, "{krate}: fieldcraft-testkit may only be a dev-dependency") } Violation::UiBelowUiLayer { krate, dep, layer } => { write!(f, "{krate} (L{layer}) depends on UI crate `{dep}`; UI toolkits are only allowed in L{UI_MIN_LAYER}+") } Violation::SolidCraftOutsideGeometry { krate, dep } => { - write!(f, "{krate} depends on `{dep}`; only modelcraft-{SOLIDCRAFT_GATEWAY} may use SolidCraft") + write!(f, "{krate} depends on `{dep}`; only fieldcraft-{SOLIDCRAFT_GATEWAY} may use SolidCraft") } } } @@ -263,92 +263,92 @@ mod tests { #[test] fn clean_downward_graph_passes() { let g = [ - c("modelcraft-model", &[("serde", Normal, false)]), - c("modelcraft-doc", &[("modelcraft-model", Normal, true)]), - c("modelcraft-engine", &[("modelcraft-doc", Normal, true), ("modelcraft-testkit", Dev, true)]), - c("modelcraft-ui-egui", &[("modelcraft-engine", Normal, true), ("egui", Normal, false)]), - c("modelcraft-cli", &[("modelcraft-ui-egui", Normal, true)]), + c("fieldcraft-model", &[("serde", Normal, false)]), + c("fieldcraft-doc", &[("fieldcraft-model", Normal, true)]), + c("fieldcraft-engine", &[("fieldcraft-doc", Normal, true), ("fieldcraft-testkit", Dev, true)]), + c("fieldcraft-ui-egui", &[("fieldcraft-engine", Normal, true), ("egui", Normal, false)]), + c("fieldcraft-cli", &[("fieldcraft-ui-egui", Normal, true)]), ]; assert!(check(&g).is_empty(), "{:?}", check(&g)); } #[test] fn upward_dependency_flagged() { - let v = check(&[c("modelcraft-doc", &[("modelcraft-engine", Normal, true)])]); + let v = check(&[c("fieldcraft-doc", &[("fieldcraft-engine", Normal, true)])]); assert!(matches!(v[..], [Violation::Upward { from: 4, to: 6, .. }])); - let v = check(&[c("modelcraft-fem", &[("modelcraft-solver", Normal, true)])]); + let v = check(&[c("fieldcraft-fem", &[("fieldcraft-solver", Normal, true)])]); assert!(matches!(v[..], [Violation::Upward { from: 1, to: 2, .. }])); } #[test] fn sideways_dependency_flagged() { - let v = check(&[c("modelcraft-mesh", &[("modelcraft-fem", Normal, true)])]); + let v = check(&[c("fieldcraft-mesh", &[("fieldcraft-fem", Normal, true)])]); assert!(matches!(v[..], [Violation::Upward { from: 1, to: 1, .. }])); } #[test] fn self_dev_dependency_ignored() { - assert!(check(&[c("modelcraft-doc", &[("modelcraft-doc", Dev, true)])]).is_empty()); + assert!(check(&[c("fieldcraft-doc", &[("fieldcraft-doc", Dev, true)])]).is_empty()); } #[test] fn upward_dev_dependency_flagged() { - let v = check(&[c("modelcraft-model", &[("modelcraft-doc", Dev, true)])]); + let v = check(&[c("fieldcraft-model", &[("fieldcraft-doc", Dev, true)])]); assert!(matches!(v[..], [Violation::Upward { kind: Dev, .. }])); } #[test] fn solvers_sit_above_the_kernels() { let g = [ - c("modelcraft-fem", &[("modelcraft-model", Normal, true)]), - c("modelcraft-solver", &[("modelcraft-fem", Normal, true), ("modelcraft-mesh", Normal, true)]), - c("modelcraft-engine", &[("modelcraft-solver", Normal, true)]), + c("fieldcraft-fem", &[("fieldcraft-model", Normal, true)]), + c("fieldcraft-solver", &[("fieldcraft-fem", Normal, true), ("fieldcraft-mesh", Normal, true)]), + c("fieldcraft-engine", &[("fieldcraft-solver", Normal, true)]), ]; assert!(check(&g).is_empty(), "{:?}", check(&g)); - let v = check(&[c("modelcraft-solver", &[("modelcraft-engine", Normal, true)])]); + let v = check(&[c("fieldcraft-solver", &[("fieldcraft-engine", Normal, true)])]); assert!(matches!(v[..], [Violation::Upward { from: 2, to: 6, .. }])); - let v = check(&[c("modelcraft-engine", &[("modelcraft-cli", Normal, true)])]); + let v = check(&[c("fieldcraft-engine", &[("fieldcraft-cli", Normal, true)])]); assert!(matches!(v[..], [Violation::Upward { from: 6, to: APPS_LAYER, .. }])); } #[test] fn ui_crates_below_the_ui_layer_flagged() { for dep in ["egui", "eframe", "winit", "egui_kittest", "rfd", "bevy_ecs", "bevy"] { - let v = check(&[c("modelcraft-engine", &[(dep, Normal, false)])]); + let v = check(&[c("fieldcraft-engine", &[(dep, Normal, false)])]); assert!(matches!(v[..], [Violation::UiBelowUiLayer { layer: 6, .. }]), "{dep}"); - let v = check(&[c("modelcraft-solver", &[(dep, Normal, false)])]); + let v = check(&[c("fieldcraft-solver", &[(dep, Normal, false)])]); assert!(matches!(v[..], [Violation::UiBelowUiLayer { layer: 2, .. }]), "{dep}"); } - assert!(check(&[c("modelcraft-engine", &[("egui_extras_not", Normal, false)])]).is_empty()); - assert!(check(&[c("modelcraft-ui-egui", &[("winit", Normal, false)])]).is_empty()); + assert!(check(&[c("fieldcraft-engine", &[("egui_extras_not", Normal, false)])]).is_empty()); + assert!(check(&[c("fieldcraft-ui-egui", &[("winit", Normal, false)])]).is_empty()); } #[test] fn only_geometry_uses_solidcraft() { - assert!(check(&[c("modelcraft-geometry", &[("solidcraft-kernel", Normal, false)])]).is_empty()); - let v = check(&[c("modelcraft-mesh", &[("solidcraft-geom", Normal, false)])]); + assert!(check(&[c("fieldcraft-geometry", &[("solidcraft-kernel", Normal, false)])]).is_empty()); + let v = check(&[c("fieldcraft-mesh", &[("solidcraft-geom", Normal, false)])]); assert!(matches!(v[..], [Violation::SolidCraftOutsideGeometry { .. }])); } #[test] fn unregistered_crate_is_error() { - let v = check(&[c("modelcraft-mystery", &[])]); - assert!(matches!(&v[..], [Violation::Unregistered { krate }] if krate == "modelcraft-mystery")); + let v = check(&[c("fieldcraft-mystery", &[])]); + assert!(matches!(&v[..], [Violation::Unregistered { krate }] if krate == "fieldcraft-mystery")); assert!(v[0].to_string().contains("register")); } #[test] fn testkit_only_as_dev_dependency() { - let v = check(&[c("modelcraft-doc", &[("modelcraft-testkit", Normal, true)])]); + let v = check(&[c("fieldcraft-doc", &[("fieldcraft-testkit", Normal, true)])]); assert!(matches!(v[..], [Violation::TestkitAsNormalDep { .. }])); - assert!(check(&[c("modelcraft-doc", &[("modelcraft-testkit", Dev, true)])]).is_empty()); - assert!(check(&[c("modelcraft-testkit", &[("modelcraft-engine", Normal, true)])]).is_empty()); + assert!(check(&[c("fieldcraft-doc", &[("fieldcraft-testkit", Dev, true)])]).is_empty()); + assert!(check(&[c("fieldcraft-testkit", &[("fieldcraft-engine", Normal, true)])]).is_empty()); } #[test] fn apps_and_xtask_exempt() { - for app in ["modelcraft", "modelcraft-cli", "xtask"] { - assert!(check(&[c(app, &[("egui", Normal, false), ("modelcraft-ui-egui", Normal, true)])]).is_empty()); + for app in ["fieldcraft", "fieldcraft-cli", "xtask"] { + assert!(check(&[c(app, &[("egui", Normal, false), ("fieldcraft-ui-egui", Normal, true)])]).is_empty()); } } @@ -356,17 +356,17 @@ mod tests { fn metadata_parsing() { let meta: Value = serde_json::from_str( r#"{"packages":[ - {"name":"modelcraft-doc","dependencies":[ - {"name":"modelcraft-model","kind":null,"path":"/x/crates/model"}, + {"name":"fieldcraft-doc","dependencies":[ + {"name":"fieldcraft-model","kind":null,"path":"/x/crates/model"}, {"name":"serde","kind":null}, {"name":"proptest","kind":"dev"}]}, - {"name":"modelcraft-model","dependencies":[]} + {"name":"fieldcraft-model","dependencies":[]} ]}"#, ) .unwrap(); let g = from_metadata(&meta).unwrap(); assert_eq!(g.len(), 2); - let doc = g.iter().find(|c| c.name == "modelcraft-doc").unwrap(); + let doc = g.iter().find(|c| c.name == "fieldcraft-doc").unwrap(); assert!(doc.deps[0].workspace && !doc.deps[1].workspace); assert_eq!(doc.deps[2].kind, Dev); assert!(check(&g).is_empty()); diff --git a/xtask/src/licences.rs b/xtask/src/licences.rs index bfac246..d497615 100644 --- a/xtask/src/licences.rs +++ b/xtask/src/licences.rs @@ -1,5 +1,5 @@ //! `cargo xtask licences [--check]`: THIRD-PARTY-LICENSES.txt, the licences of every crate built -//! into the shipped programs (modelcraft, modelcraft-cli, modelcraft-web), read from +//! into the shipped programs (fieldcraft, fieldcraft-cli, fieldcraft-web), read from //! `cargo metadata` and the crates' own licence files, so a release download can carry them. //! `--check` (part of `cargo xtask ci`) fails when the committed file is out of date, e.g. after a //! dependency changed. @@ -12,7 +12,7 @@ use serde_json::Value; use crate::cargo; pub const FILE: &str = "THIRD-PARTY-LICENSES.txt"; -const ROOTS: &[&str] = &["modelcraft", "modelcraft-cli", "modelcraft-web"]; +const ROOTS: &[&str] = &["fieldcraft", "fieldcraft-cli", "fieldcraft-web"]; pub fn run(root: &Path, check: bool) -> Result<(), String> { let text = generate()?; @@ -118,9 +118,9 @@ fn licence_files(dir: &Path, p: &Value) -> Vec<(String, String)> { fn render(crates: &[Crate]) -> String { let rule = "=".repeat(80); let mut out = format!( - "Third-party software in ModelCraft\n\nModelCraft (the desktop app, modelcraft-cli and the web app) is built from the {} crates below.\n\ + "Third-party software in FieldCraft\n\nFieldCraft (the desktop app, fieldcraft-cli and the web app) is built from the {} crates below.\n\ Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.\n\ - ModelCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.\n\n", + FieldCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.\n\n", crates.len() ); for c in crates { @@ -144,7 +144,7 @@ fn render(crates: &[Crate]) -> String { } if !without.is_empty() { out += &format!( - "\n{rule}\nCrates whose package carries no licence file (their licence is named in Cargo.toml; the MIT and Apache-2.0\ntexts are as in ModelCraft's own LICENSE-MIT and LICENSE-APACHE)\n{rule}\n\n" + "\n{rule}\nCrates whose package carries no licence file (their licence is named in Cargo.toml; the MIT and Apache-2.0\ntexts are as in FieldCraft's own LICENSE-MIT and LICENSE-APACHE)\n{rule}\n\n" ); for c in without { out += &format!( diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c99f93b..834af96 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -75,13 +75,13 @@ fn main() -> ExitCode { Some("corpus") => { // A binary of the testkit, so xtask itself stays free of SolidCraft. let mut c = cargo(); - c.args(["run", "--release", "-p", "modelcraft-testkit", "--bin", "corpus", "--"]).args(&rest); + c.args(["run", "--release", "-p", "fieldcraft-testkit", "--bin", "corpus", "--"]).args(&rest); run(c, "corpus") } Some("oracle") => { // A binary of the testkit, which has the engine and SolidCraft scripts for geometry. let mut c = cargo(); - c.args(["run", "--release", "-p", "modelcraft-testkit", "--bin", "oracle", "--"]).args(&rest); + c.args(["run", "--release", "-p", "fieldcraft-testkit", "--bin", "oracle", "--"]).args(&rest); run(c, "oracle") } Some("wasm") => cmd_wasm(),