Compare commits

..
86 Commits
Author SHA1 Message Date
jyvikoandClaude Opus 5.5 d2fe8a8303 README: what FieldCraft does today, and the current status
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:51:10 -04:00
jyvikoandClaude Opus 5.5 65bd16a342 demo video: regenerate with captions, the swing, the orbit and the setup symbols
The viewport now draws the clamp, the reference point, its coupling and
the tip load, so the setup stages show them. 33 s; MP4 0.5 MB, GIF 2.3 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:40:45 -04:00
jyvikoandClaude Opus 5.5 a423a9ef49 demo video: name FieldCraft in the tool, its README and the README section
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:40:45 -04:00
jyvikoandClaude Opus 5.5 8a8ed75103 demo video: swing the deformation from 0, close on a slow orbit
The results start on the undeformed shape (scale 0) and the deformation
swings, eased, from 0 to x150, back and out again, one picture per frame.
The video ends on a 60-frame camera orbit round the deformed beam
instead of cuts between views. The results camera is drawn back so the
contour legend leaves the beam clear, and each stage holds 2-3 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:40:45 -04:00
jyvikoandClaude Opus 5.5 30812250d3 demo video: caption each stage with the MCP tool it calls
A band under the window names the tool behind each stage (open, mesh,
batch, check_model, solve, show_results). caption.py draws the bars in the
standard library from the interface's Inter SemiBold (OFL): a minimal
TrueType glyf reader, a non-zero scanline rasteriser and a zlib PNG
writer, unit-tested with python3 -m unittest. ffmpeg pads each picture
and composites the bar with its overlay filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:40:45 -04:00
jyvikoandClaude Opus 5.5 5665cc3d8c ui: the viewport draws the analysis setup's symbols, with toggles
The setup's symbols (fieldcraft_render::glyphs) are built with the display
data and drawn over the model in every style, at the drawn nodes (a results
plot's deformed shape), muted over results; the clipping radius holds them.
Theme tokens give their colours in light and dark. The browser's Loads & BCs
and Interactions rows get eye toggles (ui.set show_loads, show_interactions),
also in the view menu. The GPU viewport draws reference point names with the
interface's font; the bottom left says which definitions are sampled.

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 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 93f1a03137 render: symbols for boundary conditions, loads and interactions, and scene labels
glyphs::Glyphs picks where a model's setup symbols go (resolving its sets
once, thinning them on a grid, capping each definition and sampling large
ones with a note) and draws them into a Scene at the nodes' current
positions: cones for constrained translations (and rotations where nodes
rotate), arrows for forces, double-headed arrows for moments, arrows normal
to pressure faces, one arrow for gravity and body forces, a curved arrow for
centrifugal loads, crosses with names for reference points, lines for
couplings, hatched squares for rigid walls. Colours come from the caller by
role, muted over results.

Scene gains labels, drawn by the CPU rasterizer with a small bitmap font of
its own.

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 95c02bc728 model: resolve_set_partial, and the rotating-node rule as Model::rotational_nodes
resolve_set_partial resolves the members of a set that are on the mesh, for
display: a set with a missing member still shows what it has, where
resolve_set refuses it. rotational_nodes is the check's rule for which nodes
have rotations (shell and beam nodes, not those of 2D sections), so the
viewport's symbols and the check agree.

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 73f35bd7f7 roadmap: finite-difference ultrasound (FDTD, EFIT, k-space) and time–frequency diagrams in M23
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:29:21 -04:00
jyvikoandClaude Opus 5.5 bd623feb92 roadmap: wave propagation and ultrasound (M23): spectral elements, absorbing boundaries, Lamb, Rayleigh and Love guided waves with SAFE dispersion, ultrasound and nonlinear acoustics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:26:41 -04:00
jyvikoandClaude Opus 5.5 cfe9527844 roadmap: gap review; nonlinear core (M15) before contact; dynamics, constitutive and material library, composites, coupled fields, failure and adhesion, biomechanics, design studies (M16–M22)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 00:25:52 -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 357de3f7a5 demo video: a horizontal cantilever, checked against beam theory
The demo used the corpus I-beam, which is extruded along z and so stood
upright. A new corpus part, cantilever-i-beam, is the same section on the
YZ plane extruded along x, web upright: a sweep along a horizontal axis
for the meshers, and a cantilever that bends downward on screen. The
i-beam part is unchanged (its corpus row and the meshing tests keep it).

The demo clamps it at x = 0 and loads the free end with 1000 N in -z
through a kinematic coupling, and now checks the solution against beam
theory and stops when it is off: tip deflection (bending plus shear)
within 3 %, root bending stress (stress_xx along the top flange,
extrapolated to the root, nodes from the Abaqus deck) within 2 %.

docs/meshing/corpus.md regenerated with the new part.

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
jyvikoandClaude Opus 5.5 89651b707c Camera orientation in degrees: ui.camera and the MCP screenshot's camera
`ui.view` and the MCP `screenshot` tool only turned to the named views, so
a picture sequence could only cut between them. `Orientation` (render) is a
camera orientation in degrees, {yaw, pitch, distance?}, checked as hostile
input: angles finite, distance finite and above 0, yaw wrapped, pitch
clamped to ±90, distance to 0.001–1e9. `modelcraft_engine::view::orientation`
parses it from JSON for both callers:

