Pieces a cut splits a body into are named after their faces since names
follow history (lineage::order_lumps), so they keep their body names when
an upstream edit moves the cut. The kernel's shell order named them
before, and the two orders can differ: a slot along X through a 40x30x10
block swaps Body1 and Body1 (1). A design saved by an older build stores
body names meaning the old order (fillet and chamfer bodies, targets,
participants, materials, appearances, joints), so it would act on the
other piece.
Each feature now says which order names its pieces
(Feature::shell_piece_order). The v13 to v14 step sets it on every saved
feature, so an older design evaluates as it was saved; features made from
now on, in new or old designs, name their pieces after their faces.
The corpus gains split-body-pieces, saved by v0.3.2: a fillet, a chamfer
and an appearance refer to the two pieces by body name. Without the
setting the two pieces swap names (the corpus test fails); with it they
stay as saved. Migration tests that compared an upgraded design with the
original now expect the setting on its features.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
Why: every reference a user makes to a face or edge (fillet and chamfer
edges, sketches on faces, shell/draft/offset faces, joints, appearances)
is stored as a name and found again after any upstream edit. Those names
were re-derived by sampling points of each face and voting, and pieces
of a split face were numbered by where their centres lay. After an
upstream edit that splits, merges, mirrors or shrinks a face, a fillet
or sketch could silently re-attach to the wrong face, and nothing told
the user. The kernel now reports exact history for its operations, so
names follow it and fail loudly instead of guessing.
- crates/doc/src/lineage.rs: a feature's kernel histories composed into
one lineage over nameable operands (bodies before the feature, moved
copies, shell cavities, tools named by role: sketch curve, cap, box
side, imported face). Instrumented: extrude/revolve/sweep/loft tools,
join/cut/intersect, lumps after a cut, fillet/chamfer, shell, draft,
split face/body, pattern/mirror (bodies and features), combine,
boundary fill, move/scale, offset/delete face, thread, import.
- crates/doc/src/history_names.rs: names from lineage; split pieces told
apart by the faces only they touch (F2:top#[F2:side:l4,F4:side:l4]),
merged faces answer to every name (aliases), lump bodies ordered by
their faces' names instead of shell order.
- crates/doc/src/name_lookup.rs, piece_names.rs: references resolve by
exact name, alias or bounding faces, else nearest piece with a
warning; names worked out from geometry (operations without history)
are reported when only geometry decided.
- Format 14: names stored by older formats get a legacy mark and are
found as the build that saved them found them (some geometric names
name a face history names differently: a tube's outer wall was named
after its inner circle). New corpus file saved by 9a979cf.
- kernel: Body::lumps carries each piece's history over the whole.
- Adversarial tests: split, merge, mirrored twins, shrink to a sliver,
moving cut with body pieces, history-less bodies, certified sketch.
- docs/book/src/dev/naming.md describes the real mechanism and why.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
The oracle no longer uses Fusion measurements. tests/oracle/<case>/ now holds the 76 recipes
(our own format, with recorded measurements and tool-specific fields removed) and an
expected.json per case:
- 55 analytic cases: closed-form values from tools/oracle-analytic/ (stdlib Python, formulas
in each case's "method").
- 21 FreeCAD cases: the same recipe built with FreeCAD's public scripting API by
tools/freecad-oracle/ (an external program, not a dependency), with a reference.stl for 20.
solvecraft-cli oracle compares body count, volume, area, centre of mass, bbox, face counts
where the geometry fixes them, and, against reference.stl, the sampled Hausdorff distance and
the principal moments (spatial.rs from #301). cargo xtask oracle reads tests/oracle, writes
docs/oracle.md with the source of each value, and supports known_failure marks for tracked
bugs. CI runs it on every PR; a manual workflow regenerates the FreeCAD values. step-corpus,
seam normalisation and the through-all emulation, which only existed to compare with Fusion,
are removed.
75/76 pass; 75-sm-bracket-holes is a known failure (#296).
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
Co-authored-by: bherbruck <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Nothing on the load path checked a sketch's size, and every sketch in the
timeline is solved when a design opens. The solver sizes its dense matrices by
the connected component, so a 30 MB file with 200,000 chained points asked the
allocator for terabytes and the process aborted, which no panic guard can
intercept.
Reject an oversized sketch while the design is still raw JSON, so the typed
arrays are never built, and refuse a connected component with more variables
than fits in a bounded matrix: that component now reports a failed solve and
leaves its geometry alone. 10,000 chained points went from 9.6 GB and 25
seconds to a failed solve in well under a second.
Fixes#145
Co-Authored-By: Claude Opus 5 <[email protected]>
(cherry picked from commit 04abbf95c26f68c7d0a126ed9e2827049f593361)
The control channel read each request with no length cap, so a client sending
gigabytes without a newline made the server thread allocate until the process
was killed. Read lines against the 16 MiB cap the MCP server already uses, now
one shared constant, answer a longer one with an error and close; close a
connection after eight lines that are not JSON, serve at most sixteen
connections at once, and run at most sixty-four queued requests per frame so a
flood cannot starve drawing.
Every file command checked the size with the metadata and then read to the end.
A character device such as /dev/zero reports a length of 0, so the cap passed
and the read was unbounded; a FIFO blocked the calling thread, which over the
control channel is the thread drawing the window. vfs::read now takes the
caller's cap, refuses anything that is not a regular file before opening it,
re-checks on the handle to close the window between the two, and reads no more
than the cap plus the byte that proves it was exceeded. Callers pass the limits
they already had, and the recovery read and the CLI's own reads go through it
too. `solvecraft-cli eval /dev/zero` and a FIFO now fail at once instead of
never returning.
Fixes#147
Co-Authored-By: Claude Opus 5 <[email protected]>
(cherry picked from commit 4156e8acd11c90c435a54a3b0ccc59991db71b12)
A construction plane built from model geometry resolves each of its sides
through the document again. That hop restarted the recursion depth at 0, so a
plane that is its own side never exceeded depth 1 and the 32-level cap never
fired: a ~600-byte design file recursed until the stack overflowed and the
process aborted, which no panic guard can intercept.
Carry the depth on the construction context so the geometry arms continue the
caller's count instead of restarting it.
Fixes#143
Co-Authored-By: Claude Opus 5 <[email protected]>
(cherry picked from commit 4bca1d49f27dfe9ef0140132746e1c7415daba8e)
`cargo xtask step-corpus --iges` (and `solvecraft-cli step-corpus --iges`) opens every oracle part.igs as File → Open does and compares body count, volume, area and face count with Fusion's measurements, writing docs/iges-import.md. The run found faces closing at an apex (a drill point's cone, a sphere's pole: the seam runs out to the apex and straight back) kept whole and meshed wrongly; they are now split along the iso-line opposite the seam like bands are (18-hole-features and 40-box-sphere-torus pass). The one failure left, 50-sweep-tight-bend, is written by Fusion as five loose, partly degenerate surfaces that Fusion cannot read back as a solid either; documented in the report. Test: a drilled blind hole round-trips through IGES.
Fixes#14
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
- solvecraft-cli fuzz / fuzz-one: seeded random modelling sessions (boxes, extruded profiles with arcs, revolves; joins and cuts of boxes, cylinders and spheres, fillets, chamfers, shells, holes, patterns) on a grid that makes coincident faces and tangencies common, every step checked (command ok, every body a sound solid via the new Body::validity, volume moving the right way), plus the boolean volume identity on random primitives; one process per seed with a time limit; failures minimised to scripts
- shell/offset: corners where a curved face meets another at an angle are refined onto the moved surfaces (Newton); edges that are neither lines nor arcs are rebuilt through their moved points; one shell per piece; faces that would cross say so (not supported yet) instead of leaving a cavity inside out; flat revolved faces count as planes; the unhealed body is tried when the healed one fails
- cylinders start their seams at an odd angle (grid-aligned seams broke booleans along them)
- partial revolves of profiles touching the axis build no zero-area face on the axis
- booleans on bodies in several pieces retry a piece at a time
- regression tests for each, and a fixed set of seeds in cargo test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
Adapted from storytold/photocraft's release.yml and packaging/ (Apache-2.0; ATTRIBUTION.md and
NOTICE say so). Packages install the licence files, THIRD-PARTY-LICENSES.txt included. The app
icon is drawn by `solvecraft-cli icon` (a blue block with a bore, rendered on a transparent
background; .ico too), so no image is committed. Notes: a short header plus GitHub's generated
What's Changed. A dry run (Run workflow, publish off) keeps the files as the release-assets
artifact. packaging-lint.yml checks the workflows, scripts and metadata.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Release assets are now the programs themselves: SolveCraft and solvecraft-cli .exe for Windows,
plain Linux binaries plus the AppImage, a macOS .dmg (SolveCraft.app with an Applications link) and
the universal CLI binary; only the web app stays a zip. The notes template takes {tag}, says to
chmod +x on Linux and macOS, and GitHub adds the change list.
Since no LICENSE or NOTICE file rides along any more, the programs carry them:
THIRD-PARTY-LICENSES.txt (written by `cargo xtask licences` from cargo metadata and each shipped
crate's own licence files, 419 crates; `cargo xtask ci` checks it is current) is embedded with
LICENSE-MIT, LICENSE-APACHE and NOTICE in solvecraft_engine::licences, shown in Help ▸ About ▸
Licences (third-party text laid out by visible rows) and printed by `solvecraft-cli licences`.
Scenario s44_about_licences.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The old ids live only in crates/engine/src/legacy_ids.rs (the legacy migration table): find_command maps them, so old scripts, recipes and control calls keep working, and saved shortcuts, recent and pinned commands are migrated on load (designs store no command ids). Parity maps the observed catalog ids to ours through xtask/data/fusion-ids.tsv. The agent rule now reads: our own dotted id, never another product's. Cargo.toml's repository points at github.com/bherbruck/solvecraft.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fusion round trip WARNs on face counts:
- 48: the pulley's annular end faces arrived as 240° and 120° sectors.
- 54: the side faces and holes of a body glued to its mirror image came
in halves.
Changes:
- Planes merge: PLANE faces and flat B-spline sheets, as well as revolved
planes. A plane keeps several loops (an annulus, a face with holes), no
seam. Flat B-spline sheets are written as PLANE.
- Pieces of an extruded cylinder (B-spline sheets) merge when their edges
are circles square to the axis. The merged face is written as a
CYLINDRICAL_SURFACE, with its parameter seam on the face's seam edge.
- A full turn's loop is built explicitly: ring, seam, other ring (or the
apex on the axis), seam back. Before, it depended on edge order, and
after 81c85b5 the pulley's last pieces no longer merged.
- The read-back check moved to the whole file. If the merged file reads
with warnings and the unmerged one does not, the unmerged one is
written.
Results: 48 has 12 faces (Fusion 12), 54 has 12 (Fusion 12). Oracle
results are unchanged (73 PASS) and the STEP corpus passes 76/76. New
test: a plate with a hole glued to its mirror exports 8 faces, all
planes and cylinders, and round-trips.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
- Surfaces of revolution follow ISO 10303-42 strictly in reader and writer: the
normal is d/d(angle) x d/d(profile), the opposite of truck's revolution, as
Fusion reads it (fixes Fusion inverting fillet sphere corners and the dimple).
step_orientation_errors flags faces whose loop runs clockwise round their
normal; engine, oracle and kernel round trips assert it.
- Revolved faces with analytic profiles are written as native PLANE,
CYLINDRICAL/CONICAL/SPHERICAL/TOROIDAL surfaces, and the pieces truck splits
a revolution into are merged back into one face per surface (with a seam
edge for full turns), so face counts match other systems.
- A horn torus (tight sweep: tube touching its axis) is written as one rational
B-spline face whose loop passes the pole twice, with truck's zero-length pole
edges collapsed, instead of a degenerate surface of revolution; the reader
starts such figure-eight loops off the pole (Fusion's own part 50 now imports
with the right volume).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
Our writer assembles the file: AP242 header and contexts, a product per part
(or per component, with NEXT_ASSEMBLY_USAGE_OCCURRENCE and item-defined
placements per occurrence), named solids, surface colours as styled items,
ADVANCED_FACE for advanced breps. truck now writes only each body's B-rep,
which we copy in; identical geometric entities are written once and shared.
Designs with components export as assemblies (bodies in component frames).
step_validate also rejects unreferenced non-root entities. Tests: the sample
plate and a component assembly export, validate and read back with no
warnings and the same volume, area and face counts; the oracle replay checks
area and faces too (39 rebuildable cases pass). An independent Part 21
parser (step-p21, run outside the repo) accepts our exports.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
Vendored truck-stepio (writer only): an intersection curve's second surface
was written at the first one's id (duplicate ids for every boolean edge),
and revolved surfaces lost their orientation (the modeling-surface sense was
always true; inverted or mirrored revolutions came out inside out). Export
now writes intersection edges as C1 cubic B-splines through exact points,
so files carry plain 3D curves and no redundant surfaces.
Vendored truck-meshalgo: hinted parameter searches that run off a B-spline's
domain are rejected or clamped (meshing their grid never finished).
STEP import: straight revolution profiles are bounded in the meridian plane
and stop at the axis; loops on revolutions start away from the axis; face
extents include edge end points. Edges between pieces of one split surface
draw as seams.
kernel::step_validate checks a STEP file (duplicate ids, dangling
references, dense numbering); the oracle replay now exports every rebuilt
part, validates it and re-imports it with no warnings and the same volume
(all 32 rebuildable cases pass).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL