Commit Graph
87 Commits
Author SHA1 Message Date
jyvikoandClaude Opus 5.5 e8cada94d0 Engine: mesh.seed.edge, mesh.seed.clear and mesh.seed.list
Seed design edges by their persistent names with a count or a size, a bias
and a constraint; one undo step each, saved with the model. Seeds on edges a
reopened design lacks are reported as stale.

Part of #230.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:58:17 -04:00
jyvikoandClaude Opus 5.5 94c98e0c0b UI: pick a design's edges by their persistent names
The viewport keeps each drawn body's B-rep edges; with Select: Edges a click
picks the nearest visible one (before and after meshing), hover and the
selection draw it wide, and multi-select works as for faces.

Part of #230.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:46:05 -04:00
jyvikoandClaude Opus 5.5 ffb71de6e1 Engine: geometry.bodies lists each body's named edges
Each edge with its persistent name and length. A reference to an edge name
selects its numbered pieces (`~k`), as a face name selects its `#k` pieces.

Part of #230.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:46:05 -04:00
jyvikoandClaude Opus 5.5 a11b971dae Geometry: persistent edge names, after the faces each edge separates
A design edge is named by its faces' persistent names, sorted and joined by
`|`, numbered `~k` by position when several separate the same faces. The
names follow the faces through a design edit and a partition replay.

