Files
OneCAD-Simulation/docs/architecture.md
T
bf325bb234 Craft-family shell, native file dialogs, samples and one-click Solve (#46)
* Native file pickers, background opening, drag and drop and recent files

Open (models, SolidCraft designs and tagged meshes in one picker), Save, Save As, Write
Deck and Recover go through the host's file pickers (rfd in the desktop app, injected as
Services so the UI crate stays portable and tests answer them). Files open on a copy of the
session in a worker thread and replace it only on success; files dropped on the window or
named on the command line open the same way. A recent-files list, an unsaved-changes prompt,
Cmd/Ctrl+O/S/Shift+S. Programmatic calls never open a picker: ui.start opens the form.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Samples and one-click Solve: results reachable from the window

file.sample {name} opens a ready-to-run sample (the two parts, the cantilever with static and
frequency steps, the bar impact) in place of the model; solve.run solves every step in order.
Help > Samples, the empty viewport's start screen and the Tools > Samples entry open them
(asking first over unsaved changes); Solve runs in the background like a file opening and the
Results tab comes up. --sample [parts|cantilever|impact] on the command line.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* The Craft-family shell: title bar, workspace ribbon, browser, context panel, timeline

The window now follows SolidCraft's and VectorCraft's layout and themes:
- title bar with the mark, quick actions (File menu, save, undo, redo, Solve, search,
  theme, help), the model's document tab and the version; integrated under the macOS
  traffic lights, own caption buttons and resize zones elsewhere (titlebar.rs);
- workspaces (Prepare, Model, Analysis, Results) with their tabs, and panels of icon
  buttons whose labelled drop-downs list the whole catalogue, coming-soon entries greyed
  with their milestone; panels collapse, then go into a » menu, when narrow;
- the browser: geometry and mesh with eye toggles, definitions with counts, results;
- no permanent Commands list: command search (S, Cmd/Ctrl+K) and a command line;
- the context panel on the right only with context: the command form, results controls,
  the selected definition's properties with Edit and Delete;
- navigation bar and orientation gizmo in the viewport; the journal timeline at the bottom
  with undo positions;
- SolidCraft's light and dark tokens; craft-ui (IconButton, Tab, WidgetPalette).
Menus and the search are view state (ui.set {menu, palette}), so captures show them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Shell polish, docs and licences

Menus painted as the family's (flat File menu with recent files, decks and samples), the
legend sized to its text, two-line panel labels inside the ribbon, glyphs the UI font has,
offscreen captures without fade-in, ui.start runs in-window file actions (samples, recover,
solve) but never a native picker, ui.select. Control protocol, architecture, ROADMAP, AGENTS
and ATTRIBUTION updated; THIRD-PARTY-LICENSES.txt with craft-ui and rfd.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Title bar mark from the application icon; fluid steps and domains in the shell

The title bar draws the app icon's bracket (assets/app-icon/icon-small.svg's faces) as
SolidCraft draws its mark; the window keeps its title-bar flags after the rebase.
solve.run solves fluid steps too (solve.fluid); the browser lists fluid domains.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Simplify: cached per-frame data, one command per timeline roll, workspace setters

- Icon textures are kept per pixel size (the shell draws several sizes a frame; the cache
  used to clear on every size change and rasterise again).
- Opening a model or recovering one starts from an empty session instead of copying the
  current one; the replaced session is freed off the window thread.
- Element counts, timeline items, search results and the selected definition's properties
  are cached until what they show changes; timeline tooltips are built on hover; long lists
  in properties are summarised.
- edit.undo and edit.redo take a count: a timeline click is one command.
- solve.run dispatches on the step's procedure (no string round trip) and reports its name.
- The workspace and tab change together through UiState::select_tab/select_workspace
  (ui.set {workspace} alone now sticks); no per-frame syncing.
- One busy guard, Kind::verbs, a fit flag for background jobs, ModelApp::home_view, the title
  bar's actions as an enum, dead code removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Title bar: Solve and tools at the right end; no traffic-light inset in full screen