- control channel `ui.camera {yaw, pitch, distance?}` turns the app's camera;
- MCP `screenshot {camera}` turns it first (bridged through `ui.camera`,
  headless in the CPU render, framed unless a distance is given). `view` and
  `camera` are not given together.

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 d84dae074d roadmap: performance row, desktop and agents status, what lands next
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 23:51:40 -04:00
jyvikoandClaude Opus 5.5 d5bd74570f Rename the project to FieldCraft
Crates fieldcraft-*, binaries fieldcraft and fieldcraft-cli, the .fieldcraft file (format tag
fieldcraft/1, manifest fieldcraft.json), FIELDCRAFT_* environment variables, the MCP server
name, egui widget ids (fc_*), the app icon document FieldCraft.icon and its generated family,
docs and licences. No compatibility shim: no files of the earlier name exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 23:51:40 -04:00
jyvikoandClaude Opus 5.5 9bc4e1b182 README: the I-beam analysis demo, driven by an agent over MCP
The GIF plays inline and links the MP4; both are generated by tools/demo-video and attributed.
The asset check now also covers .mp4 files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 23:32:51 -04:00
jyvikoandClaude Opus 5.5 e2266c692b tools/demo-video: regenerate the README demo from an MCP session with the app
An I-beam cantilever from the corpus is opened, hex meshed, defined, solved and shown with von
Mises contours and a swinging deformed shape, every step an MCP tool call bridged to the
desktop app and every picture the MCP screenshot of the window; ffmpeg encodes the MP4 and GIF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 23:32:51 -04:00
jyvikoandClaude Opus 5.5 2edb4b2601 testkit: corpus --part NAME --design PATH writes a part's .solidcraft design
Opens a corpus part in the app or over MCP without solidcraft-cli.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 23:32:51 -04:00
jyvikoandClaude Opus 5.5 67e05548b1 oracle: an ill-conditioned quantity gets a wider fixture band; Scordelis-Lo re-blessed
Main's oracle job failed at 8cba3a7: scordelis-lo-roof-solid u_z moved from
-0.3014029226397275 (blessed before #50) beyond its 1e-9 band. The case is a
thin shell (R/t = 100) of one layer of hex20R solids, ill-conditioned, so
rounding alone moves u_z by about 1e-9 relative: on macOS #50's multithreaded
supernodal factorisation moved it by 8.2e-10 (to -0.3014029223929), #52's
interpolation order by a further 1.3e-10 (to -0.3014029223541), and Linux
CI by slightly more, past the band. #52 kept the lattice geometry the same.
The result itself is unchanged at the 1e-9 level (0.33% from the
reference).

A quantity may now set `fixture_relative` (with a required
`fixture_reason`) for a wider fixture band; Scordelis-Lo uses 1e-7 and is
re-blessed. Moved-fixture messages print full precision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 23:05:56 -04:00
jyvikoandClaude Opus 5.5 288b222286 AGENTS.md: rebase merges, every commit bisectable, fixups folded with --autosquash
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 22:54:11 -04:00
8cba3a7cac ci: run once per merge on main; prose-only merges skip; a newer merge cancels the older run (#54)
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:51:59 -04:00
85ba526663 mesh: one transfinite interpolation for mapped hex and mesh.block; stable oracle report (#52)
* mesh: one transfinite interpolation for mapped hex and mesh.block; stable oracle report

mesh.block's lattice is now built from the structured hex mesher's
transfinite code: faces by quad::transfinite_grid, the interior by the new
quad::transfinite_volume (Gordon-Thiel, blended by the block's graded
parameters), which hex::mapped now calls too. 2D patches are the
transfinite_grid of their sides. Edge curves return their corners exactly.

Oracle: a zero-reference quantity (rounding noise) keeps its absolute
tolerance as its fixture band, and docs/oracle.md shows such noise and
relative errors inside the fixture band as bounds, so the report no longer
changes with the order of floating-point operations (it failed the
'docs/oracle.md is current' check on main after #50). Scordelis-Lo moved by
1.4e-9 relative (an ill-conditioned thin shell of solids: sub-ulp node
changes) and is re-blessed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* simplify: shared face filling and lattice index; faces blended like the volume; exact ends for every edge

quad::transfinite_faces fills a block's six faces (with a per-face hook
that hex::mapped uses to project onto its B-rep faces) and
quad::lattice_index fixes the lattice layout for all three users.
transfinite_grid_blended blends faces by the block's graded parameters, so
faces and interior use one parametrisation and mesh.block reproduces its
earlier Gordon-Hall lattice (the Scordelis-Lo fixture is back to its
blessed value). Straight edges also return their corners exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:50:20 -04:00
cd5e1e9df9 AGENTS.md: one workflow for every change (touched-crate tests and clippy, self-merge, CI once per merge on main) (#53)
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:42:47 -04:00
7a0211988a fix: clippy as_chunks in the Cholesky kernels and regenerated oracle.md (#50) (#51)
* solver: as_chunks for the constant tile size (clippy)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: regenerate oracle.md after the supernodal Cholesky (round-off of zero-valued results)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:38:28 -04:00
4ce5513210 Deterministic CPU multithreading: supernodal Cholesky, parallel elements, LBM blocks (#50)
* parallel: deterministic thread pool helpers for the solvers (fixed partitions, ordered results)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver: supernodal left-looking Cholesky, multithreaded with a fixed operation order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver: parallel element matrices, stresses, Lanczos block solves and products (sums in the sequential order)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* explicit: element updates in parallel, nodal forces added in element order (bit-identical to the sequential loop)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* lbm: collision and streaming by fixed cell blocks in parallel, wall forces in link order (bit-identical)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: threads setting for the solve commands (session default from MODELCRAFT_THREADS or the cores); bit-identity test on 1, 2 and 7 threads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: rayon threads in ADR 0001, the parallel crate, supernodal Cholesky

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:26:44 -04:00
5afd1addb9 Structured hex: extrude, revolve, sweep and mapped all-hex meshing (M5) (#40)
* Structured hex meshing: multi-level extrude and revolve, one-to-one sweep, mapped blocks (M5)

modelcraft_mesh::hex::hex_mesh meshes a face-tagged boundary surface with
hexahedra by the first structured method that fits: a straight or
rotational multi-level sweep (imprinted cross-section arrangement, cuts at
reflex corners, mapped/annulus/free regions), a one-to-one sweep through
rings of linking faces (least-squares affine layers with residual
correction), or a mapped logical cube (trivariate transfinite
interpolation). Interval assignment couples shared curves; boundary quads
carry B-rep face tags; the certified validity gate, a tiling check and a
fidelity check guard the output; hex20 mid-edge nodes go onto the faces.
Shape::face_surfaces exposes the analytic face surfaces; the corpus runner
gets a structured hex column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh.hex.generate: structured hex meshing as a command

Meshes one body, every body of the open design or an imported surface,
replaces the mesh, tags element faces with the B-rep faces (so sets by
geometry resolve) and reports per body the method, counts and quality.
The quality pass certifies each element once and feeds the validity gate
(quality::validity_gate_checks). Shape::size gives the cached body size
for defaults instead of a full measure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Hex: disk O-grids, convex interval cost, early size guard, hex20 corpus column

Smooth single-loop sections (cylinders, tori, vases) get a butterfly O-grid
instead of the free mesher; interval assignment raises counts by the
squared relative error so equal sides share increments evenly; a mesh far
beyond the element limit is refused before any work and mapped limits are
no longer swallowed. The corpus reports hex8 and hex20 quality.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Catalogue: structured hex commands ready (mesh.hex.sweep, mesh.hex.mapped, mesh.hex.generate)

mesh.hex.sweep and mesh.hex.mapped run mesh.hex.generate with the sweeping
methods or the mapped one; mesh.hex.generate is the sweep entry's related
command. The planned M6 octree entry moves to mesh.hex.octree (its icon
renamed with it). docs/features.md regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Docs: structured hex literature (B8), architecture, roadmap, parity; corpus hex column

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Hex: simplify review fixes

Prepare each sweep frame's cross-section once (arrangement, cuts, fill)
and reuse it across refinement retries; candidate frames come from one
setup pass. Edge triangles come from the feature edges, layer counts go
through size::edge_divisions, sweep residual weights are computed once
per node, flux shape functions live on the stack, and the arrangement's
exact side test and distances serve the region probe. The engine takes
the boundary options mesh.tet.generate takes and names unnamed faces by
index like it; the corpus hex20 run reuses the winning method; the
mapped entry lists mesh.hex.generate too; the octree icon is redrawn
under its new id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Regenerate docs/features.md and docs/meshing/corpus.md after rebasing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Regenerate docs/features.md and docs/meshing/corpus.md after rebasing onto main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Regenerate docs/features.md and docs/meshing/corpus.md after rebasing onto main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Regenerate docs/features.md and docs/meshing/corpus.md after rebasing onto main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Regenerate docs/features.md after rebasing onto main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Regenerate docs/features.md after rebasing onto main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Regenerate docs/features.md after rebasing onto main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:25:50 -04:00
5ebb9f16dc Hybrid (mixed u–p) solids: hex8 and tet10 with a constant pressure, hex20 with a linear one (#49)
The isotropic elasticity splits into D_dev + K m mᵀ; an element-wise
discontinuous pressure is condensed out (p̂ = K M⁻¹ Gᵀ u), leaving
K_dev + K G M⁻¹ Gᵀ (Hughes §4.3–4.5). Q2–P1disc and P2–P0 are inf-sup
stable; Q1–P0 does not lock; tet4 and wedge6 still lock and are refused, as
are orthotropic materials (named at the section). ElementDofs counts the
condensed pressures. Verified: element patch, rigid modes and totals for
every hybrid kind, a block in uniaxial strain at ν = 0.4999 (exact), Lamé's
cylinder in plane strain at ν = 0.4999 (hex20 1e-5, tet10 1.3e-3, hex8
2.5e-3 vs the locking displacement hex8 at −80 %). Every non-hybrid result
of the test suites hashes identically before and after.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:20:21 -04:00
0899587127 MCP server: modelcraft-cli mcp (headless or bridged), read/write roots, end-to-end analysis over MCP (#48)
* io: read and write roots that confine an automation client's file access

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: every file a command reads or writes is checked against the session's roots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: headless pictures of a session (model, geometry, deformed shape and contours)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mcp: MCP server over stdio with headless and connected backends, roots and caps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* cli: modelcraft-cli mcp [--connect ADDR | --headless] with automation read and write roots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mcp: end-to-end analysis of a corpus part over MCP (open, tet mesh, define, solve, probe, render)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: MCP server (docs/mcp.md), Claude Code setup, roots; architecture, README, roadmap, attribution

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:18:08 -04:00
e900f41ce3 FEM foundation B: heat transfer and thermal stress (solids, 2D, explicit), coupled temperature–displacement, solve.heat (#45)
* Heat transfer and thermal stress: thermal materials, temperature BCs, heat loads, heat transfer and coupled temperature–displacement steps, solve.heat

Model: Material.thermal (conductivity, specific heat, expansion, isotropic or
per axis; reference temperature), BoundaryCondition::Temperature (dof 11),
loads SurfaceFlux, Convection, HeatSource and Temperature (a field in static
steps), procedures HeatTransfer { transient (θ-method) } and
CoupledTemperatureDisplacement; checks for missing conductivity/specific
heat/expansion. fem: heat conduction kernels (conductivity, capacity,
source, face flux/film, heat flux) on the DofLayout (one T per node), thermal
strain load and stress D(ε − ε_th) for every solid formulation incl.
incompatible modes. Solver: steady and transient heat transfer, thermal loads
in static steps, coupled steps (exact one-way coupling). Engine: solve.heat,
temperature/heat-flux result fields, probes. Decks refuse heat transfer by
name. Analytic verification: slab, source+convection/flux, thick cylinder,
erfc semi-infinite solid, free/constrained expansion, bimetal strip, coupled
step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* thermal: strains in 2D elements and explicit dynamics

Plane::thermal_load and stresses_with for plane stress (εzz free), plane
strain (σzz = −D₃·ε_th) and axisymmetric elements, incompatible modes
included; the solver's 2D kernels take them. Explicit dynamics turns a
step's temperature loads into a per-element thermal strain rate (reached
linearly over the step) subtracted from each strain increment, so it feeds
J2 and Johnson–Cook updates. Verified: free expansion in every 2D theory
and element, a biaxially held plate, an explicit free bar (ΔL = αΔT L,
stress-free) and a clamped bar at −EαΔT, capped at the yield stress with
perfect plasticity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* clippy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* thermal: trusses take the axial thermal strain; keep the roadmap's M7 line

Truss::thermal_load and stresses_with project the thermal strain on the
member's tangent (Σ αₖ tₖ² ΔT); the static solver applies them to rod2/rod3
(#38). Explicit trusses take none yet and a warning says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:15:03 -04:00
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: regenerate features.md after rebase

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:11:15 -04:00
29ae46ff84 Shells and beams: MITC4/MITC3 shells, Timoshenko beams, six-DOF nodes (M4) (#47)
* fem: MITC4/MITC3 flat shells and Timoshenko beams with profile properties (ElementDofs STRUCTURAL)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver: shells and beams on the DofLayout beside solids, 2D elements and trusses; analytic verification and tagged benchmarks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: probe rotations, reaction moments and shell and beam element results; docs for shells and beams

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:00:25 -04:00
jyvikoandClaude Opus 5.5 6de6de13aa roadmap: oracle case counts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 96c9992c92 oracle: 2D cases (Kirsch plane stress, Lamé plane strain and axisymmetric, rotating disc and cylinder, NAFEMS LE1 with quad8) and J2 uniaxial explicit
The pending 2D LE1 case becomes a real one; the plasticity cases stay
pending with what they still need (static J2 for the collapse pressure,
the transcribed measurements for the Taylor bar).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 84e81acf3a mesh.block: 2D patches (quad4, quad8, tri3, tri6) with arc and ellipse edges
A four-sided planar patch at z = 0, meshed by the Coons transfinite
interpolation of its edges for plane stress, plane strain and axisymmetric
sections: the elements fill a named face of the body, edges and corners
carry names, patches join edge to edge (the join check treats the edges of
2D elements as their sides).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 ca02c01e1f oracle: re-bless the explicit cases after #42
The large-deformation explicit core of #42 (objective stress rates, updated
kinematics) moves the bar wave and rigid-wall impact results by a few 1e-4
relative; both still pass their references (u_end 0.034%, rebound KE
-0.54%). Fixtures updated deliberately for that change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 87d51a0afe ci: the oracle job follows the code-change filter; docs/oracle.md counts as code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 6dda34e837 simplify: joining in modelcraft-mesh::join, shared label helper, run_script returns step outputs
Weld and face-to-face check move from the mesh.block command to
modelcraft_mesh::join (bounding-box-limited weld, grid-binned hanging-node
test), for node merging and imports to reuse. mesh.block and
mesh.tet.generate share fresh_labels. The block map evaluates each edge
curve once per lattice parameter; vector helpers come from vec3; tet10 and
hex20 mid-side nodes follow ElementKind::edges. Session::run_script returns
each step's output, which the oracle reads instead of its own loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 cbff230734 docs: oracle in architecture and roadmap; fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 6d9452ad84 Verification oracle: tests/oracle cases, cargo xtask oracle, fixtures, docs/oracle.md, CI job
The oracle binary (modelcraft-testkit) runs each case's recipe through the
command engine, measures its quantities by JSON pointers into step outputs
or commands, compares them with the cited references and the frozen
fixtures, and writes docs/oracle.md. 20 theory cases (patch tests of every
solid element, Timoshenko cantilevers, Lame, Kirsch, beam frequencies,
torsion and bending through couplings, explicit waves and impact, CDT
invariants), 8 published benchmarks (MacNeal-Harder patch, straight and
curved cantilevers, thick cylinder, Scordelis-Lo with solids, NAFEMS LE1,
LE10, FV32) and 10 pending cases with citations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
jyvikoandClaude Opus 5.5 a3cb6d749b mesh.block: mapped hex, tet and wedge blocks with curved edges; solve.probe at points
A structured mesh of a six-sided block from its corners, with circular
and elliptical edges (Gordon-Hall transfinite interpolation), grading,
named faces, edges and vertices as geometry tags, and joining to the
existing mesh face to face (refused otherwise). solve.probe takes "at"
points and reports the nearest node with a result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 21:42:19 -04:00
15a9ac9157 1D elements I: trusses (rod2, rod3), point masses, rotary inertia, springs and dashpots (#38)
* Model, fem and decks: point masses, rotary inertia, springs, dashpots and rod3 trusses

Model: PointMass and Spring definitions (spring | dashpot; to ground or
two-point; along a degree of freedom, a vector or the line of the ends) on a
set or reference point, resolved per node; ElementKind::Rod3. fem: rod2/rod3
truss kernel on Basis::Line2/Line3 declaring ElementDofs (translations).
Decks: Abaqus T3D3, MASS, ROTARYI, SPRING1/2/A, DASHPOT1/2/A; Nastran CONM1,
CELAS2, CDAMP2; LS-DYNA ELEMENT_MASS/INERTIA and ELEMENT_DISCRETE; what a
format cannot take is reported. Commands model.mass.* and model.spring.*.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Implicit solver: trusses, point masses, rotary inertia and springs on the DofLayout

Trusses declare their translations like solids; point masses, inertia and
springs ask the layout for the kinds they need at their nodes (translations,
rotations), and reference points they use get six degrees of freedom. The
matrix pattern takes rows for those points; dashpots are ignored with a
warning. Existing results are bit-identical (hash-checked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Explicit solver: trusses, point masses, springs and dashpots on the node-major layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: fem.md on trusses and discrete elements in the layout; regenerate features.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: trusses in plane models and corotational explicit trusses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:36:46 -04:00
5e5a2ae251 Fast PR CI: small hostile-test caps, heavy tests in a separate job (#43)
* test: hostile-params test runs under small session work caps

Session gains Limits, the defaults of mesh.tet.generate's max_steiner and
max_tets (production values unchanged; a parameter still overrides them;
file.open keeps them). The registry-wide hostile-params test runs on
sessions with small caps, so the needle-triangle tessellation it meshes is
refused at once instead of meshing to the 50k Steiner cap: the engine test
binary drops from ~350 s to ~20 s locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* ci: move heavy tests to a heavy job on main, nightly and on demand

Four slow tests are #[ignore = "heavy: ..."] (every corpus recipe, the
cantilever convergence study, the explicit-vs-modal cantilever period, the
generic-predicate rational cross-check); fast representative tests of each
area stay in the PR gate. cargo xtask test --heavy runs every ignored test
(benchmarks included) and the doc tests; cargo xtask test and ci run the
rest without the doc tests. The new heavy CI job runs it on pushes to main,
nightly and on workflow_dispatch. Branch jobs restore main's rust cache and
no longer save their own. AGENTS.md documents the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* test: the three slow LBM verification cases run in the heavy job

The Stokes-drag sphere array, the staircase-pipe convergence study and the
developing-channel case are heavy (21 s, 11 s, 6 s alone); plane
Poiseuille, Couette, Taylor-Green, reproducibility and the stability
limits stay in the PR gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:21:49 -04:00
7dc7a0e36d docs: shell quad meshing survey (advancing front in (u,v) with a cross field); citation fix (#44)
Survey of ~40 papers for meshing curved B-rep faces for shells: frontal-Delaunay point placement in
the cross-field metric (Remacle et al. 2013), recombination to all-quad, cleanup (Reberol et al.
2021), global parity by minimum T-join, verification on shell benchmarks; licences of every code
base. Also fixes the Lee & Lo 1994 author initials in literature.md B7.2.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:15:57 -04:00
jyvikoandClaude Opus 5.5 50764def18 Explicit X2: large deformation, J2 and Johnson-Cook plasticity, damage and erosion (M10)
- model: materials gain solver-neutral plastic (bilinear isotropic/kinematic,
  hardening table, Johnson-Cook with adiabatic heating), damage (Johnson-Cook
  D1-D5) and erosion (plastic strain); explicit steps gain nonlinear_geometry
  (default on) and stress_rate (jaumann | green_naghdi).
- fem: rotation (Hughes-Winget, Flanagan-Taylor), plasticity (radial return,
  capped bracketed Newton for Johnson-Cook, damage; the update takes the
  mechanical strain increment so thermal strains can be subtracted), closed-form
  hex8 gradients for deformed configurations.
- explicit: updated Lagrangian hex8/tet4, per-element material state, erosion
  with reported energy, plastic work in the energy account.
- solver: warns that the linear solver uses the elastic constants of plastic
  materials (results hash-identical).
- decks: Abaqus *PLASTIC (+ Johnson-Cook keywords), Nastran MATS1/TABLES1,
  LS-DYNA *MAT_PLASTIC_KINEMATIC / *MAT_PIECEWISE_LINEAR_PLASTICITY; what a
  format cannot express is reported as Unsupported.
- engine: model.material.plasticity, model.material.damage (catalogue ready),
  explicit element probe, eroded elements in the solve summary.

Step commits before the rebase onto the DOF framework: branch explicit-x2-steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 20:52:57 -04:00
4c0fcc80ad 2D elements: plane stress, plane strain and axisymmetric sections on the DOF framework; mesh.planar (M4) (#39)
Model: planar section kinds with Extent, element edges in sets, edge-weighted 2D loads,
centrifugal load, 2D model checks. fem: Plane kernels on Basis::{Tri3,Tri6,Quad4,Quad8}
declaring ElementDofs::new(DofSet::PLANE); QM6 incompatible modes with the
Wilson-Ibrahimbegovic correction. Solver: Kernel enum on the DofLayout; 3D results
bit-identical (hash-checked). Decks: Abaqus CPS/CPE/CAX, Nastran membranes and RFORCE,
LS-DYNA ELFORM 12/13. Mesh: free triangle mesher, planar region meshing, face boundaries.
Engine: mesh.planar with tagged edges. Analytic verification suite in solver/tests/plane.rs.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:24:56 -04:00
4e4d19b88c feat: application icon family (a meshed bracket) drawn with VectorCraft (#37)
cargo xtask app-icon computes an isometric L bracket with a bolt hole and a
structured hexahedral mesh (O-grid around the hole), draws it with
vectorcraft-cli and writes icon.svg/png, small-size artwork, the iconset,
icns, ico, an Icon Composer ModelCraft.icon (floor grid Default/Dark, part,
mesh) and its actool Assets.car. The desktop app uses the 256 px export as
its window icon, falling back to eframe's default if it cannot be decoded.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:11:41 -04:00
a17752a49f Lattice Boltzmann flow on the CPU: model, solver, engine (M12) (#36)
Model: Newtonian fluid materials; fluid domains (box, spacing, conditions on
the box faces, obstacle bodies with conditions on their named faces, initial
velocity, body acceleration); fluid steps with caps. Deck writers refuse
fluid steps; a fluid-only model needs no mesh in model.check; one step
selector (Model::select_step) for every solver.

New L2 crate modelcraft-lbm: D3Q19 two-relaxation-time collision with Guo
forcing; halfway bounce-back walls (moving walls after Ladd), slip faces,
periodic pairs; Zou–He velocity and pressure faces with the D3Q19
transverse correction; obstacles voxelised from the face-tagged boundary
surface with exact predicates and symbolic perturbation; momentum-exchange
forces per box face and per B-rep face; unit conversion, automatic time step
and Mach/tau limits; bounded runs with frames and force histories.

Engine: model.fluid_domain.create/edit/delete, model.fluid_domain.boundary,
solve.fluid (obstacles from the open design or imported surfaces, lowerable
caps), solve.probe for fluid fields and wall force histories. Fluid Domain,
Inlet & Outlet and Lattice Boltzmann toolbar entries are ready.

Verification (analytic): plane Poiseuille exact with Λ = 3/16 and second
order (2.000) with Λ = 1/4; Couette exact with the wall shear; Taylor–Green
decay second order (2.01, 2.00); staircase pipe; Stokes drag of a sphere
array within 1.7% of Hasimoto at R = 6; Zou–He channel; mass conserved to
round-off; bit-for-bit reproducibility. Benchmark: Schäfer–Turek 2D-1.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:03:12 -04:00
bd55abea7f FEM foundation A: DofKind/DofLayout, shape-function library with Hessians, solvers on DofLayout (#35)
* fem: DofKind/DofLayout and the shape-function library with second derivatives

DofKind (Ux..Rz, T, P), DofSet, ElementDofs (per-node kinds and condensed
internal unknowns) and DofLayout: mesh nodes by label, kinds in order, then
reference points. basis::Basis covers line2/3, tri3/6, quad4/8/9, tet4/10,
wedge6/15, pyramid5, hex8/20/27 with values, first and second natural
derivatives (second-order forward AD, fem::jet) and the physical gradients,
Hessians and Laplacians with the map's second-derivative term. Tested for
Kronecker delta, partition of unity, polynomial completeness, finite
differences, affine and curved maps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver, explicit: number degrees of freedom with DofLayout

The implicit mesh numbers its unknowns with a DofLayout (mesh nodes by label
with the kinds their elements declare, then six per coupled reference point);
assembly adds element blocks by (node, DofKind), so a node may carry any mix
of kinds; couplings, boundary conditions, loads and results address
(node, kind). The explicit system keeps node-major translations, now checked
against its layout (NODE_DOFS). Solid formulations declare their ElementDofs.

Every static, frequency and explicit result of the solver, explicit, engine
and io test suites hashes bit-identically before and after (957 results).
docs/fem.md describes the DOF model and the shape-function conventions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* fem, solver: simplify after review

Block offsets resolved once per node pair; add_scalar/add_diagonal take
the kinds like add_block; one checked Jacobian inverse (dense::positive_inverse)
for the solid kernels and the basis library; static natural-node tables; the
Hessian path inverts the Jacobian once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:54:32 -04:00
c686cbd531 ci: skip the build jobs for pull requests that only change Markdown or docs/ (#34)
docs/features.md still counts as code (a test checks it against the catalogue). Pushes to main
and manual runs always run every job.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:43:36 -04:00
0e97f8dd1d Results display (M4) and offscreen full-UI capture (#28)
* ui.capture: the whole window drawn offscreen and rasterised on the CPU

ui.screenshot falls back to it when no frame is presented (hidden window,
locked screen), so agents can look at panels and the toolbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Draw the open design and imported surfaces in the viewport

DisplayMesh keeps node labels on every element primitive (for results),
gains add_surface (geometry faces and their boundaries, coincident points
welded) and displaced (elements moved by nodal offsets). Bodies are
tessellated at a thousandth of their size; meshed models show the geometry
as boundaries only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* results.* commands: deformed shape, contours, mode shapes, animation, reaction sums

The results display is session state (ResultsView), set by commands that are
not edits. Nodal fields (displacement, von Mises, principal and component
stresses; principal stresses in closed form after Smith 1961) and reaction
force and moment sums per set. Results kept from an earlier revision stay
viewable, marked stale; solve.probe says when a result is stale. The Results
tab's display and reaction entries are ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Results display: deformed shape, contours with legend, mode shapes and animation, probe, reaction sums

The viewport draws the session's results view: elements moved by their nodes'
displacements or a mode shape (automatic or given scale), faces split exactly
along contour band boundaries into flat-coloured pieces (same on GPU and CPU),
a legend with levels and extremes, a results panel whose controls run the
results.* commands, a click probing the nearest node (solve.probe), and stale
results marked. Solving from the form shows the result on the Results tab.
sample::cantilever is a solvable model for tests and demos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Simplify the results display (review)

One Contour in the plot; the results scene reuses scene::build; a solve shows
its result through the engine (ResultsView::for_result) and the UI raises the
Results tab when a view appears; the plot's scale and contour values are kept
across animation frames; design bodies are tessellated once per design; the
first content is framed where the display is rebuilt; field_values in one
match; probe reuses results::displacements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Results display for explicit steps: field frames, energy histories

The results view works on the engine's step results (static, frequency,
explicit): ResultsView.frame and results.frame {frame | time} pick an explicit
field frame; the deformed shape (true scale) and contours (element stresses
averaged to the nodes, velocity) follow it; animation runs through the frames.
The Results panel has a frame slider and plots the kinetic, internal, hourglass
and external energies with their values at the frame. sample::impact is a
steel bar flying onto a rigid wall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs/features.md after the rebase

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:23:05 -04:00
1899ecbd2f docs: always rebase onto latest main before opening or updating a PR; merge only when CLEAN (#33)
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:04:46 -04:00
6ca3cca1b8 fix: regenerate docs/features.md (tet meshing is ready since #29) (#32)
Main's CI failed features_page_is_current: #29 marked mesh.tet.generate ready, and the page
generated on its branch was out of date once combined with main.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:56:54 -04:00
de8c0e2700 docs: verification and validation; published benchmarks; fixtures (#31)
* docs: verification and validation against theory, experiments and published benchmarks; fixtures

Owner decision 2026-10-13: test against experimental and theoretical data; results published by
vendors (e.g. Abaqus benchmark and verification manuals) may be used as cited reference values for
our own models of the same problems; once a case matches, its result becomes a fixture so fidelity
cannot regress. Commercial products are still never run and their files and text never copied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: V&V policy in AGENTS.md, ADR 0001 and the roadmap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:43:36 -04:00
aa68bbbcdf Constrained Delaunay tetrahedralization (boundary recovery) and mesh.tet.generate (M2) (#29)
* predicates: LNC points, generic insphere and its perturbed form (Diazzi et al. 2023 §4.3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: constrained Delaunay tetrahedralization (segment recovery with LNC Steiner points, gift-wrapping facet recovery, labelling)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* cdt: rounding repair, mesh.tet.generate, corpus CDT column; review cleanups (shared perturbation rule, one facet scan, cached coordinates, worklist segment recovery)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh.tet.generate is ready in the catalogue; corpus table with the CDT column

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:37:09 -04:00
e688f6f934 docs: element library plan, family by family (#30)
Owner 2026-10-13: cover the element families a full CAE code offers, simple ones first (1D and
2D). docs/elements.md lists point/discrete, 1D, 2D, shell/membrane, 3D and special-purpose
families with status and the task that brings each.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:36:44 -04:00
d45a2da5b5 Explicit dynamics core: central differences, one-point hex8 with hourglass control, nodal-pressure tet4, solve.explicit (M10 X1) (#27)
* Model: explicit dynamic steps (end time, increment scale and cap, output intervals, initial velocities, rigid walls, hourglass and bulk viscosity controls); deck writers refuse them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* fem: uniform-strain hex8 and tet4 kinematics, Flanagan–Belytschko hourglass shape vectors, element frequency bound

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Explicit solver crate (L2): central differences with lumped mass, one-point hex8 with Flanagan–Belytschko hourglass control, Bonet–Burton tet4, bulk viscosity, rigid walls, energy account; wave and impact verification

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Engine: solve.explicit and solve.history commands, explicit results in the session and solve.probe; cantilever period against the modal solver

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Docs: explicit solver in the architecture, roadmap (M10 started), parity and literature (Part H)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Simplify: Model::select_step shared by both solvers, Procedure::sets/is_dynamic, fused per-increment passes without allocation, shared element tail, generic command caps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Catalogue: solve.explicit is ready; solve.history beside solve.probe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Rebase onto the implicit couplings work: Procedure is no longer Copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:28:32 -04:00
48039639cb Implicit solver: reference points, couplings, moments, wedge6 (M4) (#26)
* fem: wedge6 element (triangle × interval, 3 × 2 points), patch and cantilever tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver: reference points, kinematic and distributing couplings, moments by constraint elimination

Coupled reference points get six degrees of freedom; kinematic couplings are rigid
links, distributing couplings least-squares interpolation constraints. Every
constraint row and prescribed value is eliminated master-slave (Abel-Shephard
pivot choice), so the reduced matrix stays SPD; no coupling gives the old
elimination bit for bit. Verified: rigid motion of a coupled block, clamping
through a point, distributing tension vs pressure, bending and tube torsion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: reference point results in solve.static and solve.probe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: couplings, moments and wedge6 in the solver docs, literature and roadmap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver: simplify constraint elimination and reference point plumbing

Rows combine by one sort; T_f transposed once; K and M reduced in one pass on a
shared pattern; prescribed motion computed once; reference point loads and BCs go
through Resolved like node ones (Mesh::label_dofs); one dof-direction rule;
StepResult::locate for probes; dead Csr::restrict removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver: refuse oversized reductions before allocating them

A held distributing coupling point makes a dense (3N)^2 block; estimate it from
the constraint rows and refuse it against the factorization's memory budget
with a clear message, before anything is built. Other reductions with
dependents count their entries first (stopping at the budget) and reserve
fallibly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:21:52 -04:00
a292212b67 Toolbars for every capability, VectorCraft icons, coming soon for planned ones (#20)
* feat(engine): capability catalogue — every toolbar entry, ready or coming soon, and docs/features.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* feat: 98 toolbar icons drawn with VectorCraft from recipes (cargo xtask icons), attributed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* feat(ui): toolbar of tabs and panels from the catalogue, VectorCraft icons recoloured per theme, coming-soon entries greyed with their milestone; ui.toolbar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* refactor: ready entries are their command; one tooltip; tabs and panels as catalogue runs; icon cache per theme and size; ui.toolbar read-only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: roadmap notes the toolbar and icons

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* feat(ui): deck entries run deck.write with their format, ready when the writer is in FORMATS; file and edit entries; compact and collapsed panels instead of scrolling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: features page after the Nastran writer (#24) made its deck entry ready

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: features page after the LS-DYNA writer (#25) made its deck entry ready

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* feat: solve.static, solve.frequency and solve.probe on the toolbar (Solve and Results tabs)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:49:31 -04:00
40f98f0af9 Implicit solver: fem kernels (tet4/10, hex8, hex20), sparse Cholesky, block Lanczos, solve.* commands (M4) (#23)
* Renumber the layer table for solver crates: fem at L1, solver at L2, fsi at L3

L0 model -> L1 geometry, mesh, fem, decks, render -> L2 solver (explicit, lbm later)
-> L3 fsi (later) -> L4 doc -> L5 io -> L6 engine -> L7 ui-egui, mcp -> apps.
UI_MIN_LAYER moves to 7, the testkit sits above it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* modelcraft-fem: element kernels for tet4, tet10, hex8 (full, incompatible modes), hex20 (full, reduced)

Shape functions, Gauss and conical-product quadrature, stiffness, consistent and HRZ-lumped
mass, body forces, consistent pressure loads on faces, integration-point stresses and their
least-squares extrapolation to nodes, isotropic and orthotropic elasticity. The element table is
one row per kind, so wedges, shells and beams are added locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* modelcraft-solver: linear static and frequency analysis with an analytic verification suite

Assembly from the resolved model on the node graph, boundary conditions by elimination,
consistent pressure, gravity and body-force loads, nested-dissection ordering, sparse up-looking
Cholesky with a factor-size cap, reactions, integration-point and nodal-averaged stresses, von
Mises; shift-invert block Lanczos with full reorthogonalization for natural modes, participation
and effective mass. ADR 0001 records the own-linear-algebra decision (determinism, wasm).

Verified against closed forms: displacement and traction patch tests for every element, cantilever
tip deflection (Timoshenko) with convergence rates, Lamé cylinder, Kirsch hole (Kt -> 3),
cantilever and free-free beam frequencies with six rigid-body modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Engine: solve.static, solve.frequency and solve.probe; results kept in the session

Results are stored per step, restored on a failed command and cleared when the model changes.
Caps (degrees of freedom, factor entries, Lanczos vectors) may only be lowered; hostile parameters
are tested against a solvable session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: implicit solver architecture, roadmap M4, parity and references

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Simplify the solver: shared face integration, block assembly, cached extrapolation

- model: one face integrator (nodal::face_normal_weights) for tractions and pressures
- fem: mid-side tables from ElementKind::edges, orthotropic via inverse3, symmetric
  accumulation without a symmetrize pass, incompatible-mode recovery without K_uu, model's
  coordinate check, dead table rows removed
- solver: one pattern search per node pair (block, scalar and diagonal adds), extrapolation built
  once per element type, reactions from the prescribed rows only, matrices dropped after
  restriction, the free-body shift keyed on the factorization error, step selection in the solver,
  Error::Element naming the element
- engine: the solver owns the default step and the shift check; the model comparison runs once

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: results are stamped with the session revision

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:44:21 -04:00
81fa8d7220 LS-DYNA keyword writer (implicit static and eigenvalue, M3) (#25)
* decks: LS-DYNA keyword writer (long format, implicit static and eigenvalue)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* decks: LS-DYNA golden files, Jacobian-sign node-order tests, re-reader and refusals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: LS-DYNA writer status and deck notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* decks: simplify the LS-DYNA writer (plan decides once, cards written in place, shared test tables)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* decks: LS-DYNA joins FORMATS (deck.write lsdyna), refusals reported together, shared concentrated loads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* decks: LS-DYNA on main's shared deck API (steps parameter, model helpers)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:39:45 -04:00
f4a0333d28 Nastran bulk data writer (SOL 101/103) and deck.write steps (M3) (#24)
* Nastran bulk data writer: fixed-format entries, exact large-field reals, SOL 101/103 decks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Nastran writer tests: golden decks, a re-reader of our output, node order against Nastran's conventions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: Nastran writer in the roadmap, parity table and deck notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Nastran writer: simplify planning (subcases, one property table), stream cards into the deck, write through the VFS

ElementKind::corner_count/is_quadratic and Dofs::bits/from_bits join the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Nastran joins the shared deck formats; deck.write chooses steps for every format

nastran::write_resolved is the FORMATS row; Format::write_steps and with_steps write a subset of
steps (a Nastran run is static or frequency steps); problems go through the shared unsupported().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:34:26 -04:00
a71f85eb58 Abaqus input writer and deck.write (M3) (#22)
* decks: Abaqus input writer with golden decks, read-back, Jacobian and unsupported tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: deck.write command (format dispatch, atomic file write), docs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* decks: format table, one resolve per write, direct buffer writes; engine: shared path parameter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:27:44 -04:00
2d99a8571c The .modelcraft file, undo/redo on the journal, atomic saves and autosave recovery (M3) (#21)
* modelcraft-io: the .modelcraft file (modelcraft/1), atomic writes, autosave storage

A stored ZIP of a manifest (format tag, geometry sources with SHA-256,
part sizes and hashes), the model definition as stable pretty JSON, the
mesh and geometry tags in a compact binary part, and the journal one
command per line. Reading caps every size, checks every checksum and
replays the model through its checked methods. Model::without_mesh and
empty mesh fields left out of the model's JSON; geometry::read_file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: undo/redo on the journal, file.open/save/save_as, autosave and recovery

CommandSpec gains a kind: Model commands are journalled and become undo
steps; Session commands (file.*, edit.*) manage the session. Undo history
is bounded by steps and estimated memory. Geometry sources are recorded
with content hashes and reopened on file.open (missing or changed files
are warnings). Desktop app autosaves every 30 s and reports recoverable
models; Ctrl/Cmd+Z and Shift+Z/Y undo and redo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: the .modelcraft format, undo history, roadmap and parity status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* simplify: shared path parameter, one load path for open/recover, history accounting in History

Session commands skip the per-command model copy; saved revision is an
Option; journal written from borrowed entries and read without an
intermediate tree; backups copied by the file system; recovery lock and
error helpers; geometry file wrappers use read_file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:22:01 -04:00
9fb00317ad Add the modelcraft-decks crate (empty: error type only) (#19)
So the Abaqus, Nastran and LS-DYNA writer tasks add their modules in parallel.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:54:25 -04:00
57b4b2f4d2 Model definition: sets by geometry name, materials, sections, couplings, BCs, loads, steps, check (#16)
* model: split nodes and elements into a mesh module (no behaviour change)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* model: sets by geometry name, materials and units, sections, couplings, BCs, loads, steps and the check

Sets bind to persistent geometry names through the mesh's geometry tags
(element, element face/edge and node classification) and resolve against
the current mesh, so they survive a remesh. Steps list their active loads
and boundary conditions explicitly. Model::check names every missing or
bad item; Model::resolve gives the writers' view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: model definition commands (sets, materials, sections, RPs, couplings, BCs, loads, steps, tags, check)

Each definition has create/edit/delete with its model JSON form as
parameters; model.step.activate and model.activity drive the explicit step
table; mesh.tag records where the mesh lies on the geometry; model.check
and model.set.resolve report against the current mesh. Hostile params run
against an empty and a fully defined session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: model definition in architecture and roadmap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* model, engine: review cleanups

Validated deserialization (a file replays through the checked methods),
interned geometry tags by index, set resolution with tag bitsets and sorted
vectors, one delete policy (a load or BC listed by a step is in use),
shared helpers (context, remove_existing, step_mut, opt/req/name/listed),
dead code removed, Resolved without a per-element section map.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* model: mesh-independent force distribution, tractions, stable reference point labels

Force and moment on a set default to a total, split by consistent nodal
weights over the set's faces (or equally over a node set); per_node is an
explicit opt-in. Traction is force per area. resolve() expands them to
nodal values for every writer and the solver. Reference points keep a
stored node label taken from the top of the label range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:19:54 -04:00
6c36f5b2ad Planar all-quad meshing: size fields, TFI mapped meshes, free quad mesher (M5 base) (#18)
* Planar all-quad meshing: size fields, TFI mapped meshes, free quad mesher, planar quad validity

- modelcraft_mesh::size: SizeField trait, division counts, graded polyline division, per-loop parity
- modelcraft_mesh::quad: transfinite (Gordon-Hall) mapped meshes of four-sided regions; free
  all-quad meshes of polygons with holes on prescribed boundary nodes (half-resolution CDT with
  Ruppert refinement, Lee-Lo recombination, uniform split, smart Laplacian plus
  Freitag-Plassmann untangling), exact validity gate on every element
- modelcraft_mesh::quality: Robinson aspect ratio and skew for quads, exact planar quad validity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* quad mesher: true-boundary guard for Steiner points, untangling by corner area, parity retry, recombination quality floor; I-beam and perforated plate tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: quad meshing papers (B7), roadmap M5 and M2, architecture pipeline step

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* quad mesher: simplify (shared corner and validity helpers, vertex rotation helper, guard decoupled from the CDT, verify inside the CDT, non-allocating final gate)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:15:54 -04:00
25defdc0ec docs: solver literature survey; explicit, EOS, lattice Boltzmann and FSI milestones (#17)
Owner decisions 2026-10-13: own solvers beyond linear (explicit dynamics with equations of state,
lattice Boltzmann CFD, fluid-structure interaction; blast fluids after), flow first; published
reference values allowed as a tagged benchmark suite beside the analytic gate; GPU backends
deterministic per device and checked against the bit-exact CPU path; layered crates (fem kernels,
solvers, fsi). ADR 0001 lists the GPL-family solver codes (Palabos, OpenFOAM, ...) and the
readable references.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:09:07 -04:00
02cc9dab57 Boundary surface preparation (weld, manifold/orientation, exact self-intersection, feature edges) and mesh.surface.check (#15)
* mesh: boundary surface preparation (weld, manifold and orientation checks, exact self-intersection, feature edges)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: mesh.surface.check on an open design's body or an imported surface

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: struct-init the test session

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* surface: weld at an absolute distance too; the engine welds CAD tessellations at twice SolidCraft's linear tolerance

Faces of a SolidCraft tessellation carry their own copies of shared boundary points, which agree
only to within the kernel's tolerance (up to ~2e-6 mm on the test corpus).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: surface preparation in the literature survey, architecture and roadmap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* surface: simplify after review

- The weld distance travels with the mesh (SurfaceMesh::weld_distance, set by Shape::tessellate),
  so every consumer welds a tessellation correctly without opting in.
- Exact tests are infallible on predicates::orient2d_projected (shared with generic_orient2d), and
  compute each vertex's plane side once per pair with an early reject.
- One Box3 for the weld, the BVH and shells; a Half struct for edges; the weld's two lookups share
  one loop, with chained grid cells; cached normals for feature edges; shared parameter helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* corpus: boundary surface preparation as a corpus stage

20 of 21 parts are valid boundaries with their expected genus; cross-cylinders' tessellation has
cracks and flipped triangles where its two intersection curves meet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:01:42 -04:00
94dc50bb0c Exact 3D Delaunay tetrahedralization (Bowyer–Watson, ghost hull, BRIO/Hilbert, verifier) (#14)
* mesh: exact Bowyer-Watson Delaunay tetrahedralization with ghost tets, BRIO/Hilbert order and a verifier

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: Delaunay benchmark, hash-paired cavity faces, mesh.delaunay.points debug command

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: simplify Delaunay helpers, share predicates and parsers, halve verifier work

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:56:42 -04:00
2b5b47930f docs: roadmap revised around Abaqus/CAE scope; capability checklist (#13)
One end-to-end slice first (sets bound to SolidCraft names, materials, sections, BCs, loads,
steps, three deck writers, own solve) on tet meshes; then structured hex (sweep, mapped,
automatic decomposition) before octree all-hex; then contact, materials and explicit, and
post-processing. docs/parity.md lists capabilities by milestone, with the areas where we aim
to be better than Abaqus/CAE.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:50:19 -04:00
3c1485d83a Desktop app: egui/wgpu viewport, model tree, commands and control channel (#9)
* render: display data from the FE model, orbit camera and CPU rasterizer

modelcraft-render (L1, no GPU): DisplayMesh (the unshared faces of solid
elements, shells, element edges, line elements, nodes), the orbit camera
and a CPU rasterizer with PNG output for headless snapshots, adapted from
SolidCraft's render crate. Workspace: egui/eframe/egui-wgpu 0.36 and
SolidCraft's patched egui-wgpu from the pinned rev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: sample model script (a hex8 block and a tet4 cube)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Desktop app: egui/wgpu viewport, model tree, commands, status bar, control channel

modelcraft-ui-egui (L5): ModelApp over the engine Session; GPU viewport
(lit faces, element edges, nodes) with a CPU fallback; orbit/pan/zoom,
standard views and fit; model tree; commands panel generated from the
registry with a JSON parameter form; status bar; control request
handling (engine.*, ui.*, ui.screenshot, ui.render). apps/modelcraft:
eframe window, --sample, --control PORT loopback JSON-lines server.
Adapted from SolidCraft's ui-egui and app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* THIRD-PARTY-LICENSES: the desktop app's dependencies

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: control protocol, desktop app in architecture, roadmap and attribution

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* render: simplify the display mesh (sorted face keys over maps, no unused fields)

Interior faces are found by sorting fixed-size face keys instead of a BTreeMap of Vecs, edges
are deduplicated by sort, and each element's corners are looked up once. Drops the unused
`element` fields on DisplayTri/DisplayLine, the unused Mat4::IDENTITY and corner_count, and
hoists the line colour out of the raster loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* viewport: split GPU geometry into batches within the buffer limit; refuse oversized CPU renders

A mesh with a few million boundary triangles made one vertex buffer larger than the device's
max_buffer_size (256 MiB under the limits eframe requests), and wgpu's default error handler
panicked. Geometry is now uploaded in batches of whole primitives, each at most 128 MiB and the
device limit, and all are drawn. A device error handler logs errors and shows them in the status
bar instead of panicking.

The CPU renderer refuses sizes over 8192 per side or 16 MP, and scenes over 20 million
primitives, with an error, and allocates its buffers fallibly. ui.render reports a size out of
range instead of clamping it; the CPU viewport fits a large window into the limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:29:49 -04:00
a817a62440 Meshing test corpus built with SolidCraft; assembly placement in open_design (#11)
* geometry: build designs from SolidCraft command scripts (feature scripts)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* geometry: place component bodies through their occurrences when opening a design

An assembly's instances were missing and moved components sat at their own origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Meshing test corpus: 16 SolidCraft recipes, analytic shapes, loader and runner

modelcraft-testkit holds the corpus (tests/corpus/<name>/recipe.json run through SolidCraft's
engine, then opened as a design) and the runner binary behind cargo xtask corpus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: corpus results, test corpus architecture and the scripts feature; licences

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* corpus: measure each body once, check stage widths, simpler loader and runner

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:19:06 -04:00
d8b3bb51e0 Element quality metrics and the certified validity gate (mesh.quality) (#12)
* mesh: element quality metrics and a certified Jacobian validity check

Bernstein-form Jacobian determinants for tet4/tet10/wedge6/hex8/hex20, certified
by bounds and subdivision (Johnen-Remacle-Geuzaine 2013); Shewchuk 2002 tet
metrics, Knupp 2001 hex/wedge/quad metrics; quality report and the validity
gate meshers run on their output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: mesh.quality command; model documents its node order

mesh.quality reports certified validity, volume and per-metric statistics with
fixed-range histograms. The model's ElementKind doc states the Abaqus/VTK node
order and that deck code reorders formats like Nastran CHEXA20.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* quality: simplify after review; exact orient3d for tet4; gate on raw mesher arrays

- tet4 validity from the exact orient3d of the input coordinates
- validity_gate_elements takes (label, kind, coordinates) straight from a mesher
- one Jacobian per solid; certify caches piece bounds; product weights precomputed
- model::check_coordinates shared by set_node and quality; ElementKind is Ord
- engine: shared labels() parameter parser

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:14:30 -04:00
62b0553a89 geometry: face-tagged 3MF follows SolidCraft's namespace and spec (#10)
SolidCraft owns the 3MF face-tag format (its docs/formats/3mf-face-tags.md); the
reader now matches its namespace, and docs/formats/tagged-mesh.md points there while
documenting what ModelCraft's reader does and refuses.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:12:41 -04:00
4667655e08 mesh: exact predicates (Shewchuk adaptive, symbolic perturbation, indirect LPI/TPI) (#8)
* mesh: exact predicates (Shewchuk adaptive orient/incircle/insphere), symbolic perturbation, indirect LPI/TPI predicates

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: predicate benchmark (filter hit rate, CAD-degenerate inputs); record the predicate decision

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: simplify predicates (destructuring over fallbacks, shared-minor exact insphere, cheaper expansion product)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:04:56 -04:00
90254bb32f docs: SolidCraft is the primary geometry source; STEP is not excluded (#7)
Corrects ADR 0001, which recorded the owner's decision as excluding CAD exchange files. The
decision is that geometry comes mostly from SolidCraft; STEP can be a secondary input.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:01:37 -04:00
2c1821d0ad geometry: read tagged meshes through the VFS, refuse singular transforms, share the model's coordinate limit (#6)
read_3mf_file reads through solidcraft_io::vfs like open_design_file (regular files only,
browser files too), so the engine drops its own capped reader and EngineError::File.
A build item whose transform flattens its object is an error, and coordinates use
modelcraft_model::MAX_COORD (1e9) instead of a looser limit of their own.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:52:53 -04:00
jyvikoandClaude Opus 5.5 f181c98b75 Face-tagged 3MF exchange: format spec, reader, geometry.import.mesh
docs/formats/tagged-mesh.md defines the exchange: 3MF Core with an optional
ft:face attribute per triangle (and ft:faces per object) in a ModelCraft
namespace. modelcraft_geometry::tagged_mesh::read_3mf reads it from bytes
(wasm), capping every size and count and range-checking every index and
coordinate. geometry.import.mesh adds the surfaces to the session, which keeps
them beside the open design and restores them when a command fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 14:48:22 -04:00
f36b1d9a69 Open .solidcraft designs: rebuild, persistent face names, geometry commands (#4)
* geometry: open .solidcraft designs, rebuild them and keep persistent face names

open_design(bytes) reads a design (container or JSON) with solidcraft-io, rebuilds its
timeline with solidcraft-doc and returns its bodies; each Shape carries SolidCraft's
persistent face names by B-rep face index, so a face keeps its name through a parameter
edit. sample::plate_design builds a parametric test plate in code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: geometry.open, geometry.bodies and geometry.tessellate

The session holds the open design (shared, restored on a failed command, journalled when
it changes). Hostile-params coverage includes non-files, missing files, oversized paths and
extreme tolerances against an open design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: opening SolidCraft designs in the architecture and roadmap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* geometry: open_design_file reads through SolidCraft's bounded file access; simplify

The engine's hand-rolled capped read is replaced by open_design_file, which uses
solidcraft_io::vfs::read (refuses non-regular files before opening, works on the browser's
in-memory files). Drops the duplicate size check, simplifies the sample sketch and the
design-change test in Session::execute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:41:05 -04:00
620ef6bc64 Add the modelcraft-mesh crate (empty: error type only) (#3)
So the predicates and quality tasks can add modules in parallel without both creating the crate.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:15 -04:00
4abfa0845b docs: own test corpus built with SolidCraft, icons made with VectorCraft (#2)
Owner decisions 2026-10-13: no public CAD datasets (Thingi10K included); test parts are
SolidCraft recipes and analytic shapes. Icons are VectorCraft artwork with the editable
source committed; cargo xtask assets now also requires .vectorcraft and .solidcraft files
to be attributed. AGENTS.md: GitHub CI runs the full gate, commit after each working step.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:18:46 -04:00
e7f5c83b49 docs: meshing literature survey and the tet/hex pipeline it chooses (#1)
Survey of about 75 papers on robust predicates, Delaunay tetrahedralization, boundary
recovery, quality improvement and hexahedral meshing, with verified DOIs and the
licence of every associated code base. Architecture and roadmap now name the chosen
methods (robust CDT after Diazzi et al. 2023; octree-dual all-hex first). ADR 0001
adds the licence findings and the test-geometry rule.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:11:15 -04:00
jyvikoandClaude Opus 5.5 b64ec3f698 ModelCraft foundation: workspace, quality gates, FE model, command engine, geometry boundary
A pure-Rust CAE modeling workspace following the SolidCraft conventions:
- modelcraft-model: nodes and elements by label with hostile-input validation
- modelcraft-geometry: SolidCraft's kernel (pinned git dependency) behind our own API;
  tessellation into a face-tagged boundary surface for volume meshing
- modelcraft-engine: CommandSpec registry, Session::execute with rollback and panic guard,
  scripts and a journal
- modelcraft-cli: list commands, run scripts
- xtask: layers (only geometry may use solidcraft-*), assets, licences, tests, wasm, ci
- AGENTS.md, architecture, ADR 0001 (libraries and licences), roadmap, CI

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-13 13:56:42 -04:00
1015 changed files with 114407 additions and 60 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"
+90
View File
@@ -0,0 +1,90 @@
name: CI
# CI runs once per merge, on main (AGENTS.md, "Workflow for every change"): pull requests are
# checked locally on the crates they touch and merged; a red main is fixed forward.
on:
push:
branches: [main]
# A merge that only touches prose starts no run. The generated pages are checked by the jobs
# (features.md by `cargo xtask ci`, oracle.md by the oracle job), so they still count.
# (Later patterns override earlier ones.)
paths:
- '**'
- '!**/*.md'
- '!docs/**'
- 'docs/features.md'
- 'docs/oracle.md'
schedule:
# Nightly: the heavy tests on main.
- cron: '23 3 * * *'
workflow_dispatch:
permissions:
contents: read
# A newer merge cancels the run still going for the one before it, so a burst of merges is tested
# once, at the tip. Nightly and manual runs are grouped apart and never cancelled by a merge.
concurrency:
group: ci-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: ${{ github.event_name == 'push' }}
env:
CARGO_TERM_COLOR: always
jobs:
ci:
name: cargo xtask ci
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- run: cargo xtask ci
heavy:
# The heavy verification and corpus tests (#[ignore]d) and the doc tests.
name: cargo xtask test --heavy
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- run: cargo xtask test --heavy
oracle:
# Verification and validation (docs/verification.md): every case against its reference and
# its fixture, and docs/oracle.md must be the table the cases produce. Cases marked `slow`
# run here too while the whole set stays within a few minutes; `--quick` skips them.
name: cargo xtask oracle
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
key: oracle
save-if: ${{ github.ref == 'refs/heads/main' }}
- run: cargo xtask oracle
- name: docs/oracle.md is current
run: git diff --exit-code docs/oracle.md
advisories:
name: Dependency advisories and sources
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-deny
run: cargo install cargo-deny --version 0.20.2 --locked
- run: cargo deny --locked check --warn unmaintained advisories bans sources
+9
View File
@@ -0,0 +1,9 @@
/target*/
/plan/
/dist/
/.agent-files/
/worktrees/
*~
.DS_Store
*.swp
__pycache__/
+151
View File
@@ -0,0 +1,151 @@
# FieldCraft — instructions for agents
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
pre-processing done today in Abaqus/CAE, Nastran pre-processors and LS-PrePost, and aims to beat
them on meshing robustness, speed, openness and agent control.
Siblings with the same conventions: `../solidcraft` (parametric CAD, Fusion-class), and the
storytold Craft apps (`storytold/cadcraft`, `vectorcraft`, `photocraft`, …). Code adapted from
them (MIT OR Apache-2.0) is noted in ATTRIBUTION.md.
## Product name
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.
2. `docs/architecture.md`: crates, layers, the meshing pipeline.
3. `docs/adr/`: library decisions (what we may depend on, what is rejected for licence reasons).
4. Meshing work: `docs/meshing/literature.md` (the papers each algorithm comes from).
## Never crash
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<T, E>`** through the crate's error type and `?`. An unfinished feature
returns an error ("not supported yet: …"); it never panics.
- **Input-derived numbers are hostile** (command parameters, model files, solver decks,
control-channel input): `get()` not `[i]`, finite checks, caps on sizes and loop counts.
- **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 `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`).
- Every production crate root carries
`#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]`.
## Non-negotiables
- **Independent.** Behaviour is designed from public documentation, published papers, general
CAE conventions and user feedback. Never copy another product's code, icons, artwork, help
text, element or material libraries, templates or examples.
- **No commercial CAE products.** Agents must not drive, script, automate or extract data from
commercial CAE or meshing products (Abaqus, MSC/NX/Simcenter Nastran, LS-DYNA, LS-PrePost,
HyperMesh, ANSA, Femap, Patran, ANSYS, …): no test data, measurements, decks, results or ids
produced with them. Deck formats come from the vendors' publicly available reference manuals;
we write our own descriptions and our own examples, never copy manual text or example decks.
- **Verification and validation (docs/verification.md).** Every solver capability is checked
three ways, each case in `tests/oracle/` tagged with its source: **theory** (closed-form
solutions, patch tests, mesh invariants), **experiment** (measurements published in papers and
reports) and **published benchmarks** (reference results published by benchmark bodies and by
solver vendors' benchmark and verification manuals, e.g. NAFEMS, Abaqus). Owner decision
2026-10-13: published vendor results may be used as reference values, cited per case. We never
run a commercial product, never copy its input files, text or figures: each case is our own
model built from the published description, and only the published numbers are used. Once our
result matches its reference within the stated tolerance, it is frozen as a **fixture**
(`fixture.json`), so any later change that moves it fails CI (fidelity never drifts silently).
- **Algorithms come from papers.** Meshing and solver methods are implemented from published
papers and theses, cited in the module docs and in `docs/meshing/literature.md`.
- **Never read, copy or link GPL/LGPL/AGPL code**: TetGen, CGAL, Gmsh, Netgen, CalculiX,
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.** 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`
enforces it). Screenshots of commercial software are never committed.
- **Test geometry is our own.** Meshing and solver test parts are built with SolidCraft and
committed as recipes (scripts or designs) that regenerate them; analytic shapes are generated in
code. No public CAD datasets (ABC, MAMBO, Thingi10K, GrabCAD) and nothing made in a commercial
CAD or CAE product.
- **Everything is a command.** User-visible behaviour = a command in `crates/engine/src/cmd/*`
(`CommandSpec`: our own dotted, lower-case id `<area>.<thing>[.<variant>]` such as
`mesh.node.add` or `mesh.tet.generate`, never another product's keyword; label; params doc;
JSON `run`) + tests. The UI, scripts, CLI and control channel all reach the same commands.
- **Programmatic calls never open dialogs.**
- **Layering** is enforced by `cargo xtask layers` (`docs/architecture.md`): L0 `model` → L1
`geometry`, `mesh`, `fem`, `decks`, `render` → L2 solvers (`solver`, `lbm`; `explicit` later) →
L3 `fsi` (later) → L4 `doc` → L5 `io` → L6 `engine` → L7 `ui-egui`, `mcp` → apps. Nothing below
L7 depends on egui/eframe/winit/wgpu/rfd. **Only
`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 `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
adaptive-precision predicates, never a bare floating-point sign.
- **Units.** The model is unit-consistent and never converts (the user picks e.g. mm, t, s, N,
MPa); geometry from SolidCraft is in millimetres. Z is up.
- **Rust only.** **Never break wasm**: everything below the apps builds for
`wasm32-unknown-unknown` (`cargo xtask wasm`, part of `cargo xtask ci`).
- **Workflow for every change** (this section overrides any other instruction an agent carries,
such as a parent repository's CLAUDE.md or a task runner's "run tests and linters" step):
1. Work in your own worktree on a branch, with your own `CARGO_TARGET_DIR`. While you work:
`cargo check` and the tests of the crates you touch (`cargo test -p <crate>`). Commit after
each working step (a layer that compiles with its tests passing), not one big commit per PR.
Every commit lands on `main` as it is, so each one must build and pass its tests and say
what it does: `git bisect` walks them one by one.
2. When the change is done: review it once with `/simplify`, then `cargo fmt --all` and
`cargo clippy -p <crate> --all-targets -- -D warnings` for each crate you touched, once.
Regenerate what your change affects: `docs/features.md` when commands change
(`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 <sha>`, then
`git rebase -i --autosquash origin/main` (`GIT_SEQUENCE_EDITOR=true` keeps it
non-interactive). A regenerated page goes into the commit that changed what it lists.
4. Rebase onto the latest `origin/main` (never merge `main` into a branch), rerun the tests of
the touched crates, push, open the PR and merge it yourself with a rebase merge, which keeps
`main` linear and every commit bisectable: `gh pr merge <N> --rebase --delete-branch`, run as
its own command. Never squash. Then remove your worktree and delete your local branch.
5. Never run the full gate locally: no `cargo xtask ci`, no `cargo clippy --workspace`, no
`cargo xtask wasm` or `oracle` unless step 2 calls for it. Never watch CI. GitHub CI runs
`cargo xtask ci` (fmt, assets, licences, layers, clippy -D warnings, tests, wasm), the
oracle, the heavy tests and the advisories once per merge on `main`; the coordinator
watches it and sends a failure back to the change that caused it, which is fixed forward
at once.
- **Keep the main gate fast** (under ~3 minutes on CI). A test that takes more than a few seconds
(convergence studies, the whole corpus, cross-checks between solvers) is
`#[ignore = "heavy: cargo xtask test --heavy (CI heavy job)"]`, with a fast representative
test left in the main gate. The `heavy` CI job runs `cargo xtask test --heavy` (every ignored
test, benchmarks included, and the doc tests) on every merge to `main`, nightly and on
demand (`gh workflow run CI --ref <branch>`). 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/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 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 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 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=<repo>/target`. Parallel agents: separate target dirs, one
worktree each.
## Roadmap
`ROADMAP.md` (committed) tracks milestones. Update it whenever a milestone task lands.
+180
View File
@@ -0,0 +1,180 @@
# Attribution
Every non-code asset in this repository (images, icons, fonts, example geometry) is listed here
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):** FieldCraft contains **no iconography, images, artwork, fonts, templates,
example decks or help text from commercial CAE products.** Every asset is original work by
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.
## Files
| File | Author | Source | Licence |
|---|---|---|---|
| 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
| File | Adapted from | Licence |
|---|---|---|
| xtask/src/layers.rs | SolidCraft `xtask/src/layers.rs` (itself adapted from CADCraft's xtask) | MIT OR Apache-2.0 |
| xtask/src/assets.rs, xtask/src/licences.rs, xtask/src/test.rs, xtask/src/main.rs | SolidCraft `xtask/src/` | MIT OR Apache-2.0 |
| clippy.toml, rustfmt.toml | SolidCraft | MIT OR Apache-2.0 |
| crates/geometry/src/tagged_mesh.rs (package and part reading, transforms, units, caps) | SolidCraft `crates/io/src/threemf.rs` | MIT OR Apache-2.0 |
| crates/render/src/camera.rs, crates/render/src/raster.rs | SolidCraft `crates/render/src/camera.rs`, `raster.rs` | MIT OR Apache-2.0 |
| 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/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 |
## Test oracles
Used only by tests (dev-dependencies, not shipped); no code from them is copied or adapted.
| Used in | Oracle | Licence |
|---|---|---|
| crates/mesh/src/predicates/tests.rs, crates/mesh/tests/predicates_bench.rs | `robust` (georust), a port of Shewchuk's public-domain `predicates.c` | MIT OR Apache-2.0 |
| crates/mesh/src/predicates/tests.rs, crates/mesh/src/predicates/dyadic.rs (tests) | `num-bigint`, `num-rational`, `num-traits` (exact rationals) | MIT OR Apache-2.0 |
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+14
View File
@@ -0,0 +1,14 @@
# Contributing
Thanks for helping. Read [AGENTS.md](AGENTS.md) first: it holds the rules every change follows,
whether a person or an agent writes it.
1. Branch from `main`, keep a pull request to one topic.
2. Run `cargo xtask ci` before pushing; CI runs the same gates.
3. User-visible behaviour is a command (`crates/engine/src/cmd/`) with tests, including hostile
parameters.
4. Meshing and solver algorithms cite the paper they come from in the module docs.
5. Never contribute code, text or data from GPL-family projects or commercial CAE products
(see [docs/adr/0001-libraries.md](docs/adr/0001-libraries.md)).
By contributing you agree your work is licensed under MIT OR Apache-2.0.
Generated
+5047
View File
File diff suppressed because it is too large. Load diff
+123
View File
@@ -0,0 +1,123 @@
[workspace]
resolver = "3"
members = ["crates/*", "apps/*", "xtask"]
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
authors = ["FieldCraft contributors"]
repository = "https://github.com/jyviko/fieldcraft"
[workspace.dependencies]
# internal
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"
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["float_roundtrip"] }
log = "0.4"
# 3MF packages (docs/formats/tagged-mesh.md): the same versions SolidCraft uses.
zip = { version = "3", default-features = false, features = ["deflate"] }
quick-xml = "0.41"
# content hashes and part checksums of .fieldcraft files (the version SolidCraft uses)
sha2 = "0.10"
# images (PNG snapshots and screenshots)
png = "0.18"
# SVG toolbar icons rasterised in the UI (no text, fonts or raster images in icons)
resvg = { version = "0.48", default-features = false }
# ui: the same egui/eframe/egui-wgpu line as SolidCraft
egui = "0.36"
eframe = { version = "0.36", default-features = false }
egui-wgpu = "0.36"
# Shared Craft-family egui components (icon buttons, tabs, widget palette); the same rev as
# VectorCraft. MIT OR Apache-2.0.
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675" }
# native file pickers (the desktop app only; the UI crate takes them as services)
rfd = "0.17"
# Geometry: SolidCraft's B-rep kernel (MIT OR Apache-2.0), pinned. Only crates/geometry uses it
# (plan/adr/0001-libraries.md).
solidcraft-geom = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" }
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 fieldcraft-geometry's
# `scripts` feature.
solidcraft-engine = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" }
[workspace.lints.rust]
unsafe_code = "forbid"
[workspace.lints.clippy]
dbg_macro = "warn"
todo = "warn"
too_many_arguments = "allow"
type_complexity = "allow"
# `!(x > tol)` is our idiom for "not a usable number" (NaN-safe).
neg_cmp_op_on_partial_ord = "allow"
[profile.dev.package."*"]
opt-level = 2
[profile.dev]
opt-level = 1
# The lattice Boltzmann kernels are compute loops over millions of cells; their verification
# runs (tests) need them optimised.
[profile.dev.package.fieldcraft-lbm]
opt-level = 3
# Tests and lints (`cargo xtask ci`, GitHub CI). Debug assertions are off because truck's
# debug-only checks reject valid geometry (SolidCraft builds its tests the same way).
[profile.ci]
inherits = "dev"
opt-level = 1
debug = "line-tables-only"
debug-assertions = false
overflow-checks = true
incremental = false
[profile.ci.package."*"]
opt-level = 2
[profile.ci.package.fieldcraft-lbm]
opt-level = 3
[profile.release]
opt-level = 3
lto = "thin"
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 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" }
truck-stepio = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" }
truck-meshalgo = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" }
matext4cgmath = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" }
# egui-wgpu with SolidCraft's fixes (vendor/egui-wgpu there): MSAA clamped to what the device
# supports and 1x on OpenGL adapters, so the requested 4x never fails pipeline validation at start.
egui-wgpu = { git = "https://github.com/bherbruck/solidcraft", rev = "de1479029dd6821cffd28b70688a58e7f61f0cfd" }
+177
View File
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+21
View File
@@ -0,0 +1,21 @@
MIT License
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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
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).
Third-party material
Software dependencies are listed in Cargo.lock; their licences are in THIRD-PARTY-LICENSES.txt
(`cargo xtask licences`). Geometry comes from SolidCraft (https://github.com/bherbruck/solidcraft,
MIT OR Apache-2.0), which builds on the truck crates (https://github.com/ricosjp/truck, Apache-2.0,
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.
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.
+76 -2
View File
@@ -1,3 +1,77 @@
# Issue media
# FieldCraft
Screenshots referenced from GitHub issues, kept off `main`. FieldCraft's own renders and captures (MIT OR Apache-2.0).
An open-source, clean-room CAE application in pure Rust: from a parametric SolidCraft part to a
verified finite element analysis, with tetrahedral and hexahedral meshing, its own implicit,
explicit, thermal and lattice Boltzmann solvers, and decks for Abaqus, Nastran and LS-DYNA. The
desktop app, the command line and AI agents (over MCP) drive the same commands.
**Status:** early but working end to end — open a part, mesh it, define the analysis, solve,
see the results. Hex meshing is the centre of gravity; the next large step is the nonlinear
implicit solver that contact, elastomers and composites need. See [ROADMAP.md](ROADMAP.md).
![An AI agent analyses an I-beam cantilever in FieldCraft over MCP: open, hex mesh, steel, clamp and tip load, solve, von Mises contours, the deformed shape and an orbit round it](docs/media/ibeam-analysis.gif)
An AI agent drives FieldCraft over [MCP](docs/mcp.md), each stage captioned with the tool it
calls: it opens an I-beam, hex meshes it, defines the material, the clamp and a 1000 N tip load,
solves, and shows the von Mises stress and the deformed shape (tip deflection 0.176 mm; beam
theory with shear 0.177 mm).
[MP4](docs/media/ibeam-analysis.mp4) · regenerated by [tools/demo-video](tools/demo-video/README.md).
## What it does today
- **Geometry in:** SolidCraft designs (`.solidcraft`) with persistent face, edge and body names,
and face-tagged 3MF meshes; surface checks (weld, manifold, orientation, self-intersection).
- **Meshing:** constrained Delaunay tetrahedra (tet4, tet10) with exact predicates and a
certified validity gate; structured hex by sweeping, revolving and mapping (hex8, hex20; 16 of
the 21 corpus parts mesh all-hex); 2D quad and tri meshes; quality metrics per element and set.
- **Elements:** solids (tet4, tet10, wedge6, hex8 with incompatible modes, hex20 full and
reduced, hybrid u–p for near-incompressible materials), plane stress, plane strain and
axisymmetric, trusses, Timoshenko beams, MITC3/MITC4 shells, springs, dashpots and point masses.
- **Model:** sets bound to geometry names, so they survive remeshing; isotropic and orthotropic
elasticity, J2 plasticity, Johnson–Cook damage, thermal properties; fixed, displacement,
symmetry and temperature conditions; forces, moments, pressure, gravity, centrifugal and heat
loads; reference points with kinematic and distributing couplings; undo, redo and a
text-diffable `.fieldcraft` file.
- **Solvers:** linear static and frequency (supernodal Cholesky, block Lanczos); steady and
transient heat transfer and coupled thermal stress; explicit dynamics with plasticity, damage,
erosion and rigid walls; D3Q19 lattice Boltzmann flow with forces on the part's faces. Results
never depend on the thread count.
- **Decks:** Abaqus, Nastran and LS-DYNA writers, each refusing by name what it cannot express.
- **Results:** contours of stress, strain and displacement with a legend, deformed shapes, mode
shapes, explicit frames, probes and reaction sums; the analysis setup drawn in the viewport.
- **Verification:** every capability is checked against closed forms and published benchmarks
(NAFEMS, MacNeal–Harder, Scordelis–Lo) in [`tests/oracle`](tests/oracle), with results frozen
so they cannot drift: [docs/oracle.md](docs/oracle.md).
- **Driving it:** a desktop app in the Craft family's layout, a CLI, a JSON control channel and an
MCP server; every capability is a command ([docs/features.md](docs/features.md)).
## Build
```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 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`.
## Documentation
- [Architecture](docs/architecture.md): crates, layers and the meshing pipeline
- [Element library](docs/elements.md) and [verification and validation](docs/verification.md)
- [Capability checklist](docs/parity.md) and [roadmap](ROADMAP.md)
- [Libraries and licences](docs/adr/0001-libraries.md)
- [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)
## License
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).
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.
+87
View File
@@ -0,0 +1,87 @@
# Roadmap
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
remeshing, automatic hex partitioning, undo, a text-diffable model and agent control.
The capability checklist is [docs/parity.md](docs/parity.md). Beyond the pre-processor and the
linear solver come our own explicit, CFD (lattice Boltzmann) and fluid–structure solvers (M10–M14;
methods and papers in [docs/solvers/literature.md](docs/solvers/literature.md)).
Order (revised 2026-10-13): one end-to-end slice first — *SolidCraft part → named faces → sets →
mesh → material and section → BCs and loads → static and frequency steps → Abaqus, Nastran and
LS-DYNA decks and our own solve → contour plot* — on tet meshes, so every hex mesher lands in a
pipeline that already produces solvable, verified models. Then hex: structured (sweep and mapped,
with partitions proposed automatically) before octree all-hex, because that is how analysts get
hex meshes today and it gives the best elements where stresses are read.
Revised 2026-10-14 after a gap review (below): the implicit side is linear today, and nearly
everything analysts ask for next — contact and friction, elastomers, viscoelasticity, creep,
adhesion, composite failure, tissues — needs a nonlinear implicit solver first. The nonlinear
core (M15) therefore comes right after automatic hex (M6) and before contact (M7). Every material
model is written once in `fieldcraft-fem` with its consistent tangent and serves the implicit and
explicit solvers alike; every coupled field (temperature, pressure, electric potential, pore
pressure) is a degree of freedom of the existing DOF framework, not a separate solver.
| 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 (`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, contact and friction | Needs M15. Part instances from SolidCraft occurrences; tie between non-matching meshes (mortar); implicit contact: surface-to-surface mortar and node-to-surface, penalty and augmented Lagrange, self-contact, automatic pair detection with a bounding-volume search shared with explicit contact; friction: Coulomb with stick–slip, static/kinetic, anisotropic, pressure-, rate- and temperature-dependent coefficients; contact output (pressure, slip, status); thermal contact conductance and frictional heating; wear (Archard); bolt pretension, connectors with behaviour (elastic, damped, stops, locks); amplitudes, multiple steps, restart; nonlinear steps in all three decks. Verified by Hertz (sphere and cylinder), the Cattaneo–Mindlin partial-slip problem, the sliding block, NAFEMS contact benchmarks | 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 — `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 (Smagorinsky, WALE) with wall models; thermal LBM and conjugate heat transfer with the structure's thermal solver; non-Newtonian viscosity (power-law, Carreau–Yasuda, Cross, Herschel–Bulkley, Bingham) and temperature-dependent viscosity; porous media (Brinkman, Darcy–Forchheimer); multiphase and free surface (M13); validation by the lid-driven cavity, backward-facing step, cylinder wake (Strouhal) and turbulent channel; 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 |
| M15 Nonlinear implicit core | Newton–Raphson with line search, automatic increments and cutbacks, convergence on force, displacement and energy; geometric nonlinearity (total and updated Lagrangian, large rotations for shells and beams, follower pressure); every `fem` material behind one interface with its consistent tangent (plasticity, damage and thermal strain from the explicit solver reused implicitly); arc-length (Riks) for snap-through; linear buckling (eigen) with preload stress stiffening, and imperfections from buckling modes; nonlinear stabilisation; restart. Verified by the elastica, Lee's frame, the snap-through arch, the hinged cylindrical shell and plastic collapse loads | planned |
| M16 Structural dynamics | Implicit transient (Newmark, HHT-α, generalised-α); modal superposition transient; steady-state harmonic (direct and modal), with Rayleigh, modal, structural and material damping; random vibration from PSDs and response spectrum (SRSS, CQC); complex eigenvalues with damping; rotating machinery (Campbell diagrams, gyroscopic terms); substructures (Craig–Bampton) | planned |
| M17 Constitutive library and material library | Elastic: transversely isotropic, fully anisotropic (21 constants), temperature-dependent. Elastomers: hyperelastic neo-Hookean, Mooney–Rivlin, Yeoh, Ogden, Arruda–Boyce, Gent, Marlow, with Mullins softening and finite-strain viscoelasticity (Prony series; parallel networks after Bergström–Boyce), foams. Viscoelasticity: small- and finite-strain Prony series, time–temperature superposition (WLF, Arrhenius), storage and loss moduli for harmonic steps. Viscoplasticity and creep: Norton power law, time and strain hardening, Perzyna, Anand, Chaboche combined hardening. Plasticity: Hill anisotropic, Drucker–Prager, Mohr–Coulomb, Cam-clay, crushable foam, concrete damaged plasticity, Gurson. Fluids: Newtonian and non-Newtonian viscosity shared with M12. Material library: curated, cited data sets (steels, aluminium and titanium alloys, polymers, elastomers, composites, tissues) with units and temperature dependence; calibration from test data (uniaxial, biaxial, shear, relaxation, DMA) by least squares with Drucker stability checks; user materials as a safe Rust trait | planned |
| M18 Composites and anisotropy | Material orientations (rectangular, cylindrical, spherical, from geometry edges and faces, per-element fields, draping of fibres over curved parts); layups on shells and continuum shells, ply stacks on swept hex; classical lamination theory checks; ply stresses and strains through the thickness; failure indices (Tsai–Wu, Tsai–Hill, Hashin, Puck, LaRC) and progressive damage; homogenisation (rule of mixtures, Mori–Tanaka, RVEs with periodic boundary conditions); delamination through M20's cohesive zones. Verified by laminate closed forms and the NAFEMS composite benchmarks | planned |
| M19 Coupled fields | On the DOF framework, each a degree of freedom beside the displacements: piezoelectricity (electric potential, d/e coupling and permittivity matrices, voltage and charge conditions, resonance and impedance through M16 harmonic steps); poroelasticity (Biot, u–p, Darcy flow, consolidation; Terzaghi and Mandel verification; soils and biphasic tissue); electro-thermal (Joule heating) and thermo-electro-mechanical coupling; acoustics (Helmholtz elements, structure–acoustic coupling, absorbing boundaries); low-frequency electromagnetics later | planned |
| M20 Damage, fracture, fatigue and adhesion | Continuum damage beyond Johnson–Cook (Lemaitre, ductile and shear criteria) for implicit and explicit; fracture mechanics (J-integral and stress intensity factors, crack-front meshes from the hex mesher; XFEM later); fatigue (S–N and ε–N with mean-stress corrections, rainflow counting, Paris crack growth); adhesion and debonding by cohesive zones (bilinear and exponential traction–separation, mixed mode with Benzeggagh–Kenane) as cohesive elements and cohesive contact; adhesive joints. Verified by DCB, ENF and MMB delamination tests and published fatigue data | planned |
| M21 Biomechanics and biomaterials | Soft tissue: Holzapfel–Gasser–Ogden fibre-reinforced hyperelasticity with dispersion, Fung, transversely isotropic models; biphasic and poro-hyperelastic cartilage (M19); active muscle (Hill-type); hydrogel swelling. Bone: density-dependent moduli from CT data, strain-energy-density remodelling (Huiskes, Beaupré). Growth and remodelling by kinematic growth (F = Fe·Fg). Implants and biomaterials (titanium, PEEK, UHMWPE) from the M17 library. Verified against published experiments with their tolerances | planned |
| M22 Design studies | Parameters of the SolidCraft design driven from FieldCraft: parametric sweeps, design of experiments, response surfaces and sensitivities; topology optimisation (SIMP with filters, manufacturing constraints); size and shape optimisation; submodelling (driven boundaries from a global model); uncertainty quantification | planned |
| M23 Wave propagation and ultrasound | Elastic, acoustic and coupled waves from the explicit solver's 1D bar today to guided and ultrasonic waves, by two methods. Finite differences first, for fast 2D (then 3D) ultrasound on regular grids: staggered-grid velocity–stress FDTD (Virieux) and the elastodynamic finite integration technique for solids, the rotated staggered grid for anisotropic media, a k-space pseudospectral scheme for weakly heterogeneous tissue and water, all on grids voxelised from the face-tagged geometry by the LBM's exact voxeliser, with heterogeneous, layered and fluid–solid media, CPML boundaries, threads and the GPU backend; staircased curved boundaries scatter spuriously, so the FD grid is checked against the finite elements on curved parts. Finite elements for curved geometry and coupling with structures: spectral elements (high-order Gauss–Lobatto–Legendre hexes with diagonal mass) on the hex meshes, and a mesh check in elements per wavelength with the dispersion error it implies; absorbing boundaries (convolutional PML, absorbing layers with increasing damping) and infinite media; tone-burst sources (Hann-windowed cycles), point, line and surface excitation, and piezoelectric transducers (M19); anisotropic media (composites, crystals) and viscoelastic, power-law-in-frequency attenuation; fluid–solid interfaces and fluid-loaded plates; ultrasound in water and tissue, nonlinear acoustics (Westervelt, KZK) for focused ultrasound. Guided waves: dispersion curves by semi-analytical finite elements (SAFE) for plates, pipes, rails and layered and anisotropic laminates — Lamb, Rayleigh, Love and SH modes — with phase and group velocities and mode shapes; mode-selective excitation. Output: A-scans and B-scans at sensors, wavefield snapshots, 2D FFT (frequency–wavenumber) for mode identification, and time–frequency diagrams of any signal — short-time Fourier spectrograms, continuous wavelet transforms (Morlet), reassigned spectrograms and smoothed pseudo Wigner–Ville — with the SAFE or Rayleigh–Lamb group-velocity curves overlaid as arrival times, and group velocities extracted from the ridges. GPU paths (M12's backend) for long runs. Verified by Lamb's problem (point load on a half-space), Rayleigh-wave speed, Rayleigh–Lamb dispersion, Love waves in a layer over a half-space, reflection and transmission at interfaces, and published guided-wave measurements | 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 |
| Continuous: performance | CPU first, deterministic: results never depend on the thread count (fixed partitions, sums in a fixed order; checked by hashing results on 1, 2 and 7 threads). Supernodal left-looking Cholesky (11–15× faster than the column solver on one thread), parallel element matrices, stresses and Lanczos products, explicit element updates and LBM cell blocks on rayon threads (done — `fieldcraft-parallel`, `threads` on the solve commands, `FIELDCRAFT_THREADS`); parallel forward/back substitution and explicit tet4 assembly; GPU paths later, within a stated tolerance of the CPU | started |
| Continuous: desktop and agents | Desktop app (viewport, model tree, commands, control channel, the open design drawn, `ui.capture` of the whole window offscreen: done; toolbar of every capability from the catalogue, ready or coming soon with its milestone, icons drawn with VectorCraft, docs/features.md: done; the Craft-family shell — title bar, workspaces and ribbon drop-downs, browser, context panel, command search, journal timeline, light and dark themes — native file pickers with background opening, drag and drop and recent files, samples and one-click Solve: done; MCP server over stdio, headless or bridged to the app, with read and write roots, docs/mcp.md: done; caps labels in Inter SemiBold; the README's analysis video recorded through MCP by `tools/demo-video`: done; support, load, reference-point and coupling symbols in the viewport and headless renders, with their browser toggles: done; a camera parameter for screenshots, a ribbon that follows the command being run: in progress), browser build, command schema as the public API | started |
## Next
In progress, in this order of landing:
1. Tet quality: sliver repair and Delaunay refinement to a size field, so curved and filleted parts
(the filleted bracket) solve on tets (M2).
2. Octree all-hex for any part (M6), the project's main meshing goal.
3. The analysis video regenerated with the setup's symbols (done: support, load, reference-point
and coupling symbols in the viewport and headless renders), captions and an orbit (screenshot
camera and the ribbon following the command: done).
4. A Scordelis–Lo refinement study (32×32×2, 64×64×4, hex20 full and reduced) separating
discretisation error from the solid-versus-shell model difference (−0.33 % today).
5. The nonlinear implicit core (M15), the gate for contact (M7), elastomers and viscoelasticity
(M17), composite failure (M18), adhesion (M20) and tissues (M21).
## Gap review (2026-10-14)
What exists and what is missing, by area; each gap is in a milestone above.
| Area | Today | Missing |
|---|---|---|
| Solution procedures | Linear static, frequency (Lanczos), steady and transient heat, coupled temperature–displacement, explicit dynamics, LBM flow | Nonlinear static and geometric nonlinearity, arc-length, buckling (M15); implicit, harmonic, random and spectrum dynamics (M16) |
| Contact, friction, adhesion | Explicit planar rigid walls (frictionless); couplings, springs, dashpots | Implicit and explicit deformable contact, tie, friction laws, thermal contact, wear (M7); cohesive adhesion and debonding (M20) |
| Materials | Isotropic and orthotropic elasticity, density, thermal, J2 plasticity (bilinear, table, Johnson–Cook), Johnson–Cook damage and erosion, Newtonian fluids | Hyperelastic and elastomers, viscoelasticity, viscoplasticity and creep, anisotropic and pressure-dependent plasticity, non-Newtonian viscosity, a cited material library and calibration (M17) |
| Composites and anisotropy | Orthotropic constants | Orientations, layups, ply results, failure criteria, homogenisation (M18) |
| Coupled fields | Thermal–structural (one way and coupled) | Piezoelectric, poroelastic, electro-thermal, acoustics (M19) |
| Failure | Johnson–Cook damage and element erosion (explicit) | Fracture mechanics, fatigue, cohesive zones, composite progressive damage (M18, M20) |
| Biomechanics | — | Tissue models, cartilage, muscle, bone remodelling, growth (M21) |
| Fluid dynamics | LBM D3Q19 TRT, walls, inlets/outlets, obstacle forces | Turbulence with wall models, thermal and conjugate heat transfer, non-Newtonian, porous media, multiphase, GPU (M12, M13) |
| Wave propagation | One-dimensional elastic waves and impact in the explicit solver (bar cases in the oracle) | A 2D/3D finite-difference ultrasound solver (velocity–stress FDTD, EFIT, k-space pseudospectral) on voxelised geometry, time–frequency diagrams (spectrogram, wavelet, Wigner–Ville) with dispersion overlays, spectral elements, absorbing boundaries, tone-burst and transducer sources, attenuation, Lamb/Rayleigh/Love guided waves and SAFE dispersion curves, ultrasound in fluids and tissue, nonlinear acoustics, A/B-scans and frequency–wavenumber analysis (M23) |
| Design | — | Parametric studies on SolidCraft parameters, optimisation, submodelling (M22) |
+11
View File
@@ -0,0 +1,11 @@
# Security policy
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/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.
Reports are especially useful for opening untrusted models, decks or scripts (crashes, runaway
memory or CPU, path tricks) and for the control channel once it exists.
File diff suppressed because it is too large. Load diff
+15
View File
@@ -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 }
+156
View File
@@ -0,0 +1,156 @@
//! fieldcraft-cli commands
//! fieldcraft-cli run <script.json>
//! 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 fieldcraft_engine::Session;
use serde_json::json;
const USAGE: &str = "\
usage:
fieldcraft-cli commands list every command with its parameters
fieldcraft-cli run <script.json> run a script (a JSON array of {\"command\", \"params\"})
and print the model summary
fieldcraft-cli features print docs/features.md (every capability, ready or
coming soon) from the capability catalogue
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 (`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,
the access a root left out is not granted)
";
/// Largest script accepted (bytes).
const MAX_SCRIPT_BYTES: u64 = 256 * 1024 * 1024;
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
let result = match args.first().map(String::as_str) {
Some("commands") => {
for c in fieldcraft_engine::commands() {
println!("{:<24} {:<18} {}", c.id, c.label, c.params);
}
Ok(())
}
Some("features") => {
print!("{}", fieldcraft_engine::catalogue::features_page());
Ok(())
}
Some("mcp") => mcp(args.get(1..).unwrap_or(&[])),
Some("run") => args.get(1).ok_or_else(|| format!("run needs a script\n\n{USAGE}")).and_then(|p| run(p)),
Some("-h" | "--help" | "help") | None => {
print!("{USAGE}");
Ok(())
}
Some(other) => Err(format!("unknown command `{other}`\n\n{USAGE}")),
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
}
}
/// The `mcp` options: each given once, as `--flag value` or `--flag=value`.
fn mcp_options(args: &[String]) -> Result<fieldcraft_mcp::Options, String> {
let mut connect = None;
let mut headless = false;
let mut read = None;
let mut write = None;
let mut it = args.iter();
while let Some(arg) = it.next() {
let (flag, inline) = match arg.split_once('=') {
Some((f, v)) if f.starts_with("--") => (f, Some(v.to_string())),
_ => (arg.as_str(), None),
};
let mut value = || inline.clone().or_else(|| it.next().cloned()).filter(|v| !v.is_empty()).ok_or_else(|| format!("{flag} needs a value"));
let slot = match flag {
"--headless" if inline.is_none() => {
if std::mem::replace(&mut headless, true) {
return Err("--headless is given twice".into());
}
continue;
}
"--connect" => &mut connect,
"--automation-read-root" => &mut read,
"--automation-write-root" => &mut write,
other => return Err(format!("mcp: unknown option `{other}`\n\n{USAGE}")),
};
if slot.replace(value()?).is_some() {
return Err(format!("{flag} is given twice"));
}
}
if headless && connect.is_some() {
return Err("--connect and --headless exclude each other".into());
}
let roots = match (&read, &write) {
(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(fieldcraft_mcp::Options { connect, roots })
}
fn mcp(args: &[String]) -> Result<(), String> {
let (backend, note) = fieldcraft_mcp::backend(mcp_options(args)?)?;
if let Some(note) = note {
eprintln!("fieldcraft-cli mcp: {note}");
}
let mut server = fieldcraft_mcp::Server::new(backend);
server.serve(std::io::stdin().lock(), std::io::stdout().lock()).map_err(|e| format!("mcp: {e}"))
}
fn run(path: &str) -> Result<(), String> {
let size = std::fs::metadata(path).map_err(|e| format!("{path}: {e}"))?.len();
if size > MAX_SCRIPT_BYTES {
return Err(format!("{path}: larger than {} MB", MAX_SCRIPT_BYTES / (1024 * 1024)));
}
let text = std::fs::read_to_string(path).map_err(|e| format!("{path}: {e}"))?;
let script: serde_json::Value = serde_json::from_str(&text).map_err(|e| format!("{path}: {e}"))?;
let mut session = Session::default();
session.run_script(&script).map_err(|e| e.to_string())?;
let info = session.execute("model.info", &json!({})).map_err(|e| e.to_string())?;
println!("{}", serde_json::to_string_pretty(&info).map_err(|e| e.to_string())?);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn opts(args: &[&str]) -> Result<fieldcraft_mcp::Options, String> {
mcp_options(&args.iter().map(|s| s.to_string()).collect::<Vec<_>>())
}
#[test]
fn mcp_options_are_checked() {
let dir = std::env::temp_dir();
let d = dir.to_string_lossy();
assert!(opts(&[]).unwrap().connect.is_none());
assert_eq!(opts(&["--connect", "127.0.0.1:7070"]).unwrap().connect.as_deref(), Some("127.0.0.1:7070"));
assert_eq!(opts(&["--connect=127.0.0.1:7070"]).unwrap().connect.as_deref(), Some("127.0.0.1:7070"));
assert!(opts(&["--automation-read-root", &d]).unwrap().roots.is_confined());
assert!(opts(&[&format!("--automation-write-root={d}")]).unwrap().roots.is_confined());
for bad in [
vec!["--connect"],
vec!["--connect", "a", "--connect", "b"],
vec!["--headless", "--headless"],
vec!["--headless", "--connect", "a"],
vec!["--automation-read-root", "/no/such/folder"],
vec!["--automation-read-root="],
vec!["--nope"],
vec!["--headless=yes"],
] {
assert!(opts(&bad).is_err(), "{bad:?}");
}
}
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "fieldcraft"
description = "FieldCraft desktop app"
version.workspace = true
edition.workspace = true
license.workspace = true
publish = false
[lints]
workspace = true
[dependencies]
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 }
serde_json = { workspace = true }
log = { workspace = true }
+161
View File
@@ -0,0 +1,161 @@
//! 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
//! (`fieldcraft_ui_egui::control`).
//!
//! Adapted from SolidCraft's `apps/solidcraft/src/control_server.rs` (MIT OR Apache-2.0), see
//! ATTRIBUTION.md.
use std::io::{BufRead, BufReader, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::Duration;
use fieldcraft_ui_egui::ControlRequest;
use serde_json::{Value, json};
/// Longest request line (bytes).
const MAX_LINE: usize = 16 * 1024 * 1024;
/// Connections served at once. The channel has no authentication beyond being bound to
/// loopback, so a flood must not be able to spawn threads without end.
const MAX_CONNECTIONS: usize = 16;
/// Lines that are not JSON before the connection is closed, so garbage cannot loop forever.
const MAX_BAD_LINES: usize = 8;
/// How long a request waits for the UI thread.
const REPLY_TIMEOUT: Duration = Duration::from_secs(60);
static OPEN: AtomicUsize = AtomicUsize::new(0);
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
let (tx, rx) = channel::<ControlRequest>();
let listener = match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => l,
Err(e) => {
eprintln!("fieldcraft: control server failed to bind 127.0.0.1:{port}: {e}");
return rx;
}
};
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 {
OPEN.fetch_sub(1, Ordering::Relaxed);
let mut refused = stream;
let _ = writeln!(refused, "{}", json!({"ok": false, "error": "too many connections"}));
continue;
}
let tx = tx.clone();
let ctx = ctx.clone();
std::thread::spawn(move || {
serve(stream, tx, ctx);
OPEN.fetch_sub(1, Ordering::Relaxed);
});
}
});
rx
}
/// One line without its newline, or `None` at the end of the stream. A client that sends
/// gigabytes with no newline would otherwise make this thread allocate until the process is
/// killed, so the line is refused as soon as it passes `max`.
fn read_line(r: &mut impl BufRead, max: usize) -> std::io::Result<Option<Vec<u8>>> {
let mut line = Vec::new();
loop {
let (done, used) = {
let available = r.fill_buf()?;
if available.is_empty() {
return Ok((!line.is_empty()).then_some(line));
}
match available.iter().position(|b| *b == b'\n') {
Some(i) => {
line.extend_from_slice(available.get(..i).unwrap_or(&[]));
(true, i + 1)
}
None => {
line.extend_from_slice(available);
(false, available.len())
}
}
};
r.consume(used);
if line.len() > max {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "request too large"));
}
if done {
return Ok(Some(line));
}
}
}
fn serve(stream: TcpStream, tx: Sender<ControlRequest>, ctx: egui::Context) {
let Ok(read) = stream.try_clone() else { return };
let mut out = stream;
let mut reader = BufReader::new(read);
let mut bad = 0usize;
loop {
let line = match read_line(&mut reader, MAX_LINE) {
Ok(Some(l)) => String::from_utf8_lossy(&l).into_owned(),
Ok(None) => break,
Err(e) => {
let _ = writeln!(out, "{}", json!({"ok": false, "error": e.to_string()}));
break;
}
};
if line.trim().is_empty() {
continue;
}
let reply = match serde_json::from_str::<Value>(&line) {
Ok(msg) => {
let id = msg.get("id").cloned().unwrap_or(Value::Null);
let method = msg.get("method").and_then(Value::as_str).unwrap_or("").to_string();
let params = msg.get("params").cloned().unwrap_or(json!({}));
let (req, rrx) = ControlRequest::new(method, params);
if tx.send(req).is_err() {
break;
}
ctx.request_repaint();
let mut r = rrx.recv_timeout(REPLY_TIMEOUT).unwrap_or_else(|_| json!({"ok": false, "error": "timeout"}));
if let Some(o) = r.as_object_mut() {
o.insert("id".into(), id);
}
r
}
Err(e) => {
bad += 1;
json!({"ok": false, "error": format!("bad JSON: {e}")})
}
};
if writeln!(out, "{reply}").is_err() || bad >= MAX_BAD_LINES {
break;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// A request longer than the cap is refused instead of allocated.
#[test]
fn lines_past_the_cap_are_refused() {
let flood = vec![b'A'; MAX_LINE + 2];
let mut r = BufReader::new(flood.as_slice());
let e = read_line(&mut r, MAX_LINE).unwrap_err();
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
}
/// Lines split on newlines, blank lines kept, no trailing newline needed; a line spanning
/// several reads arrives whole.
#[test]
fn lines_split_on_newlines() {
let text = b"{\"a\":1}\n\n{\"b\":2}\nlast".to_vec();
let mut r = BufReader::with_capacity(3, text.as_slice());
let mut got = Vec::new();
while let Some(line) = read_line(&mut r, MAX_LINE).unwrap() {
got.push(String::from_utf8(line).unwrap());
}
assert_eq!(got, vec!["{\"a\":1}", "", "{\"b\":2}", "last"]);
let mut r = BufReader::with_capacity(4, b"0123456789\n".as_slice());
assert!(read_line(&mut r, 5).is_err());
}
}
+232
View File
@@ -0,0 +1,232 @@
//! FieldCraft desktop app.
//!
//! 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 (`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
//! `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 fieldcraft_engine::Session;
use fieldcraft_engine::autosave::{self, Autosaver};
use fieldcraft_ui_egui::{Filter, ModelApp, Services};
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");
const MSAA: u16 = 4;
/// The window and Dock icon: the 256 px export of the application icon (assets/app-icon).
const ICON_PNG: &[u8] = include_bytes!("../../../assets/app-icon/icon.iconset/icon_128x128@2x.png");
/// The application icon for the window; eframe's default icon if the PNG cannot be decoded.
fn app_icon() -> egui::IconData {
eframe::icon_data::from_png_bytes(ICON_PNG).unwrap_or_else(|e| {
log::warn!("app icon: {e}");
egui::IconData::default()
})
}
/// How often unsaved changes are autosaved for recovery (seconds).
const AUTOSAVE_SECONDS: f64 = 30.0;
struct App {
app: ModelApp,
/// Autosave for crash recovery (`None` when the recovery folder is unavailable).
autosaver: Option<Autosaver>,
/// App time of the last autosave check.
autosaved_at: f64,
}
impl App {
fn autosave(&mut self) {
if let Some(a) = &mut self.autosaver
&& let Err(e) = a.save(&self.app.session)
{
log::warn!("autosave: {e}");
}
}
}
impl eframe::App for App {
fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.app.logic(ctx);
if self.app.now - self.autosaved_at >= AUTOSAVE_SECONDS {
self.autosaved_at = self.app.now;
self.autosave();
}
if self.autosaver.is_some() {
ctx.request_repaint_after(std::time::Duration::from_secs_f64(AUTOSAVE_SECONDS));
}
}
fn on_exit(&mut self) {
if let Some(a) = self.autosaver.take()
&& let Err(e) = a.close(&self.app.session)
{
log::warn!("autosave: {e}");
}
}
fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
self.app.raw_input_hook(raw);
}
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
self.app.ui(ui);
}
}
/// Native file pickers and the recent-files file, for the UI.
fn services() -> Services {
let dialog = |filters: &[Filter]| filters.iter().fold(rfd::FileDialog::new(), |d, f| d.add_filter(f.name, f.extensions));
Services {
pick_open: Some(Box::new(move |filters: &[Filter]| dialog(filters).pick_file().map(|p| p.to_string_lossy().to_string()))),
pick_save: Some(Box::new(move |name: &str, filters: &[Filter]| {
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: fieldcraft_io::recovery::default_dir().and_then(|d| d.parent().map(|p| p.join("recent.json"))),
}
}
/// Command-line options.
#[derive(Debug, Default, PartialEq)]
struct Args {
/// The sample to open (`--sample [NAME]`).
sample: Option<String>,
control: Option<u16>,
/// Files to open (models, designs, tagged meshes).
files: Vec<String>,
}
fn parse_args(args: impl IntoIterator<Item = String>, env_port: Option<String>) -> Result<Option<Args>, String> {
let mut out = Args { control: env_port.and_then(|p| p.trim().parse().ok()), ..Default::default() };
let mut args = args.into_iter().peekable();
while let Some(a) = args.next() {
match a.as_str() {
"--sample" => {
// An optional sample name follows.
let name = args.peek().filter(|a| fieldcraft_engine::sample::find(a).is_some()).cloned();
if name.is_some() {
args.next();
}
out.sample = Some(name.unwrap_or_else(|| "parts".into()));
}
"--control" => {
let port = args.next().ok_or("--control needs a port")?;
out.control = Some(port.parse().map_err(|_| format!("--control: `{port}` is not a port number"))?);
}
"--version" | "-V" => {
println!("fieldcraft {}", fieldcraft_ui_egui::VERSION);
return Ok(None);
}
"--help" | "-h" => {
println!("{USAGE}");
return Ok(None);
}
other if other.starts_with('-') => return Err(format!("unknown argument `{other}`\n{USAGE}")),
file => out.files.push(file.to_string()),
}
}
Ok(Some(out))
}
fn main() -> eframe::Result {
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!("fieldcraft: {e}");
std::process::exit(2);
}
};
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("FieldCraft")
.with_icon(app_icon())
.with_inner_size([1400.0, 900.0])
.with_min_inner_size([960.0, 600.0])
.with_drag_and_drop(true)
// The app bar is the title bar (as in the other Craft apps): on macOS under the
// traffic lights, elsewhere with its own caption buttons and resize zones.
.with_decorations(!CUSTOM_TITLEBAR)
.with_fullsize_content_view(true)
.with_titlebar_shown(false)
.with_title_shown(false),
depth_buffer: DEPTH_BITS,
multisampling: MSAA,
..Default::default()
};
eframe::run_native(
"FieldCraft",
options,
Box::new(move |cc| {
let mut app = ModelApp::new(Session::default()).with_services(services());
app.custom_titlebar = CUSTOM_TITLEBAR;
app.integrated_titlebar = cfg!(target_os = "macos");
if let Some(rs) = &cc.wgpu_render_state {
let info = rs.adapter.get_info();
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 {
app = app.with_control(control_server::start(port, cc.egui_ctx.clone()));
}
if let Some(name) = &args.sample {
app.open_sample(name);
}
for f in &args.files {
app.open_path(f);
}
let autosaver = autosave::default_dir().and_then(|dir| {
let listed = app.session.execute("file.recovery.list", &serde_json::json!({"dir": dir}));
let recoverable = listed.ok().and_then(|v| v.as_array().map(Vec::len)).unwrap_or(0);
if recoverable > 0 {
app.set_status(format!("{recoverable} autosaved model(s) can be recovered: File › Recover…"), false);
}
Autosaver::new(&dir).map_err(|e| log::warn!("autosave unavailable: {e}")).ok()
});
Ok(Box::new(App { app, autosaver, autosaved_at: 0.0 }))
}),
)
}
#[cfg(test)]
mod tests {
#[test]
fn the_app_icon_decodes() {
let icon = super::app_icon();
assert_eq!((icon.width, icon.height), (256, 256));
assert_eq!(icon.rgba.len(), 256 * 256 * 4);
}
use super::*;
fn parse(a: &[&str], env: Option<&str>) -> Result<Option<Args>, String> {
parse_args(a.iter().map(|s| s.to_string()), env.map(str::to_string))
}
#[test]
fn arguments() {
assert_eq!(parse(&[], None), Ok(Some(Args::default())));
assert_eq!(
parse(&["--sample", "--control", "7070"], None),
Ok(Some(Args { sample: Some("parts".into()), control: Some(7070), files: vec![] }))
);
assert_eq!(parse(&[], Some("9000")), Ok(Some(Args { control: Some(9000), ..Default::default() })));
assert_eq!(parse(&[], Some("nope")), Ok(Some(Args::default())));
assert!(parse(&["--control"], None).is_err());
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.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));
}
}
Binary file not shown.
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<clipPath id="clip-path-1">
<path d="M320 100 L704 100 C854 100 924 170 924 320 L924 704 C924 854 854 924 704 924 L320 924 C170 924 100 854 100 704 L100 320 C100 170 170 100 320 100 Z"/>
</clipPath>
<radialGradient id="radial-gradient-1" cx="0" cy="0" r="578" gradientTransform="matrix(1 0 0 0.5 512 720)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#4f8a82"/>
<stop offset="0.3" stop-color="#4f8a82" stop-opacity="0.9"/>
<stop offset="0.65" stop-color="#4f8a82" stop-opacity="0.35"/>
<stop offset="1" stop-color="#4f8a82" stop-opacity="0"/>
</radialGradient>
</defs>
<title>FieldCraft icon floor grid (Dark)</title>
<g id="Floor_grid" data-name="Floor grid" clip-path="url(#clip-path-1)">
<path id="floor_grid" data-name="floor grid" d="M154.62 100 L100 131.53 M869.38 100 L924 131.53 M368.82 100 L100 255.2 M655.18 100 L924 255.2 M583.01 100 L100 378.87 M440.99 100 L924 378.87 M797.21 100 L100 502.53 M226.79 100 L924 502.53 M924 150.47 L100 626.2 M100 150.47 L924 626.2 M924 274.13 L100 749.87 M100 274.13 L924 749.87 M924 397.8 L100 873.53 M100 397.8 L924 873.53 M924 521.47 L226.79 924 M100 521.47 L797.21 924 M924 645.13 L440.99 924 M100 645.13 L583.01 924 M924 768.8 L655.18 924 M100 768.8 L368.82 924 M924 892.47 L869.38 924 M100 892.47 L154.62 924" fill="none" stroke="url(#radial-gradient-1)" stroke-width="3" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<clipPath id="clip-path-1">
<path d="M320 100 L704 100 C854 100 924 170 924 320 L924 704 C924 854 854 924 704 924 L320 924 C170 924 100 854 100 704 L100 320 C100 170 170 100 320 100 Z"/>
</clipPath>
<radialGradient id="radial-gradient-1" cx="0" cy="0" r="578" gradientTransform="matrix(1 0 0 0.5 512 720)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#7fa8a2"/>
<stop offset="0.3" stop-color="#7fa8a2" stop-opacity="0.9"/>
<stop offset="0.65" stop-color="#7fa8a2" stop-opacity="0.35"/>
<stop offset="1" stop-color="#7fa8a2" stop-opacity="0"/>
</radialGradient>
</defs>
<title>FieldCraft icon floor grid</title>
<g id="Floor_grid" data-name="Floor grid" clip-path="url(#clip-path-1)">
<path id="floor_grid" data-name="floor grid" d="M154.62 100 L100 131.53 M869.38 100 L924 131.53 M368.82 100 L100 255.2 M655.18 100 L924 255.2 M583.01 100 L100 378.87 M440.99 100 L924 378.87 M797.21 100 L100 502.53 M226.79 100 L924 502.53 M924 150.47 L100 626.2 M100 150.47 L924 626.2 M924 274.13 L100 749.87 M100 274.13 L924 749.87 M924 397.8 L100 873.53 M100 397.8 L924 873.53 M924 521.47 L226.79 924 M100 521.47 L797.21 924 M924 645.13 L440.99 924 M100 645.13 L583.01 924 M924 768.8 L655.18 924 M100 768.8 L368.82 924 M924 892.47 L869.38 924 M100 892.47 L154.62 924" fill="none" stroke="url(#radial-gradient-1)" stroke-width="3" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="linear-gradient-1" x1="512" y1="141" x2="512" y2="376.622" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#86e3d3"/>
<stop offset="1" stop-color="#4cc4b1"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="512" y1="457.259" x2="512" y2="755.042" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#86e3d3"/>
<stop offset="1" stop-color="#4cc4b1"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="512" y1="209.598" x2="512" y2="624.284" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#20b39f"/>
<stop offset="1" stop-color="#118c7c"/>
</linearGradient>
<linearGradient id="linear-gradient-4" x1="512" y1="588.018" x2="512" y2="878.381" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#20b39f"/>
<stop offset="1" stop-color="#118c7c"/>
</linearGradient>
<linearGradient id="linear-gradient-5" x1="512" y1="335.738" x2="512" y2="878.381" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0f786d"/>
<stop offset="1" stop-color="#0a544e"/>
</linearGradient>
<linearGradient id="linear-gradient-6" x1="512" y1="558.616" x2="512" y2="658.304" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0b665e"/>
<stop offset="1" stop-color="#04302c"/>
</linearGradient>
</defs>
<title>FieldCraft icon part</title>
<g id="Part">
<path id="wall_top" data-name="wall top" d="M512 141 L825.3 321.88 L730.48 376.62 L417.19 195.74 Z" fill="url(#linear-gradient-1)"/>
<path id="base_top" data-name="base top" d="M401.19 457.26 L714.48 638.14 L512 755.04 L198.7 574.16 Z M521.64 643.71 C555.35 624.24 555.35 592.68 521.64 573.22 C487.92 553.75 433.26 553.75 399.55 573.22 C365.83 592.68 365.83 624.24 399.55 643.71 C433.26 663.17 487.92 663.17 521.64 643.71 Z" fill="url(#linear-gradient-2)"/>
<path id="wall_front" data-name="wall front" d="M409.19 209.6 L722.48 390.48 L722.48 624.28 L409.19 443.4 Z" fill="url(#linear-gradient-3)"/>
<path id="base_front" data-name="base front" d="M190.7 588.02 L504 768.9 L504 878.38 L190.7 697.5 Z" fill="url(#linear-gradient-4)"/>
<path id="end" d="M833.3 697.5 L520 878.38 L520 768.9 L738.48 642.76 L738.48 390.48 L833.3 335.74 Z" fill="url(#linear-gradient-5)"/>
<path id="bolt_hole" data-name="bolt hole" d="M521.64 643.71 C487.92 663.17 433.26 663.17 399.55 643.71 C365.83 624.24 365.83 592.68 399.55 573.22 C433.26 553.75 487.92 553.75 521.64 573.22 C555.35 592.68 555.35 624.24 521.64 643.71 Z" fill="url(#linear-gradient-6)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<clipPath id="clip-path-1">
<path d="M512 141 L825.3 321.88 L730.48 376.62 L417.19 195.74 Z M401.19 457.26 L714.48 638.14 L512 755.04 L198.7 574.16 Z M521.64 643.71 C555.35 624.24 555.35 592.68 521.64 573.22 C487.92 553.75 433.26 553.75 399.55 573.22 C365.83 592.68 365.83 624.24 399.55 643.71 C433.26 663.17 487.92 663.17 521.64 643.71 Z M409.19 209.6 L722.48 390.48 L722.48 624.28 L409.19 443.4 Z M190.7 588.02 L504 768.9 L504 878.38 L190.7 697.5 Z M833.3 697.5 L520 878.38 L520 768.9 L738.48 642.76 L738.48 390.48 L833.3 335.74 Z"/>
</clipPath>
</defs>
<title>FieldCraft icon mesh</title>
<g id="Mesh" clip-path="url(#clip-path-1)">
<path id="hexahedral_mesh" data-name="hexahedral mesh" d="M460.59 482.32 L242.11 608.46 M679.07 608.46 L460.59 734.6 M354.57 484.18 L405.97 513.86 M624.45 640 L675.86 669.67 M299.95 515.71 L351.35 545.39 M569.83 671.53 L621.24 701.21 M245.32 547.25 L296.73 576.92 M515.21 703.07 L566.62 732.75 M460.59 482.32 L460.59 230.04 M563.41 170.68 L460.59 230.04 M515.21 513.86 L515.21 261.57 M618.03 202.22 L515.21 261.57 M569.83 545.39 L569.83 293.11 M672.65 233.75 L569.83 293.11 M624.45 576.93 L624.45 324.65 M727.27 265.29 L624.45 324.65 M679.07 608.46 L679.07 356.18 M781.89 296.82 L679.07 356.18 M409.19 389.57 L730.48 575.07 M409.19 326.5 L730.48 512 M409.19 263.43 L730.48 448.93 M460.59 170.68 L781.89 356.18 M242.11 727.18 L242.11 608.46 M296.73 758.72 L296.73 640 M351.35 790.25 L351.35 671.53 M405.97 821.79 L405.97 703.07 M460.59 853.32 L460.59 734.6 M190.7 638.14 L512 823.64 M781.89 727.18 L781.89 356.18 M730.48 756.86 L730.48 638.14 M675.86 788.39 L675.86 669.67 M621.24 819.93 L621.24 701.21 M566.62 851.47 L566.62 732.75 M833.3 638.14 L512 823.64 M833.3 578.78 L730.48 638.14 M833.3 515.71 L730.48 575.07 M833.3 452.64 L730.48 512 M833.3 389.57 L730.48 448.93 M460.59 558.62 L460.59 482.32 M433.29 561.17 L405.97 513.86 M399.55 573.22 L351.35 545.39 M378.69 592.7 L296.73 576.92 M374.26 608.46 L242.11 608.46 M487.89 561.17 L515.21 513.86 M378.69 624.22 L296.73 640 M521.64 573.22 L569.83 545.39 M399.55 643.71 L351.35 671.53 M542.49 592.7 L624.45 576.92 M433.29 655.75 L405.97 703.06 M546.92 608.46 L679.07 608.46 M542.49 624.22 L624.45 640 M521.64 643.71 L569.83 671.53 M487.89 655.75 L515.21 703.07 M460.59 658.3 L460.59 734.6 M545.74 657.62 L540.09 660.8 L534.3 663.92 L528.33 666.99 L522.16 670.03 L515.76 673.05 L509.11 676.08 L502.15 679.15 L494.85 682.27 L487.14 685.51 L478.94 688.9 L470.14 692.52 L460.59 696.45 L451.05 692.52 L442.25 688.9 L434.05 685.51 L426.34 682.27 L419.03 679.15 L412.08 676.08 L405.42 673.05 L399.02 670.03 L392.86 666.99 L386.89 663.92 L381.09 660.8 L375.45 657.62 L369.94 654.36 L364.54 651.01 L359.22 647.57 L353.95 644.01 L348.72 640.31 L343.47 636.47 L338.16 632.45 L332.74 628.24 L327.14 623.79 L321.26 619.05 L315 613.97 L308.19 608.46 L315 602.95 L321.26 597.87 L327.14 593.13 L332.74 588.68 L338.16 584.47 L343.47 580.45 L348.72 576.61 L353.95 572.91 L359.22 569.35 L364.54 565.91 L369.94 562.56 L375.45 559.3 L381.09 556.12 L386.89 553 L392.86 549.93 L399.02 546.89 L405.42 543.87 L412.08 540.84 L419.03 537.77 L426.34 534.65 L434.05 531.41 L442.25 528.02 L451.05 524.4 L460.59 520.47 L470.14 524.4 L478.94 528.02 L487.14 531.41 L494.85 534.65 L502.15 537.77 L509.11 540.84 L515.76 543.87 L522.16 546.89 L528.33 549.93 L534.3 553 L540.09 556.12 L545.74 559.3 L551.25 562.56 L556.65 565.91 L561.97 569.35 L567.23 572.91 L572.47 576.61 L577.72 580.45 L583.02 584.47 L588.44 588.68 L594.05 593.13 L599.92 597.87 L606.19 602.95 L613 608.46 L606.19 613.97 L599.92 619.05 L594.05 623.79 L588.44 628.24 L583.02 632.45 L577.72 636.47 L572.47 640.31 L567.23 644.01 L561.97 647.57 L556.65 651.01 L551.25 654.36 Z" fill="none" stroke="#ffffff" stroke-width="4.5" stroke-linejoin="round" opacity="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

+116
View File
@@ -0,0 +1,116 @@
{
"fill": "system-light",
"groups": [
{
"layers": [
{
"glass-specializations": [
{
"appearance": "tinted",
"value": true
}
],
"image-name": "03-mesh.svg",
"name": "03-mesh",
"position": {
"scale": 0.9,
"translation-in-points": [
0,
0
]
}
}
],
"name": "03 Mesh",
"shadow": {
"kind": "none",
"opacity": 0
},
"specular": false,
"translucency": {
"enabled": false,
"value": 0.3
}
},
{
"layers": [
{
"glass-specializations": [
{
"appearance": "tinted",
"value": true
}
],
"image-name": "02-part.svg",
"name": "02-part",
"position": {
"scale": 0.9,
"translation-in-points": [
0,
0
]
}
}
],
"name": "02 Part",
"shadow": {
"kind": "none",
"opacity": 0
},
"specular": true,
"translucency": {
"enabled": false,
"value": 0.3
}
},
{
"layers": [
{
"fill": "none",
"glass-specializations": [
{
"value": false
},
{
"appearance": "tinted",
"value": true
}
],
"image-name-specializations": [
{
"value": "01-floor.svg"
},
{
"appearance": "dark",
"value": "01-floor-dark.svg"
}
],
"name": "01-floor",
"position": {
"scale": 0.9,
"translation-in-points": [
0,
0
]
}
}
],
"name": "01 Floor",
"shadow": {
"kind": "none",
"opacity": 0
},
"specular": false,
"translucency": {
"enabled": false,
"value": 0.3
}
}
],
"supported-platforms": {
"circles": [
"watchOS"
],
"squares": "shared"
}
}
+27
View File
@@ -0,0 +1,27 @@
# FieldCraft application icon
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
family's layout (rounded-square backdrop, isometric floor grid, lit top faces) in its own teal.
Everything here is generated by `cargo xtask app-icon` (`xtask/src/app_icon.rs`), which computes
the geometry and draws it with VectorCraft's command-line tool (`VECTORCRAFT_CLI`, else
`vectorcraft-cli` on the PATH). Change the recipe there and rerun it; do not edit the exports by
hand. It runs on macOS (sips, iconutil, `xcrun actool`) with ImageMagick (`magick`) installed.
| File | What it is |
|---|---|
| `icon.vectorcraft`, `icon.svg`, `icon.png` | The full icon on its backdrop, 1024 × 1024 |
| `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 |
| `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 `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/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`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<filter id="filter-1" filterUnits="userSpaceOnUse" x="67" y="67" width="890" height="890" data-vc-blend="multiply" color-interpolation-filters="sRGB"><feGaussianBlur in="SourceAlpha" stdDeviation="7"/><feOffset dx="0" dy="10" result="shadow"/><feFlood flood-color="#1d3b38" flood-opacity="0.22"/><feComposite in2="shadow" operator="in" result="paint"/><feMerge><feMergeNode in="paint"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<linearGradient id="linear-gradient-1" x1="512" y1="100" x2="512" y2="924" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fbfdfd"/>
<stop offset="1" stop-color="#dbe5e4"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="512" y1="141" x2="512" y2="385.86" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#86e3d3"/>
<stop offset="1" stop-color="#4cc4b1"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="512" y1="452.64" x2="512" y2="764.28" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#86e3d3"/>
<stop offset="1" stop-color="#4cc4b1"/>
</linearGradient>
<linearGradient id="linear-gradient-4" x1="512" y1="200.36" x2="512" y2="638.14" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#20b39f"/>
<stop offset="1" stop-color="#118c7c"/>
</linearGradient>
<linearGradient id="linear-gradient-5" x1="512" y1="578.78" x2="512" y2="883" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#20b39f"/>
<stop offset="1" stop-color="#118c7c"/>
</linearGradient>
<linearGradient id="linear-gradient-6" x1="512" y1="326.5" x2="512" y2="883" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0f786d"/>
<stop offset="1" stop-color="#0a544e"/>
</linearGradient>
<linearGradient id="linear-gradient-7" x1="512" y1="558.616" x2="512" y2="658.304" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0b665e"/>
<stop offset="1" stop-color="#04302c"/>
</linearGradient>
</defs>
<title>FieldCraft application icon, small sizes</title>
<g id="Backdrop">
<g filter="url(#filter-1)">
<path id="backdrop" d="M320 100 L704 100 C854 100 924 170 924 320 L924 704 C924 854 854 924 704 924 L320 924 C170 924 100 854 100 704 L100 320 C100 170 170 100 320 100 Z" fill="url(#linear-gradient-1)"/>
</g>
</g>
<g id="Part">
<path id="wall_top" data-name="wall top" d="M512 141 L833.3 326.5 L730.48 385.86 L409.19 200.36 Z" fill="url(#linear-gradient-2)"/>
<path id="base_top" data-name="base top" d="M409.19 452.64 L730.48 638.14 L512 764.28 L190.7 578.78 Z M521.64 643.71 C555.35 624.24 555.35 592.68 521.64 573.22 C487.92 553.75 433.26 553.75 399.55 573.22 C365.83 592.68 365.83 624.24 399.55 643.71 C433.26 663.17 487.92 663.17 521.64 643.71 Z" fill="url(#linear-gradient-3)"/>
<path id="wall_front" data-name="wall front" d="M409.19 200.36 L730.48 385.86 L730.48 638.14 L409.19 452.64 Z" fill="url(#linear-gradient-4)"/>
<path id="base_front" data-name="base front" d="M190.7 578.78 L512 764.28 L512 883 L190.7 697.5 Z" fill="url(#linear-gradient-5)"/>
<path id="end" d="M833.3 697.5 L512 883 L512 764.28 L730.48 638.14 L730.48 385.86 L833.3 326.5 Z" fill="url(#linear-gradient-6)"/>
<path id="bolt_hole" data-name="bolt hole" d="M521.64 643.71 C487.92 663.17 433.26 663.17 399.55 643.71 C365.83 624.24 365.83 592.68 399.55 573.22 C433.26 553.75 487.92 553.75 521.64 573.22 C555.35 592.68 555.35 624.24 521.64 643.71 Z" fill="url(#linear-gradient-7)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

+66
View File
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<filter id="filter-1" filterUnits="userSpaceOnUse" x="67" y="67" width="890" height="890" data-vc-blend="multiply" color-interpolation-filters="sRGB"><feGaussianBlur in="SourceAlpha" stdDeviation="7"/><feOffset dx="0" dy="10" result="shadow"/><feFlood flood-color="#1d3b38" flood-opacity="0.22"/><feComposite in2="shadow" operator="in" result="paint"/><feMerge><feMergeNode in="paint"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<linearGradient id="linear-gradient-1" x1="512" y1="100" x2="512" y2="924" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fbfdfd"/>
<stop offset="1" stop-color="#dbe5e4"/>
</linearGradient>
<clipPath id="clip-path-1">
<path d="M320 100 L704 100 C854 100 924 170 924 320 L924 704 C924 854 854 924 704 924 L320 924 C170 924 100 854 100 704 L100 320 C100 170 170 100 320 100 Z"/>
</clipPath>
<radialGradient id="radial-gradient-1" cx="0" cy="0" r="578" gradientTransform="matrix(1 0 0 0.5 512 720)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#7fa8a2"/>
<stop offset="0.3" stop-color="#7fa8a2" stop-opacity="0.9"/>
<stop offset="0.65" stop-color="#7fa8a2" stop-opacity="0.35"/>
<stop offset="1" stop-color="#7fa8a2" stop-opacity="0"/>
</radialGradient>
<linearGradient id="linear-gradient-2" x1="512" y1="141" x2="512" y2="376.622" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#86e3d3"/>
<stop offset="1" stop-color="#4cc4b1"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="512" y1="457.259" x2="512" y2="755.042" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#86e3d3"/>
<stop offset="1" stop-color="#4cc4b1"/>
</linearGradient>
<linearGradient id="linear-gradient-4" x1="512" y1="209.598" x2="512" y2="624.284" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#20b39f"/>
<stop offset="1" stop-color="#118c7c"/>
</linearGradient>
<linearGradient id="linear-gradient-5" x1="512" y1="588.018" x2="512" y2="878.381" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#20b39f"/>
<stop offset="1" stop-color="#118c7c"/>
</linearGradient>
<linearGradient id="linear-gradient-6" x1="512" y1="335.738" x2="512" y2="878.381" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0f786d"/>
<stop offset="1" stop-color="#0a544e"/>
</linearGradient>
<linearGradient id="linear-gradient-7" x1="512" y1="558.616" x2="512" y2="658.304" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0b665e"/>
<stop offset="1" stop-color="#04302c"/>
</linearGradient>
<clipPath id="clip-path-2">
<path d="M512 141 L825.3 321.88 L730.48 376.62 L417.19 195.74 Z M401.19 457.26 L714.48 638.14 L512 755.04 L198.7 574.16 Z M521.64 643.71 C555.35 624.24 555.35 592.68 521.64 573.22 C487.92 553.75 433.26 553.75 399.55 573.22 C365.83 592.68 365.83 624.24 399.55 643.71 C433.26 663.17 487.92 663.17 521.64 643.71 Z M409.19 209.6 L722.48 390.48 L722.48 624.28 L409.19 443.4 Z M190.7 588.02 L504 768.9 L504 878.38 L190.7 697.5 Z M833.3 697.5 L520 878.38 L520 768.9 L738.48 642.76 L738.48 390.48 L833.3 335.74 Z"/>
</clipPath>
</defs>
<title>FieldCraft application icon</title>
<g id="Backdrop">
<g filter="url(#filter-1)">
<path id="backdrop" d="M320 100 L704 100 C854 100 924 170 924 320 L924 704 C924 854 854 924 704 924 L320 924 C170 924 100 854 100 704 L100 320 C100 170 170 100 320 100 Z" fill="url(#linear-gradient-1)"/>
</g>
</g>
<g id="Floor_grid" data-name="Floor grid" clip-path="url(#clip-path-1)">
<path id="floor_grid" data-name="floor grid" d="M154.62 100 L100 131.53 M869.38 100 L924 131.53 M368.82 100 L100 255.2 M655.18 100 L924 255.2 M583.01 100 L100 378.87 M440.99 100 L924 378.87 M797.21 100 L100 502.53 M226.79 100 L924 502.53 M924 150.47 L100 626.2 M100 150.47 L924 626.2 M924 274.13 L100 749.87 M100 274.13 L924 749.87 M924 397.8 L100 873.53 M100 397.8 L924 873.53 M924 521.47 L226.79 924 M100 521.47 L797.21 924 M924 645.13 L440.99 924 M100 645.13 L583.01 924 M924 768.8 L655.18 924 M100 768.8 L368.82 924 M924 892.47 L869.38 924 M100 892.47 L154.62 924" fill="none" stroke="url(#radial-gradient-1)" stroke-width="3" stroke-linejoin="round"/>
</g>
<g id="Part">
<path id="wall_top" data-name="wall top" d="M512 141 L825.3 321.88 L730.48 376.62 L417.19 195.74 Z" fill="url(#linear-gradient-2)"/>
<path id="base_top" data-name="base top" d="M401.19 457.26 L714.48 638.14 L512 755.04 L198.7 574.16 Z M521.64 643.71 C555.35 624.24 555.35 592.68 521.64 573.22 C487.92 553.75 433.26 553.75 399.55 573.22 C365.83 592.68 365.83 624.24 399.55 643.71 C433.26 663.17 487.92 663.17 521.64 643.71 Z" fill="url(#linear-gradient-3)"/>
<path id="wall_front" data-name="wall front" d="M409.19 209.6 L722.48 390.48 L722.48 624.28 L409.19 443.4 Z" fill="url(#linear-gradient-4)"/>
<path id="base_front" data-name="base front" d="M190.7 588.02 L504 768.9 L504 878.38 L190.7 697.5 Z" fill="url(#linear-gradient-5)"/>
<path id="end" d="M833.3 697.5 L520 878.38 L520 768.9 L738.48 642.76 L738.48 390.48 L833.3 335.74 Z" fill="url(#linear-gradient-6)"/>
<path id="bolt_hole" data-name="bolt hole" d="M521.64 643.71 C487.92 663.17 433.26 663.17 399.55 643.71 C365.83 624.24 365.83 592.68 399.55 573.22 C433.26 553.75 487.92 553.75 521.64 573.22 C555.35 592.68 555.35 624.24 521.64 643.71 Z" fill="url(#linear-gradient-7)"/>
</g>
<g id="Mesh" clip-path="url(#clip-path-2)">
<path id="hexahedral_mesh" data-name="hexahedral mesh" d="M460.59 482.32 L242.11 608.46 M679.07 608.46 L460.59 734.6 M354.57 484.18 L405.97 513.86 M624.45 640 L675.86 669.67 M299.95 515.71 L351.35 545.39 M569.83 671.53 L621.24 701.21 M245.32 547.25 L296.73 576.92 M515.21 703.07 L566.62 732.75 M460.59 482.32 L460.59 230.04 M563.41 170.68 L460.59 230.04 M515.21 513.86 L515.21 261.57 M618.03 202.22 L515.21 261.57 M569.83 545.39 L569.83 293.11 M672.65 233.75 L569.83 293.11 M624.45 576.93 L624.45 324.65 M727.27 265.29 L624.45 324.65 M679.07 608.46 L679.07 356.18 M781.89 296.82 L679.07 356.18 M409.19 389.57 L730.48 575.07 M409.19 326.5 L730.48 512 M409.19 263.43 L730.48 448.93 M460.59 170.68 L781.89 356.18 M242.11 727.18 L242.11 608.46 M296.73 758.72 L296.73 640 M351.35 790.25 L351.35 671.53 M405.97 821.79 L405.97 703.07 M460.59 853.32 L460.59 734.6 M190.7 638.14 L512 823.64 M781.89 727.18 L781.89 356.18 M730.48 756.86 L730.48 638.14 M675.86 788.39 L675.86 669.67 M621.24 819.93 L621.24 701.21 M566.62 851.47 L566.62 732.75 M833.3 638.14 L512 823.64 M833.3 578.78 L730.48 638.14 M833.3 515.71 L730.48 575.07 M833.3 452.64 L730.48 512 M833.3 389.57 L730.48 448.93 M460.59 558.62 L460.59 482.32 M433.29 561.17 L405.97 513.86 M399.55 573.22 L351.35 545.39 M378.69 592.7 L296.73 576.92 M374.26 608.46 L242.11 608.46 M487.89 561.17 L515.21 513.86 M378.69 624.22 L296.73 640 M521.64 573.22 L569.83 545.39 M399.55 643.71 L351.35 671.53 M542.49 592.7 L624.45 576.92 M433.29 655.75 L405.97 703.06 M546.92 608.46 L679.07 608.46 M542.49 624.22 L624.45 640 M521.64 643.71 L569.83 671.53 M487.89 655.75 L515.21 703.07 M460.59 658.3 L460.59 734.6 M545.74 657.62 L540.09 660.8 L534.3 663.92 L528.33 666.99 L522.16 670.03 L515.76 673.05 L509.11 676.08 L502.15 679.15 L494.85 682.27 L487.14 685.51 L478.94 688.9 L470.14 692.52 L460.59 696.45 L451.05 692.52 L442.25 688.9 L434.05 685.51 L426.34 682.27 L419.03 679.15 L412.08 676.08 L405.42 673.05 L399.02 670.03 L392.86 666.99 L386.89 663.92 L381.09 660.8 L375.45 657.62 L369.94 654.36 L364.54 651.01 L359.22 647.57 L353.95 644.01 L348.72 640.31 L343.47 636.47 L338.16 632.45 L332.74 628.24 L327.14 623.79 L321.26 619.05 L315 613.97 L308.19 608.46 L315 602.95 L321.26 597.87 L327.14 593.13 L332.74 588.68 L338.16 584.47 L343.47 580.45 L348.72 576.61 L353.95 572.91 L359.22 569.35 L364.54 565.91 L369.94 562.56 L375.45 559.3 L381.09 556.12 L386.89 553 L392.86 549.93 L399.02 546.89 L405.42 543.87 L412.08 540.84 L419.03 537.77 L426.34 534.65 L434.05 531.41 L442.25 528.02 L451.05 524.4 L460.59 520.47 L470.14 524.4 L478.94 528.02 L487.14 531.41 L494.85 534.65 L502.15 537.77 L509.11 540.84 L515.76 543.87 L522.16 546.89 L528.33 549.93 L534.3 553 L540.09 556.12 L545.74 559.3 L551.25 562.56 L556.65 565.91 L561.97 569.35 L567.23 572.91 L572.47 576.61 L577.72 580.45 L583.02 584.47 L588.44 588.68 L594.05 593.13 L599.92 597.87 L606.19 602.95 L613 608.46 L606.19 613.97 L599.92 619.05 L594.05 623.79 L588.44 628.24 L583.02 632.45 L577.72 636.47 L572.47 640.31 L567.23 644.01 L561.97 647.57 L556.65 651.01 L551.25 654.36 Z" fill="none" stroke="#ffffff" stroke-width="4.5" stroke-linejoin="round" opacity="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>assembly.instance</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M8 10 L13 12.5 L13 17.5 L8 20 L3 17.5 L3 12.5 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 12.5 L8 15 L13 12.5 M8 15 L8 20" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 4 L21 6.5 L21 11.5 L16 14 L11 11.5 L11 6.5 Z" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 6.5 L16 9 L21 6.5 M16 9 L16 14" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 8 L13.5 6 M12.19 8.13 L13.5 6 L11 6.05" fill="none" stroke="#dde2ea" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>deck.read</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M8 3 L16 3 L20 7 L20 21 L8 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 3 L16 7 L20 7" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 10 L17 10 M11 13 L17 13 M11 16 L15 16" fill="none" stroke="#dde2ea" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 15 L9 15 M6.19 16.53 L9 15 L6.19 13.47" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>deck.write.abaqus</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M4 3 L12 3 L16 7 L16 21 L4 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3 L12 7 L16 7" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 10 L13 10 M7 13 L13 13 M7 16 L11 16" fill="none" stroke="#dde2ea" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 15 L21 15 M18.19 16.53 L21 15 L18.19 13.47" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 6 C16 4.619 17.119 3.5 18.5 3.5 C19.881 3.5 21 4.619 21 6 C21 7.381 19.881 8.5 18.5 8.5 C17.119 8.5 16 7.381 16 6 Z" fill="#4f9ce8" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>deck.write.lsdyna</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M4 3 L12 3 L16 7 L16 21 L4 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3 L12 7 L16 7" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 10 L13 10 M7 13 L13 13 M7 16 L11 16" fill="none" stroke="#dde2ea" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 15 L21 15 M18.19 16.53 L21 15 L18.19 13.47" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.5 3 L21.3 8.2 L15.7 8.2 Z" fill="#4f9ce8" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>deck.write.nastran</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M4 3 L12 3 L16 7 L16 21 L4 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3 L12 7 L16 7" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 10 L13 10 M7 13 L13 13 M7 16 L11 16" fill="none" stroke="#dde2ea" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 15 L21 15 M18.19 16.53 L21 15 L18.19 13.47" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 3.5 L21 3.5 L21 8.5 L16 8.5 Z" fill="#4f9ce8" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>edit.redo</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M19 9 L10 9 C7.239 9 5 11.239 5 14 C5 16.761 7.239 19 10 19 L16 19" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 5.5 L19.5 9 L16 12.5" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>edit.undo</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M5 9 L14 9 C16.761 9 19 11.239 19 14 C19 16.761 16.761 19 14 19 L8 19" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5.5 L4.5 9 L8 12.5" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>file.open</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 5 L9 5 L11 7 L20 7 L20 19 L3 19 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 17 L12 10 M13.53 12.81 L12 10 L10.47 12.81" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>file.recover</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M4.5 12 C4.505 15.538 6.981 18.591 10.442 19.327 C13.903 20.062 17.406 18.278 18.849 15.048 C20.291 11.817 19.279 8.018 16.421 5.932 C13.564 3.846 9.637 4.041 7 6.4" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.4 4.6 L7 6.4 L5.6 10.2" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 8 L12 12 L15 14" fill="none" stroke="#dde2ea" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>file.sample</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 10 L11 6 L19 10 L19 18 L11 22 L3 18 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 10 L11 14 L19 10 M11 14 L11 22" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.5 1.5 L19.44 4.206 L22.304 4.264 L20.022 5.994 L20.851 8.736 L18.5 7.1 L16.149 8.736 L16.978 5.994 L14.696 4.264 L17.56 4.206 Z" fill="#f0b44a" stroke="#f0b44a" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>file.save</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 14 L3 20 L21 20 L21 14" fill="none" stroke="#dde2ea" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3 L12 15 M10.47 12.19 L12 15 L13.53 12.19" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>file.save_as</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 14 L3 20 L21 20 L21 14" fill="none" stroke="#dde2ea" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 3 L9 15 M5.8 12 L9 15 L12.2 12" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 12 L19.5 6.5 L21.5 8.5 L16 14 L13.5 14.5 Z" fill="#2d4e70" stroke="#f0b44a" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>geometry.bodies</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M9 8 L15 11 L15 18 L9 21 L3 18 L3 11 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 11 L9 14 L15 11 M9 14 L9 21" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 4 L19 6.5 L19 13 L16 14.5" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 4 L9 6.5 L9 7" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>geometry.datum</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 15 L9 9 L21 9 L15 15 Z" fill="#2d4e70" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 12 L12 3 M13.53 5.81 L12 3 L10.47 5.81" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 12 C10.5 11.172 11.172 10.5 12 10.5 C12.828 10.5 13.5 11.172 13.5 12 C13.5 12.828 12.828 13.5 12 13.5 C11.172 13.5 10.5 12.828 10.5 12 Z" fill="#4f9ce8"/>
<path d="M12 15 L12 21" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.5 2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>geometry.import.mesh</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 13 L12 9 L21 13 L12 17 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 11 L16.5 15 M7.5 15 L16.5 11 M12 9 L12 17" fill="none" stroke="#dde2ea" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 13 L3 16 L12 20 L21 16 L21 13" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 2 L12 8 M10.47 5.19 L12 8 L13.53 5.19" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>geometry.midsurface</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 9 L9 4 L21 4 L15 9 Z" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 14 L9 9 L21 9 L15 14 Z" fill="#2d4e70" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 19 L9 14 L21 14 L15 19 Z" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>geometry.open</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M13 6 L20 9.5 L20 17.5 L13 21 L6 17.5 L6 9.5 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 9.5 L13 13 L20 9.5 M13 13 L13 21" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 3 L9 9 M5.93 8.1 L9 9 L8.1 5.93" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>geometry.partition</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M12 3 L20 7 L20 16 L12 20 L4 16 L4 7 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7 L12 11 L20 7 M12 11 L12 20" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5 L16 9 M16 9 L16 18 M16 9 L16 9" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5 L8 14 L16 18" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>geometry.tessellate</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 12 C3 7.029 7.029 3 12 3 C16.971 3 21 7.029 21 12 C21 16.971 16.971 21 12 21 C7.029 21 3 16.971 3 12 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 12 L19.79 16.5 M12 12 L12 21 M12 12 L4.21 16.5 M12 12 L4.21 7.5 M12 12 L12 3 M12 12 L19.79 7.5" fill="none" stroke="#4f9ce8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.79 16.5 L12 21 L4.21 16.5 L4.21 7.5 L12 3 L19.79 7.5 Z" fill="none" stroke="#4f9ce8" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>interaction.bolt</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M7 3 L17 3 L17 7 L7 7 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 7 L14.5 7 L14.5 21 L9.5 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 11 L14.5 12.5 M9.5 14 L14.5 15.5 M9.5 17 L14.5 18.5" fill="none" stroke="#dde2ea" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 13 L4 6 M5.2 8.19 L4 6 L2.8 8.19" fill="none" stroke="#f0b44a" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20 13 L20 20 M18.8 17.81 L20 20 L21.2 17.81" fill="none" stroke="#f0b44a" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>interaction.connector</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M2.2 18 C2.2 17.006 3.006 16.2 4 16.2 C4.994 16.2 5.8 17.006 5.8 18 C5.8 18.994 4.994 19.8 4 19.8 C3.006 19.8 2.2 18.994 2.2 18 Z" fill="#dde2ea"/>
<path d="M18.2 6 C18.2 5.006 19.006 4.2 20 4.2 C20.994 4.2 21.8 5.006 21.8 6 C21.8 6.994 20.994 7.8 20 7.8 C19.006 7.8 18.2 6.994 18.2 6 Z" fill="#dde2ea"/>
<path d="M4 18 L10 14 M14 10 L20 6" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.2 12 C9.2 10.454 10.454 9.2 12 9.2 C13.546 9.2 14.8 10.454 14.8 12 C14.8 13.546 13.546 14.8 12 14.8 C10.454 14.8 9.2 13.546 9.2 12 Z" fill="none" stroke="#4f9ce8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>interaction.contact.find</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 8 C3 5.239 5.239 3 8 3 C10.761 3 13 5.239 13 8 C13 10.761 10.761 13 8 13 C5.239 13 3 10.761 3 8 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 13 L13 13 L13 21 L3 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 15 C12 12.791 13.791 11 16 11 C18.209 11 20 12.791 20 15 C20 17.209 18.209 19 16 19 C13.791 19 12 17.209 12 15 Z" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.83 17.83 L21.83 20.83" fill="none" stroke="#4f9ce8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>interaction.contact.general</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 8 C3 5.239 5.239 3 8 3 C10.761 3 13 5.239 13 8 C13 10.761 10.761 13 8 13 C5.239 13 3 10.761 3 8 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 15 L21 15 L21 21 L3 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 3 L21 3 L21 10 L14 10 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.5 14.3 C7.476 14.607 8.524 14.607 9.5 14.3" fill="none" stroke="#4f9ce8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 6 L13 10" fill="none" stroke="#4f9ce8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>interaction.contact.pair</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M6 9 C6 5.686 8.686 3 12 3 C15.314 3 18 5.686 18 9 C18 12.314 15.314 15 12 15 C8.686 15 6 12.314 6 9 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 15 L21 15 L21 21 L3 21 Z" fill="#2d4e70" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 15 L16 15" fill="none" stroke="#4f9ce8" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<title>interaction.fsi</title>
<g id="Layer_1" data-name="Layer 1">
<path d="M3 7 L3.23 6.71 L3.46 6.43 L3.69 6.17 L3.92 5.94 L4.15 5.75 L4.38 5.61 L4.6 5.53 L4.83 5.5 L5.06 5.53 L5.29 5.61 L5.52 5.75 L5.75 5.94 L5.98 6.17 L6.21 6.43 L6.44 6.71 L6.67 7 L6.9 7.29 L7.12 7.57 L7.35 7.83 L7.58 8.06 L7.81 8.25 L8.04 8.39 L8.27 8.47 L8.5 8.5 L8.73 8.47 L8.96 8.39 L9.19 8.25 L9.42 8.06 L9.65 7.83 L9.88 7.57 L10.1 7.29 L10.33 7 L10.56 6.71 L10.79 6.43 L11.02 6.17 L11.25 5.94 L11.48 5.75 L11.71 5.61 L11.94 5.53 L12.17 5.5 L12.4 5.53 L12.62 5.61 L12.85 5.75 L13.08 5.94 L13.31 6.17 L13.54 6.43 L13.77 6.71 L14 7" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 12 L3.23 11.71 L3.46 11.43 L3.69 11.17 L3.92 10.94 L4.15 10.75 L4.38 10.61 L4.6 10.53 L4.83 10.5 L5.06 10.53 L5.29 10.61 L5.52 10.75 L5.75 10.94 L5.98 11.17 L6.21 11.43 L6.44 11.71 L6.67 12 L6.9 12.29 L7.12 12.57 L7.35 12.83 L7.58 13.06 L7.81 13.25 L8.04 13.39 L8.27 13.47 L8.5 13.5 L8.73 13.47 L8.96 13.39 L9.19 13.25 L9.42 13.06 L9.65 12.83 L9.88 12.57 L10.1 12.29 L10.33 12 L10.56 11.71 L10.79 11.43 L11.02 11.17 L11.25 10.94 L11.48 10.75 L11.71 10.61 L11.94 10.53 L12.17 10.5 L12.4 10.53 L12.62 10.61 L12.85 10.75 L13.08 10.94 L13.31 11.17 L13.54 11.43 L13.77 11.71 L14 12" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 17 L3.23 16.71 L3.46 16.43 L3.69 16.17 L3.92 15.94 L4.15 15.75 L4.38 15.61 L4.6 15.53 L4.83 15.5 L5.06 15.53 L5.29 15.61 L5.52 15.75 L5.75 15.94 L5.98 16.17 L6.21 16.43 L6.44 16.71 L6.67 17 L6.9 17.29 L7.12 17.57 L7.35 17.83 L7.58 18.06 L7.81 18.25 L8.04 18.39 L8.27 18.47 L8.5 18.5 L8.73 18.47 L8.96 18.39 L9.19 18.25 L9.42 18.06 L9.65 17.83 L9.88 17.57 L10.1 17.29 L10.33 17 L10.56 16.71 L10.79 16.43 L11.02 16.17 L11.25 15.94 L11.48 15.75 L11.71 15.61 L11.94 15.53 L12.17 15.5 L12.4 15.53 L12.62 15.61 L12.85 15.75 L13.08 15.94 L13.31 16.17 L13.54 16.43 L13.77 16.71 L14 17" fill="none" stroke="#4f9ce8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17 21 C17 15 18 9 20 3" fill="none" stroke="#dde2ea" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 21 L21 21" fill="none" stroke="#dde2ea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.
Loaded 100 of 1015 files, more files were not shown because too many files have changed in this diff. Show more