Part of #230.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:46:05 -04:00
jyvikoandClaude Opus 5.5 29e7a4ca31 Engine: a reference collection names the requirement (and its provider) that makes an item
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:34:24 -04:00
jyvikoandClaude Opus 5.5 860707efa6 Sample: crank-mirror, the crank arm meshed from a half and mirrored
file.sample crank-mirror partitions the crank arm on its symmetry plane, sweeps
one half all-hex (hex20) and mirrors it into one conforming mesh, then defines
the crank-arm model. Samples may now run preparing commands (datums,
partitions) before their script sees the bodies. Its test checks the halves
alike and joined, every element valid and the sets on both halves; the heavy
test solves it: sigma_xx at the root -62.43 / +62.43 MPa against M/Z = 62.5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:23:04 -04:00
jyvikoandClaude Opus 5.5 f89545a66a Engine: model.boundary_condition.symmetry on a datum plane
A symmetry condition (zero normal displacement, zero rotation about the
in-plane axes) on a datum plane normal to X, Y or Z, with its set given or made
of the open design's faces on the plane (a partition's cut faces), so half a
symmetric part is solved as a half. Half a 40 mm bar cut at its mid-width, with
the condition and half the load, bends as the whole bar to 2e-12 with half the
reaction. Inclined planes are refused as not supported yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:08:42 -04:00
jyvikoandClaude Opus 5.5 0f99a1c687 Engine: mesh.mirror reflects meshed bodies across a plane
The bodies' elements are reflected across a datum plane (or a point and
normal) with fieldcraft_mesh::mirror and joined on it. The image is tagged as
the design's body at the reflected place (found by volume and centroid, or
named in `as`), its faces with that body's faces under them, so sets by face
name reach both halves. Half a bar meshed hex8 or hex20 and mirrored has the
whole bar's node and element counts and volume, and its bending deflection to
1e-12; tet4 and tet10 mirror valid and conforming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 20:00:59 -04:00
jyvikoandClaude Opus 5.5 0b6b6c966c Engine: mesh.cells meshes a partitioned part cell by cell
Each cell (a body of the open design) gets its own method: structured hex where
one fits (auto falls back to tet and says why), or tet. All go into one mesh.
Cells on either side of a partition are joined: where their element faces
match node for node the nodes are merged; otherwise (hex next to tet, or
different divisions) the two partition faces get a set each and a dual mortar
tie, the reported hex-tet transition. A 40 mm bar cut in half, one half hex,
the other tet, stretches F L / (E A) to 1e-6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 19:50:37 -04:00
jyvikoandClaude Opus 5.5 f6d227d8cb UI: datum planes and axes in the viewport, datums and partitions in the browser
Datum planes draw as a hatched square around the model, axes as a line through
it, each with its name, in the desktop viewport and the headless render. The
browser's Geometry node lists the datums and the partitions with the datum
each cuts by.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 19:37:37 -04:00
jyvikoandClaude Opus 5.5 1c16972cf2 Engine: geometry.datum, geometry.datum.axis and geometry.partition
Datum planes by principal plane and offset, three points, or point and
normal; datum axes by principal axis, two points, or point and direction.
geometry.partition cuts the open design by a datum plane and keeps the
partition in the model, so geometry.open, file.open and undo cut the design
again. Requirements, forms and the Prepare entries are wired; features.md
lists them as ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 19:37:37 -04:00
jyvikoandClaude Opus 5.5 e0d5adba23 Engine: each command lists what it needs, and execute checks it first
CommandSpec gains `requires: &[Requirement]` (crates/engine/src/requirement.rs): an open
design, geometry, nodes, a mesh, something to analyse, materials and sections for the
elements, sets, the definition an edit or delete acts on, a step, a result. Session::execute
checks them before running, so a command missing one fails at once with no partial work,
naming each missing piece and the toolbar entry that provides it ("Needs a material —
Properties › Material"). Requirements are real prerequisites (the model's own reference
checks and model.check's rules), never a workflow order. engine.commands / list_commands
list them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 18:43:29 -04:00
jyvikoandClaude Opus 5.5 06b98f01c7 Undo: a new model is untitled, and undoing it brings the old file back (#214)
geometry.open with "model": "new" cleared the model but kept the session's
file path, so File > Save wrote the new model over the previous one. The path
and the revision saved there (which document the session is) were not part of
the undo state. A step now records the document when its command changed it:
the new model is untitled and unsaved; undo restores the old file and its
clean state, redo the untitled model (or the file it was saved to since).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 18:25:09 -04:00
jyvikoandClaude Opus 5.5 4a60674530 geometry.open: choose to keep the model or start a new one, and report what is stale
Opening a design over a model kept its mesh and every definition silently,
even when the design was another part (#145, #114). geometry.open now takes
`model`: "keep" (default: same-design reopen keeps the mesh and sets that
follow persistent face names) or "new" (clears mesh, definitions and
imported surfaces, keeps the unit system). The reply says whether the design
is `related` to the model's geometry references and lists `stale` mesh
bodies and sets. One undo step either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 18:07:30 -04:00
jyvikoandClaude Opus 5.5 48cae3eb1d engine: heated-bracket test allows the clamped bore's 2.1 % on Linux
The heavy test asked the upright's top to grow within 2 % of free thermal
expansion; the clamped bore holds it back by 2.1 % on the Linux CI's tet mesh,
which differs from macOS's (#115). 3 % still catches a wrong expansion
coefficient or temperature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 16:54:01 -04:00
jyvikoandClaude Opus 5.5 227c4544ed solver: mortar contact in 2D models
Plane strain, plane stress and axisymmetric models take contact pairs and
general (self) contact on the linear edges of tri3 and quad4 elements:
edges oriented out of their elements, the edge mortar of fem with the
thickness or 2 pi r in the weights, the element's area over the edge's
length as the thickness under it, and the in-plane rows of the tangent
blocks. Verified by the contact patch test in plane strain and
axisymmetric (to 1e-9 small sliding, 1e-4 finite), Hertz's line contact
on quad4 edges (Johnson 1985, 4.3; equal to the hex8 model's to 1e-5),
and a hybrid rubber C-ring closing on itself that moves node for node as
the one-layer solid ring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 16:30:44 -04:00
jyvikoandClaude Opus 5.5 6bd52c0b29 engine: crank-arm sample, a bicycle crank swept all-hex and solved
file.sample crank-arm: a 170 mm 6061-T6 crank built in SolidCraft
(crates/engine/samples/crank-arm.json), swept along z by mesh.hex.generate
as 7274 hex20 elements of 3 mm (scaled Jacobian min 0.17). The hex variant
drops the pocket floor fillet and boss chamfers, and its root arcs meet the
bosses 20 degrees off tangent so the arm's top face has no zero-angle cusp.
The square bore is fixed and 1500 N acts on the pedal hole; the faces are
picked by their surfaces, not their names. Its heavy test matches the root
bending stress to M/Z = 62.6 MPa within 3 % (0.3 % in practice).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 16:20:35 -04:00
jyvikoandClaude Opus 5.5 0b7f3021d9 engine: bracket-hex sample, the partitioned bracket meshed all-hex and solved
file.sample bracket-hex: the corpus bracket split into 30 SolidCraft cells,
meshed by mesh.hex.blocks as one conforming hex20 mesh of 3 mm, clamped at
the upright and loaded at the foot. The sample's script finds the clamp and
load faces in every cell that has them. Its test matches the tip deflection
of a fine tet10 solution within 1 % and checks the clamp carries the load.

Closes #166

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:55:22 -04:00
jyvikoandClaude Opus 5.5 f5a0a1289a engine: heated-bracket sample, steady heat and thermal stress in steel
file.sample heated-bracket: the corpus filleted bracket in steel, tet10, its
hole bores clamped, the upright's end held at 120 C and the rest cooled by
still air; one coupled temperature-displacement step. Its test checks the
temperatures against the heater and the air, then warms the bracket evenly,
clamps one hole, and matches the free thermal expansion alpha dT times the
distance from the hole along x.

Closes #165

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:55:22 -04:00
jyvikoandClaude Opus 5.5 ecdff11e72 engine: flow-past sample, air past an L-bracket on the floor
file.sample flow-past: air at 25 mm/s past the upright L-bracket in a box of
2.5 mm lattice cells (Re 83), inlet, outlet, a no-slip floor and slip walls;
the fluid step reports the force on each named face. Its test holds the
drag coefficient on the upright's frontal area between 1 and 3.5 (2.3) and
the lattice's mass change under 1 %. No picture: the viewport does not draw
flow results yet (M12).

Part of #164 (its streamlines wait for a flow display in the viewport)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:55:22 -04:00
jyvikoandClaude Opus 5.5 cf8ed71e4f engine: rubber-seal sample, a Mooney-Rivlin gasket ring squeezed 25 %
file.sample rubber-seal: a gasket ring of hybrid hex8 elements in four
mapped quarter blocks, squeezed 2.5 mm between plates bonded to it
(nonlinear static, Mooney-Rivlin rubber). Its test frees the plates and
matches the closed form for incompressible uniaxial compression within 3 %,
and bounds the bonded ring's extra stiffness. The plates are prescribed
displacements until implicit contact (#161) can press it into a groove.

Closes #163

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:55:22 -04:00
jyvikoandClaude Opus 5.5 581646bdac engine: drop-test sample, an aluminium bracket dropped onto a tilted floor
file.sample drop-test: an L-bracket standing on its foot (a SolidCraft script
in sample.rs) swept into hex8, falling 2 m onto a rigid floor tilted 15 deg;
explicit dynamics for 1 ms with gravity and plasticity. Its test checks the
mass against rho V, the impact speed sqrt(2 g h) and kinetic energy, the
energy balance and the momentum the floor takes. docs/examples.md starts
with it, and its picture is docs/media/examples/drop-test.png.

Closes #162

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:55:22 -04:00
jyvikoandClaude Opus 5.5 c9a765aa02 engine: samples can build their geometry from a SolidCraft script
A sample may carry a SolidCraft script (the corpus recipe form) whose design
opens before its commands run, and its script sees the design's bodies.
fieldcraft-engine now builds fieldcraft-geometry with its scripts feature.
The start screen scrolls its sample list, and a test keeps the file.sample
form's choices in step with the samples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:55:22 -04:00
jyvikoandClaude Opus 5.5 0220e2d8bb engine, docs: self-contact in nonlinear static steps
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:47:17 -04:00
jyvikoandClaude Opus 5.5 7e9366f726 engine: interaction.tie, tie.edit and tie.delete
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:28:02 -04:00
jyvikoandClaude Opus 5.5 32c5c1b307 engine: friction output of implicit contact
solve.nonlinear counts the slipping nodes, solve.probe gives a secondary node's shear traction
and slip, and the results display draws contact_shear and contact_slip, contact_status coding
stick and slip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 15:14:14 -04:00
jyvikoandClaude Opus 5.5 5770288f47 engine: contact output of nonlinear static steps
solve.nonlinear summarises each contact (normal force, largest pressure, closed nodes, deepest
penetration, augmentations), solve.probe gives a secondary node's pressure, gap and status per
contact, and the results display draws contact_pressure, contact_gap and contact_status. The
contact commands, the catalogue and the model docs say what nonlinear static steps do with a
pair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 14:58:32 -04:00
jyvikoandClaude Opus 5.5 bc75fd08d1 explicit: opt-in mass scaling to a target increment, the added mass reported
An explicit step may ask for mass scaling to a target time increment (on
a set, or the whole model): elements whose stable increment is below it
get the density that reaches it, with their lumped masses, frequency
bound and hourglass scales; adiabatic heating keeps the physical
density. The run reports the elements scaled, the added mass in all and
per section with its fraction, a warning, and the added mass's kinetic
energy in the energy account. Verified on a uniform bar scaled to twice
its increment (added mass 3×, increment doubled, a quarter of the
kinetic energy left to the physical mass). ROADMAP and parity note the
hyperelastic materials and mass scaling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 14:29:43 -04:00
jyvikoandClaude Opus 5.5 a953dc6216 solver, model: arc-length control with critical points from the inertia
NonlinearStatic.arc_length {constraint, max_load_factor, displacement_limit}: Riks–Crisfield
arc-length increments (cylindrical or spherical arc, tangent predictor signed by the angle
criterion, the constraint's quadratic root chosen by the angle with the last iterate), arcs
grown, shrunk and cut back as increments are. The converged state's tangent is factored for the
next predictor (LDLᵀ when indefinite); its negative eigenvalues are recorded per increment and
changes in their count are reported as limit or bifurcation points. Ends at the maximum load
factor, a displacement limit or the increment cap. Abaqus *STATIC, RIKS; LS-DYNA refuses it by
name; forms gain the arc-length group. A shallow arch of hex20 snaps through on the closed-form
path (1.9 %), both limit points found, inertia 0 → 1 → 0 between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 13:53:27 -04:00
jyvikoandClaude Opus 5.5 72696614c2 engine: parameter schemas for solve.buckle, buckling steps and imperfections
Fixes the build of develop: #171 crossed with the typed command forms, so BUCKLE lacked its
form. SOLVE_BUCKLE follows SOLVE_FREQUENCY; the step form gains the buckle variant and the
nonlinear step's imperfection group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 13:41:29 -04:00
jyvikoandClaude Opus 5.5 1b57e2e672 solver, model, engine: linear buckling steps
Procedure::Buckle {modes, shift}: the step's loads as a linear preload, K_σ from its stresses,
and (K + λ K_σ) φ = 0 by block Lanczos in the K inner product: K⁻¹(−K_σ) on Cholesky without a
shift, the buckling transformation (K + σ K_σ)⁻¹ K on LDLᵀ with one, whose inertia counts the
load factors below σ (Grimes–Lewis–Simon 1994). Lanczos gains an operand and an eigenvalue map
(defaults keep frequency steps bit for bit) and reports converged negative eigenvalues. Results
are frequency results with a `buckling` part; modes display with their load factor.
solve.buckle (in the Static drop-down); Abaqus *BUCKLE, Nastran SOL 105 and LS-DYNA refuse it by
name. Euler columns of hex20 within 0.18 % of Engesser's load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 13:30:59 -04:00
jyvikoandClaude Opus 5.5 188f2d5fc4 mcp, cli: list each command's parameters as JSON Schema
CommandSpec::listing gives id, label, the parameter doc and the schema; MCP
list_commands, the control channel's engine.commands and the new
fieldcraft-cli commands --json use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 13:20:04 -04:00
jyvikoandClaude Opus 5.5 8848ef9e7c engine: parameter schemas for every command
cmd/forms.rs describes each command's parameters: model definitions (sets,
materials with elasticity, plasticity, hyperelasticity, damage, thermal and fluid
options, sections, reference points, couplings, masses, springs, boundary
conditions, loads, fluid domains, steps including nonlinear and explicit),
meshers, solvers, deck export, files and results. Tests check that every command
has a form and that a form's defaults are accepted or refused only for the
required fields left empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 13:20:04 -04:00
jyvikoandClaude Opus 5.5 5c5516aae0 engine: a typed parameter schema per command (CommandSpec::form)
Field, Kind, Dim and Collection describe a command's JSON parameters: numbers with
unit dimensions, choices, vectors, references to model items, groups, one-of
variants and rows. prefill, switch_variant and problems serve forms; json_schema
gives the same as JSON Schema. No command is described yet (form: None).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:20:04 -04:00
jyvikoandClaude Opus 5.5 a30d0b5c5b explicit: deformable contact and friction, verified by bars, sliding blocks, the patch test and Hertz
Segment-to-segment penalty contact between element faces: each secondary
face and each main face facing it are projected onto the secondary face's
plane, split into triangles, clipped (Sutherland–Hodgman) and integrated
with a three-point rule per sub-triangle (Puso–Laursen segmentation, the
penalty at the integration points as Fischer–Wriggers), so a uniform
pressure gives the consistent nodal forces on both meshes and the contact
patch test passes on non-matching meshes. Every contact runs both ways at
half the penalty: pairs with each surface as the secondary, general and
self contact face against face. A bucket grid of face boxes every
increment, faces integrated in parallel and their forces added in face
order: the same numbers on any thread count.

The penalty per area ε = s γ min(m/A)/Δt² comes from the lumped masses,
each node's directional area and the stable increment, bounded by
Gershgorin's row sums so contact never shortens the increment. Coulomb
friction with exponential decay by a regularised return map at the
secondary nodes; rigid walls take Coulomb impulses. Contact work and
friction dissipation join the energy balance; each contact reports its
peak force, first and last contact, deepest penetration, friction and the
nodes adjusted at the start (initial penetrations adjusted strain-free or
refused with the node, face and depth); a cap on contact faces.

Verified (crates/explicit/tests/contact.rs, tests/oracle/explicit-*):
colliding bars (2L/c +1.2%, ρcAv₀/2, KE kept to 0.005%, contact energy
back to 8e-5 of it), blocks sliding to a stop on a rigid wall and on a
plate under constant and decaying friction (0.005–0.2%), the patch test
(σ_zz uniform to 0.06%), two spheres colliding against Hertz (peak +2.3%,
duration −0.4%; the oracle case waits for a graded sphere mesher), a free
body untouched by general contact, determinism across threads. Oracle
measures gain `over` (ratios). Existing fixtures do not move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 12:49:45 -04:00
jyvikoandClaude Opus 5.5 ecf947c090 model: contact pairs, general contact and Coulomb friction, mapped by the deck writers
Contact definitions on face sets: a pair (each surface's nodes against the
other's faces), general contact on a surface (self contact) or on every
exterior face of the solids; Coulomb friction with static and kinetic
coefficients and an exponential decay, a penalty scale and the choice to
adjust or refuse initially penetrating nodes, validated with caps. Rigid
walls of explicit steps gain friction. The check wants solid faces on
contact surfaces; sets used by a contact cannot be deleted.

fem::contact: the closest point of a triangle (Ericson) or a bilinear
quadrilateral (projected Newton) to a point, and a bucket grid of face
boxes for the global search, deterministic and bounded.

Commands interaction.contact.pair, .general, .edit, .delete and .find
(the touching parts of the mesh, with face sets and pairs made of them);
contacts in the model browser. Abaqus writes *SURFACE INTERACTION with
*FRICTION (exponential decay), *CONTACT PAIR and general *CONTACT; LS-DYNA
*CONTACT_AUTOMATIC_SURFACE_TO_SURFACE and _SINGLE_SURFACE with FS, FD, DC;
Nastran refuses contact by name. The implicit solver refuses contact by
name (M7); the explicit solver refuses it until the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 12:49:45 -04:00
jyvikoandClaude Opus 5.5 0638f91f00 engine: read the files commands name through fieldcraft_io::fs
file.open, geometry.open, geometry.import.mesh and reopening a model's
geometry read through the io crate's file edge instead of SolidCraft's
vfs, so in the browser they see the files the page holds (the same
store saves write to). Natively it is the same capped regular-file read.
fieldcraft-geometry drops its path-based readers (read_file,
open_design_file, read_3mf_file): it works on bytes, and reading files
is the io crate's job.

Part of #119

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 10:47:41 -04:00
jyvikoandClaude Opus 5.5 4445be0b48 mesh.quality: a one-line summary, shown in the status bar
Quality from the ribbon printed its whole JSON report into the status
bar, cut off after the first metric's histogram, so a user saw braces
and no answer. The report now carries a `summary` line ("30676 elements,
all valid; scaled Jacobian min 0.305, mean 0.963", with the invalid and
undetermined counts when there are any), and the status bar shows a
reply's summary instead of its JSON whenever it has one. The CLI and MCP
get the same line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 08:46:50 -04:00
jyvikoandClaude Opus 5.5 cf72503f76 mesh.tet.generate: mesh every body of the open design by default
The Tet Mesh form opened with {} and failed with "give either body (with
tolerance) or surface", so a first-time user could not mesh a part
without knowing its body name and a chordal tolerance. The tet mesher
now takes its bodies as the hex meshers do: every body of the open
design by default, one named body, or an imported surface, with the
tessellation tolerance defaulting to 1e-3 of each body's diagonal. With
several bodies an error names the body it is about. The reply keeps its
totals (kind, counts, first labels, volumes) and lists each body's
counts, size and quality report under `bodies`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 08:46:50 -04:00
jyvikoandClaude Opus 5.5 9469ac0853 Tet mesh quality: one validity criterion, Delaunay refinement, ODT, Klingner–Shewchuk, tet10
The end-to-end demo met a raw-CDT sliver that the validity gate accepted (exactly positive) and
the solver refused. Now:

- One validity criterion, `fieldcraft_model::admissible_jacobian` (normalized Jacobian above
  1e-12), used by the implicit solver (fem `positive_inverse`), the explicit tet4/hex8
  kinematics and the mesh gate, which proves it on the whole element (tet4: the solvers' own
  Jacobian; curved kinds: a Bernstein floor), so whatever passes the gate passes the solvers.
- CDT rounding repaired by flips on the rounded mesh (Diazzi et al. 2023 §5.1) instead of the
  ±2-ulp nudge.
- `fieldcraft-mesh::tet`: a mutable tetrahedral complex whose every change is a checked
  replacement (positive and admissible tetrahedra, faces matched with opposite orientation) with
  an exact undo log; edge removal (Klincsek), multi-face removal, Delaunay insertion with
  boundary faces, edge collapse; a curvature- and thickness-graded size field; constrained
  Delaunay refinement with encroachment splits; ODT smoothing; the Klingner–Shewchuk schedule;
  boundary vertices moved onto the exact B-rep surfaces at the end; tet10 with mid-edge nodes on
  the geometry.
- `mesh.tet.generate` gains size, order, quality, quality_target, curvature_angle, grading,
  max_passes, max_points and reports the CDT's and the mesh's quality; `solve.probe` takes a set
  and reports its largest von Mises stress.
- Oracle cases filleted-bracket-fillet-stress(-coarse): the fillet's peak von Mises stress on
  tet10 meshes of 2k and 13.5k elements, 213.3 MPa (a four-level study stays within 1.8%); the
  CDT cases keep the raw tetrahedralization.
- Corpus quality columns: all 20 valid parts mesh and pass the gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 08:28:49 -04:00
jyvikoandClaude Opus 5.5 7d2696057f engine: mesh.hex.octree command
Replaces the mesh with the octree all-hex mesh of a body, an imported surface or every body
of the open design, and tags boundary element faces with their B-rep faces so sets by
geometry resolve. Size, tolerance, refinement depth, element cap, attempts and optimiser
sweeps are validated and capped; the element cap defaults to the session limit
(`Limits::max_hex_elements`), which the hostile-params test keeps small. Body input and face
tagging are shared with mesh.hex.generate. The catalogue's Automatic Hex entry is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 07:38:11 -04:00
jyvikoandClaude Opus 5.5 53adab54c2 fem, model: Mooney-Rivlin and Yeoh hyperelasticity in nonlinear steps
Two more large-strain options on the isotropic constants, decoupled like
neo-Hookean with the volume kappa/2 (J - 1)^2: Mooney-Rivlin with C10 = mu/2 - C01
(0 <= C01 <= mu/2) and Yeoh with C10 = mu/2 and the higher terms C20, C30, so the
small-strain material is the elastic one everywhere. MaterialModel::Polynomial
gives the isochoric stress and tangent from Holzapfel's fictitious quantities
(eqs. 6.168, 6.193), consistent by central differences, equal to neo-Hookean
with C10 alone; hybrid elements take it. Abaqus writes *HYPERELASTIC,
MOONEY-RIVLIN and YEOH; LS-DYNA keeps refusing hyperelastic materials in
nonlinear steps by name. Verified in uniaxial and equibiaxial tension and pure
shear against the compressible closed form (exact).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 07:23:06 -04:00
jyvikoandClaude Opus 5.5 17b2c837f2 solver: J2 plasticity in nonlinear static steps, finite and small strain
Elements of a plastic material take the J2 material point: total Lagrangian
with nonlinear geometry, the small-strain loops without (elastic elements keep
their linear stiffness, so elastic results do not move). The plastic history is
committed per converged increment. Increments follow the step's load path,
ending at its points, the extrapolated predictor scaled by the change of the
load factor. Steps with path-dependent materials start an increment without a
predictor from the tangent predictor (the prescribed motion spread through the
converged tangent), and a first tangent that is not positive definite is a
cutback when the undeformed model's is. Refused by name: the Johnson-Cook rate
term (a static step has no time) and kinematic hardening at finite strain;
damage, erosion and adiabatic heating are left out with a warning. Results
carry the equivalent plastic strain at the integration points (solve.probe,
the step summary). LS-DYNA writes a load path as the step's curve; Abaqus
refuses it and a plastic deck of several general steps (Abaqus carries the
plastic state between them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 06:03:36 -04:00
jyvikoandClaude Opus 5.5 98d2f55407 render: the bodies of a design with several in their own colours
Geometry triangles remember their surface (body); the viewport and the
headless pictures draw each body of a multi-body design, such as the
cells of a partitioned part, in its own colour from the theme (eight in
turn), and a single body in the geometry colour as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 04:56:46 -04:00
jyvikoandClaude Opus 5.5 7f5399c7e9 mesh.hex.blocks: one conforming hex mesh of a design split into blocks
Meshes the open design's bodies (or the listed ones) as the cells of one
conforming all-hex mesh with one size and tessellation tolerance for all;
elements carry their cell's body, boundary faces its B-rep faces. A cell
that is no block, or cells that touch without sharing whole faces, is an
error naming the bodies. Catalogue entry with its VectorCraft icon, the
MCP mesh tool's hex_blocks method, docs/features.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 04:56:46 -04:00
jyvikoandClaude Opus 5.5 2183fdbe81 solver: nonlinear static analysis by Newton–Raphson with automatic increments
solve.nonlinear follows a nonlinear static step in load increments: dead
loads and prescribed displacements ramp with the load factor, a pressure
follows the deformed faces (its load stiffness symmetrised so the
supernodal Cholesky stays), the consistent tangent is refactored every
iteration with an optional line search, and an increment converges on
force, displacement and energy together. Increments start from the
extrapolated last one, grow after fast convergence and are cut back on
inversion, a non-finite or growing residual, an indefinite tangent or too
many iterations; the step stops cleanly at the minimum increment, the
increment cap or the Newton iteration limit. Elements are assembled in
parallel in label order (same bits on any thread count). Results carry
Cauchy stress, logarithmic strain, the increment history and a displacement
frame per increment, which results.frame and the results panel show.
Trusses, shells, beams, plane stress, incompatible modes, temperature and
centrifugal loads are refused by name; couplings warn that they stay
linear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 04:00:21 -04:00
jyvikoandClaude Opus 5.5 bc9fe2191a model: nonlinear static steps and neo-Hookean hyperelasticity, mapped by the deck writers
A nonlinear_static procedure (nonlinear geometry, initial/min/max increments,
increment and iteration caps, force/displacement/energy tolerances, line
search), validated with caps against hostile values. Materials gain an
explicit hyperelastic option (decoupled compressible neo-Hookean on the
isotropic constants). Abaqus writes *STEP, NLGEOM, INC / *STATIC with the
increments and *HYPERELASTIC, NEO HOOKE; LS-DYNA writes the ramped implicit
run with full Newton and *CONTROL_IMPLICIT_AUTO; Nastran refuses SOL 400 by
name, as LS-DYNA does hyperelastic materials in nonlinear steps. Explicit
dynamics refuses hyperelastic materials by name; solve.run names the solver
still to come.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 04:00:21 -04:00
jyvikoandClaude Opus 5.5 68a9553f53 engine: headless pictures draw the analysis setup's symbols
view::scene draws the boundary conditions, loads and interactions as the
viewport does (muted over a results plot) and frames them; Shown.sampled and
the MCP picture info's sampled_symbols list definitions drawn with a sample.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:34:33 -04:00
jyvikoandClaude Opus 5.5 34200013a4 The ribbon follows each command to its stage
A command run by a script, the control channel, MCP, the command search
or the browser left the ribbon on whatever tab it showed, so an agent's
session never showed the stage it was at. `catalogue::stage_tab` is the
rule, from the catalogue: a command's entry tab, none for the Tools tab
(files, history, the model summary). The app follows it after a command
that changed the model or the results display (queries leave the ribbon
alone); a script follows its last command with a stage. A ribbon click
runs a command of the tab shown, so it never moves the ribbon: a test
checks every entry and drop-down command against its tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:15:50 -04:00
jyvikoandClaude Opus 5.5 025c826dd4 results.deformed: scale 0 draws the undeformed shape
A factor of 0 was refused, so a picture sequence swinging the deformation
out from the undeformed shape had to start at a small factor or switch
`show` off. 0 is now a factor like any other (the view stays deformed), and
the results panel no longer works around the refusal. The MCP show_results
docs say so, beside `deformed: false`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:15:50 -04:00