The owner's feedback on the title bar: Solve, search, theme and help sit at its right end
before the version; on macOS the left group (mark, name, quick actions) moves into the
traffic lights' room in full screen, where there are none, and back when leaving it. The
layout is a pure function (bar_layout) tested for both states, narrow windows and custom
caption buttons. ui.fullscreen {on} for agents.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Caps labels in Inter SemiBold

egui has no bold weight of its default font, so the ribbon's tabs and panel labels, the
workspace switcher and its menu, menu headings, the browser header, the context panel's
headers and the Solve label were thin, especially in the light theme. They now use a label
font family: Inter SemiBold 4.1 (SIL OFL 1.1, unmodified, fonts/OFL.txt, ATTRIBUTION.md),
with egui's fonts as fallback, installed with the theme on the live and offscreen contexts.
theme::label_font falls back to the default font until the family is bound (fonts take
effect from the next pass), so nothing can panic. Panels widen to their label's longest word.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: regenerate features.md after rebase

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 22:11:15 -04:00

337 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture
ModelCraft turns geometry into finite element models, writes them for the solvers engineers
already use, and solves linear problems itself.
```
SolidCraft B-rep ──tessellate──▶ boundary surface ──▶ volume mesh ──▶ FE model ──▶ Abaqus .inp
(solidcraft-kernel) (welded, watertight, tet (Delaunay) materials, Nastran .bdf
face-tagged) hex (core goal) sections, LS-DYNA .k
BCs, loads, ──▶ own linear
steps solver
```
## Crates and layers
`cargo xtask layers` enforces the table in `xtask/src/layers.rs`: a crate may depend only on
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 |
| 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. Bytes in, bytes out; the file system only in `fs` and `recovery`. Result files later. | started |
| L6 | `modelcraft-engine` | Commands (`CommandSpec`), `Session::execute` with rollback and the panic guard, scripts. | 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. | ui 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 |
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`.
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
scripts (`script_design`, through `solidcraft-engine`) for the test corpus; the library opens
designs without it.
## Meshing pipeline
1. **Boundary surface.** SolidCraft tessellates each body; every triangle carries its B-rep face,
so feature edges and face-based loads and BCs survive meshing. Faces of an opened design also
carry SolidCraft's persistent names (`F2:side:l4`, from the feature and sketch curve that made
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
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
position.
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
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.
4. **Hexahedral meshing (the core).** Octree/grid dual all-hex first, the one family that meshes
any input validly (Livesu et al. 2021 dual schemes, Pitzalis et al. 2021 refinement, Gao et al.
2019 feature projection and padding, Garanzha et al. 2021 untangling). Then hex-dominant by tet
recombination (Pellerin et al. 2018), then polycube (DualCube labelling) for blocky parts;
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
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. Without geometry, `mesh.block`
(`modelcraft-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
faces, edges and vertices become geometry tags that sets select. It meshes the analytic
shapes of the verification cases.
6. **Quality.** Scaled Jacobian, aspect ratio, dihedral angles; every generated mesh is checked
for positive Jacobians and conserved volume before it reaches the model.
## Model definition
`modelcraft-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
name of a face, edge or vertex (`{"body": "Body1", "face": "F2:top"}`). A mesher tags what it
makes: each element with the body (solids), face (shells) or edge (beams) it fills, each
boundary element face with its geometry face, element edges with geometry edges, nodes with
vertices (`Model::geometry_tag`, `tag_element`, `tag_element_face`, …; `mesh.tag` by hand).
Replacing or removing an element drops its tags; a remesh clears them and writes new ones.
- **Sets** are defined by geometry names, explicit labels or both, and resolve against the
current mesh into sorted nodes, elements and element faces. A face name also selects the pieces
a kernel splits it into (`name#0`, `name#1`). Because the definition names geometry, a remesh,
or an edit of the design followed by a remesh, keeps every set, section and load in place.
- **Element faces** are numbered per kind (`ElementKind::faces`): solids' faces are oriented with
the right-hand normal pointing out; shells have a positive and a negative side.
- **Definitions** (materials, sections with their element formulation, reference points,
couplings, boundary conditions, loads, steps) refer to each other by name. A reference must
exist when it is made, and a definition still referred to (a load listed by a step included)
cannot be deleted; what depends on
the mesh is checked when the model is resolved.
- **Steps** list the boundary conditions and loads active in them. Nothing propagates from one
step to the next.
- **Units** are a label (`mm-t-s-N-MPa`, …); numbers are never converted, and the check warns
when a modulus, density or gravity is implausible for the chosen system.
- **Check and resolve.** `Model::check` lists errors and warnings, each naming its item (a set
member not on the mesh, an element with no section or in two, a moment on solid nodes, a
missing density for a frequency step, …). `Model::resolve` returns what writers and the solver
read besides the definitions: every set resolved (each element then lies in exactly one
section's set), the node label of every reference point, and every force, moment and traction
as nodal values. It fails when the check finds an error.
- **Loads that keep their meaning on any mesh.** A force or moment on a set is a total by
default: split by the consistent nodal weights `∫ N_i dA` of the set's faces (Gauss quadrature
on each face's own linear or quadratic geometry), or equally over a set of nodes only. Applying
it at every node is an explicit, mesh-dependent opt-in (`per_node`). A traction is force per
area on faces. The expansion lives in `resolve`, so the deck writers and the solver share it.
- **Reference point labels** are stored with the point: chosen from the top of the label range
when it is created, so remeshing or adding nodes and points never renumbers them.
- **Files.** A model deserializes by replaying its contents through the same checked methods the
commands use, so a file cannot hold what commands would refuse.
## Implicit solver
`modelcraft-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
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,
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
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
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)`
(`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
prescribe the kinds a node carries (rotations of solid-only nodes are ignored) and a coupled
point's six directions; its loads become a load vector: forces, moments and tractions from
`Resolved::nodal_loads`, forces and moments on coupled reference points, pressure as
consistent face loads (shell faces too), gravity and body forces through the element shape
functions (consistent forces and moments on shells and beams).
2. **Assembly** on the node graph (one dense block per node pair: 3 × 3 for solids, 2 × 2 in 2D
models, up to 6 × 6 at shell and beam nodes; elements in label order, so sums are
reproducible bit for bit).
3. **Ordering and elimination** (`constraints`). Nested dissection of the nodes by coordinate
bisection. Couplings become linear constraint rows: kinematic = rigid link of each coupled
translation to the point's translation and rotation; distributing = the point follows the
least-squares rigid fit of the set's translations (equal weights, the deck writers' choice).
Prescribed values and rows are eliminated master–slave in a fixed order, one dependent degree
of freedom per row (the coupled node or the distributing point while it is free, else the
largest coefficient), giving u = T_f q + T_p u_p; the reduced matrix T_fᵀ K T_f stays
symmetric positive definite (no penalty, no multipliers). Free mesh equations follow the
node order, reference point equations come last. Without couplings T is a selection and the
numbers are those of plain elimination, bit for bit.
4. **Static solution.** Up-looking sparse Cholesky (the factor size is computed and capped before
any value), reactions T_pᵀ (K u − f) (at nodes and at prescribed reference points, forces and
moments), integration-point stresses extrapolated to the nodes and averaged, von Mises. Shells
report section forces and bottom, middle and top stresses at their nodes (their top surface
joins the nodal average), beams their end section forces; nodes report rotations and reaction
moments. A zero pivot names the node or reference point and direction that can move freely.
5. **Frequency.** Shift-invert block Lanczos (block 6, full reorthogonalization) on
K − σM, σ = 0 for a supported structure and slightly negative for a free one, so rigid-body
modes come out at zero; mass-normalized shapes, participation factors and effective masses.
6. **Results** stay in the session per step, stamped with the session revision they were solved
at; `Session::result` hands out only those of the current revision, so an edit makes them
stale and undoing back to the solved state makes them current again.
Verification (`crates/solver/tests/verification.rs`, analytic only): displacement and traction
patch tests for every element and formulation (exact to round-off), cantilever tip deflection
against Timoshenko with convergence rates of the linear elements, Lamé's cylinder, Kirsch's
plate with a hole (Kt → 3), cantilever and free–free beam frequencies with six rigid-body modes.
`crates/solver/tests/plane.rs`: the 2D elements' displacement patch tests in plane stress, plane
strain and axisymmetry, uniform traction, a plane stress cantilever against Timoshenko with
convergence rates, Kirsch's plate, Lamé's cylinder in plane strain and axisymmetric, a rotating
disc (plane stress) and a rotating cylinder (axisymmetric) against Timoshenko & Goodier, masses
and rigid-body modes.
`crates/solver/tests/couplings.rs`: a block given a rigid motion through a coupled point
(kinematic and distributing), clamping through a coupled point equals clamping (static and
modes), tension through a distributing coupling against the equivalent pressure away from the
end, pure bending by a moment through a distributing coupling, torsion of a tube by a moment on
a kinematically coupled point (twist and shear stress). `crates/solver/tests/one_d.rs`: member
forces of determinate trusses (a plane Pratt truss, a space tripod) against the method of joints,
the three-bar truss, a rod3 constant-stress patch and a bar hanging under its weight, spring–mass,
skew springs, the two-mass chain, a bar with a tip mass and the torsional pendulum.
`crates/solver/tests/structural.rs`: membrane and bending patch tests of quad4 and tri3 shells
in a tilted plane, the simply supported plate under pressure against Navier's series (deflection
and first frequency), cantilever beams against Timoshenko (exact) and Euler–Bernoulli
frequencies, a portal frame against slope-deflection, solids continued by rods and skinned by
shells. `crates/solver/tests/benchmarks.rs` (published reference values, tagged with their
source, separate from the analytic gate): the Scordelis–Lo roof and the pinched cylinder.
## Explicit solver
`modelcraft-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).
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
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,
rotational springs and moments return "not supported yet". Masses are row-sum lumped. A zero-valued boundary condition holds a degree of freedom; a
displacement value is reached at constant velocity over the step. Loads act in full from the
start. The step lists initial velocities and fixed frictionless rigid planes by set.
2. **Time step.** Each element's highest frequency is bounded in closed form (exact for a cube);
the increment is the step's scale (0.9 by default) times the smallest element increment with
Wilkins' bulk-viscosity correction, recomputed every increment. The ten controlling elements
are reported; a step that would need more increments than its cap is refused before it runs.
3. **Integration.** Central differences in the half-step form, elements in label order, no
parallel reductions (bit-for-bit repeatable). Rigid walls and prescribed degrees of freedom are
kinematic velocity constraints whose impulses are the reactions.
4. **Energy account.** Kinetic, internal (stress work), hourglass, bulk viscosity, external and
constraint work, and their balance; a run warns when the balance is off by more than 1% of the
largest energy or the hourglass energy exceeds 10% of the internal energy.
5. **Results.** Histories (energies, momentum, constraint force, chosen nodes) at the history
interval and field frames (displacement, velocity, element stress and strain) at the output
interval, capped in size; kept in the session like the implicit results.
6. **Large deformation and materials (X2).** With `nonlinear_geometry` (the default) the
elements are updated Lagrangian: strain and spin increments on the midpoint configuration,
forces on the current one (hex8 gradients in closed form, `UniformStrain::hex8_deformed`). The
material state (stress, back stress, strain, stiffness hourglass forces) is rotated with the
Jaumann rate (Hughes–Winget) or the Green–Naghdi rate (Flanagan–Taylor), `fem::rotation`;
materials are hypoelastic or J2 plastic (bilinear isotropic/kinematic, hardening table,
Johnson–Cook with adiabatic heating) by radial return with Johnson–Cook damage,
`fem::plasticity`. Elements whose damage reaches 1 or whose plastic strain reaches the
material's erosion strain are eroded (reported with their energy; their nodes keep mass and
momentum). The energy account adds the plastic work and the eroded energy.
Verification (`crates/explicit/tests/verification.rs` and `large_deformation.rs`, analytic
only): stress waves in hex8 and tet4 bars (front at c·t, σ = ρ c v, work input), a bar striking a
rigid wall (contact time 2L/c, rebound speed, plateau force ρ c v₀ A), the energy balance,
bit-for-bit repeatability; Dienes' simple shear for both rates, a stressed element turned rigidly
by 90°, the J2 uniaxial curve and unloading at large strain, damage failure at the fracture
strain, and the Taylor bar against Taylor's 1948 estimate; `crates/explicit/tests/one_d.rs`:
spring–mass period, damped decay rate and period, a dashpot alone, two masses on a line spring,
waves in rod2 and rod3 bars; in the engine (`solve.rs`), a
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
pressure at points and wall force histories). Papers are cited in the module docs and in
`docs/solvers/literature.md` (Part I).
1. **Model.** A fluid material (density, Newtonian viscosity), a fluid domain (box, cubic cell
spacing, a condition on each box face: wall, moving wall, slip, velocity inlet with a uniform
or parabolic profile, pressure outlet, periodic pair; obstacle bodies and moving-wall
conditions on their named faces; initial velocity; body acceleration) and a fluid step (end
time or steps, optional time step, frames, the TRT parameter Λ). Deck writers refuse fluid
steps.
2. **Setup.** Lattice units from the spacing, the time step and the density. Without a given
time step the solver keeps the characteristic lattice velocity at 0.05 and τ ≤ 1. A lattice
Mach number above 0.3, τ below 0.505 or a lattice acceleration above 0.01 is refused with the
numbers and what to change; caps on cells, steps, cell updates and frame memory are checked
before anything is allocated.
3. **Grid.** The engine triangulates each obstacle body (or takes an imported surface) and
prepares its `BoundarySurface`; cells whose centres have a positive winding number are solid,
decided with exact `orient2d`/`orient3d` and one symbolic perturbation, so faces through cell
centres and lines through edges are classified consistently.
4. **Stepping.** TRT collision with Guo forcing in place; pull streaming as one shifted copy per
direction with per-link rules for the cells next to walls and box faces; Zou–He on inlet and
outlet layers. Wall links give the momentum exchanged with every box face and B-rep face
(the face of the triangle nearest the link). A non-positive density or a lattice speed above
0.5 stops the run with an error.
5. **Results.** Frames of velocity and gauge pressure per cell (single precision), force
histories per wall and the fluid mass, in model units, stored per step like the implicit
results.
Verification (`crates/lbm/tests/verification.rs`, analytic): plane Poiseuille exact to round-off
with Λ = 3/16 and second order with Λ = 1/4; Couette exact with the wall shear μU/H; Taylor–Green
decay second order; Poiseuille in a staircase pipe; Stokes drag of a simple cubic sphere array
against Hasimoto (1959); a parabolic-inlet, pressure-outlet channel against Poiseuille; mass
conserved to round-off; bit-for-bit reproducibility. `crates/lbm/tests/benchmark.rs` (ignored,
published reference values): Schäfer–Turek 2D-1.
## Commands
Every user-visible action is a `CommandSpec` in `crates/engine/src/cmd/<area>.rs` with a JSON
form. `Session::execute` clones the model, runs the command under `catch_unwind`, and restores
the model on an error or a panic; commands that changed the model are journalled, so the
journal replays as a script. Each journalled command is also an undo step (the state before it):
`edit.undo` restores that state and moves the command off the journal onto the redo stack,
`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`.
## Test corpus
Meshers are measured on our own parts (`docs/adr/0001-libraries.md`, test geometry). Each part is
a SolidCraft command script in `tests/corpus/<name>/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,
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
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()`.
## Verification oracle
`tests/oracle/<case>/` holds the verification and validation cases (docs/verification.md): our
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
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.