Commit Graph
749 Commits
Author SHA1 Message Date
Christian SvenssonandClaude Opus 5.5 079a600481 Recovery: autosave every open design tab (#2884) (#2952)
* Fix recovery for unsaved background tabs (#2884)

* Recovery: scheduled autosaves of every tab write on the worker thread (#2884)

After #3139, Autosaver::save waits for the write and Autosaver::tick runs
it on a worker. The scheduled path now goes through tick (which skips a
tab whose previous write is still running), so several dirty tabs no
longer write on the window thread each interval (#3044). save stays for
Save, close and shutdown. Each tab's autosaver has no clock of its own:
the shared cadence decides when it is due. The tests wait for the
writes a tick started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Recovery: closing a tab waits for its running autosave before removing the entry (#2884)

With the autosave on a worker thread, discard could remove the files and
the lock while a write was still running; the write then recreated the
entry without a lock, and the next launch offered a design closed with
Don't Save. discard now ends the running write first, as Drop does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:58:05 +00:00
Krzysztof HeimandKrzysztof Heim 1b84ce6330 fix: skip impossible drags of determined sketch geometry (#2006) (#2199)
Co-authored-by: Krzysztof Heim <krzysztof.heim@b4bridge.com>
2026-10-20 20:56:50 +00:00
7dfbaf2a2c Parameter autocomplete matches anywhere in the name, prefix matches first (#2757) (#2849)
* claim: #2757

* fix(params): autocomplete offers a parameter for any part of its name (#2757)

Typing `side` now offers `cab_side_thickness`: parameters match the word
anywhere in their name, after those that start with it, both in the expression
boxes and in parameters.complete. Functions, constants and units still match
from the start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 20:39:14 +00:00
bherbruckandClaude Opus 5.5 5d9ebe9911 Renaming a feature no longer rebuilds the design (#3044) (#3097)
* perf: renaming a feature evaluates nothing (#3044)

A feature's name labels it and builds nothing, but a timeline rename evaluated
the whole design again (about 70 s on the Prusa Core One assembly, and again
for undo). When the only change is one feature's name, the model renames its
result, sketch and construction geometry in place and takes the renamed
design's fingerprints. A rename other features follow (a plane a sketch is on)
or of a failed feature still evaluates the design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Guard cheap commands on designs with thousands of bodies, plus a scaling bench (#3044) (#3121)

Cheap commands stay cheap on designs with thousands of bodies, with a scaling bench.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:08:59 -04:00
SS-ENandClaude Opus 5.5 34afcf75c1 Joints: a loop that meets only to within nanometres still moves (#3475)
A closed loop whose joints meet a few nanometres apart along their parallel
axes (faces modelled that far apart) passes the conflict check, but the loop
solver wanted 1e-7 mm before it moved anything. No value of a planar loop
takes such a gap out, so every drive of it stopped at once (stopped_by_loop,
fraction 0) and joint.reach called the loop open.

The solver now closes such a loop as far as it comes and, when what is left
is within the conflict check, keeps it that closed on the way. joint.drive,
joint.reach and the nearby pose of the dof count share it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:41:37 +00:00
932d8d6b4f Tessellation: trimmed faces of display meshes stay on their surfaces (#1653)
* Tessellation: trimmed faces of display meshes stay on their surfaces

Display meshes triangulate a trimmed face in parameters scaled by the
surface's speed along each, so a strip stretched along one parameter is
not fanned across, and every boundary segment stays a constraint: one that
crosses another is split where they meet. Boolean meshes keep truck's way.

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

* Tessellation: one boundary rule for every mesh; drop the #1648 retry

A boundary segment that crosses one already added is split where they
meet in Boolean meshes too, which keeps a pinched face (#1648) whole
without the snap-and-retry. Faces whose segments all go in mesh as
before.

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

* Robustness goldens: seeds 1, 23, 55, 64 and 86 re-recorded on current main for #1653

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

* Thread preview test: wait for the stale fit error to clear before the next preview

After switching back from M6x1.3 to M8x1.3, wait_feature_preview stopped at the M6 error still on
screen when the new preview started a few frames later (seen on Linux CI with #1653's meshing).

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

* Robustness goldens: the slow seeds 14, 20 and 40 re-recorded for #1653 from the merge queue

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

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:49:45 +00:00
fkirill fb87cde6a1 Sketch: persist Shift-picked midpoints with ordinary constraints (#2087) (#2378) 2026-10-20 16:26:19 +00:00
Victor Santos Oliveira 5305d7c82d Clarify delayed detection in unclean-exit reports (#3640) 2026-10-20 16:05:55 +00:00
Colin Faulkingham 59cd55f531 Sketch: keep three-point arcs attached to the through point (#2166) (#3610)
* claim: #2166

* Fix three-point arc through-point attachments (#2166)
2026-10-20 15:55:58 +00:00
kaltenbrunnermarkusandClaude Opus 5.5 2668518079 Section Analysis: dragging the cut stays smooth on large parts (#3616)
On an imported part with 2.5 million triangles, every frame of a Section
Analysis drag looked up the picked face's plane several times (each lookup
tessellates the whole body, about 0.1 s) and cut every triangle again for
the outline (38 ms).

- planar_face keeps its last answers for the model state they were found in.
- The outline cuts through a SectionIndex: the mesh's triangles sorted into
  slabs along the cut's normal, kept per mesh while the normal stays, so a
  cut looks at one slab (0.06 ms on that part) and finds the same segments.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:54:04 +00:00
rlartotandClaude Sonnet 5.5 e29a57dc3a inspect.interference/clearance: list mesh bodies as unchecked (#3249) (#3258)
* claim: #3249

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* inspect.interference/clearance: list mesh bodies as unchecked

Mesh bodies (STL, 3MF and OBJ imports) were filtered out before any pair
was formed, so two overlapping STL imports reported count 0 and an empty
unchecked list, which reads as "no collision". Each mesh body is now
listed in `unchecked` as {a: name, b: null, reason: "mesh body: not
checked"} by both commands. inspect.clearance also stops refusing a mesh
body named in `bodies` or `against` as "no solid body"; it is listed
instead. The parameter docs and the assemblies guide say so.

Fixes #3249

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* docs: separate the mesh-body note from the paragraph above

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 15:42:39 +00:00
JakobonlyandClaude Opus 5.5 23b0120759 Sketch: a radius dimension past an arc's end extends the arc to its arrow (#3393) (#3408)
* claim: #3393

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

* Sketch: a radius dimension past an arc's end extends the arc to its arrow (#3393)

A radius or diameter dimension on an arc whose leader is dragged past the arc's
ends had its arrow pointing at empty space. The radial frame now keeps the arc's
span, and an arrow outside it gets the arc continued from the nearer end round
to the arrow tip and a little past it, as dimension drawings do. Full circles,
ellipses and arrows on the arc are unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:40:26 +00:00
Dahak BuildsandDahakBuilds 7bda28e786 Contact: a check finds bodies crossing face to face, not only a corner inside (#3323) (#3388)
* claim: #3323

* Contact: a check finds bodies crossing face to face, not only a corner inside (#3323)

The collision probe sampled each tessellation's points (vertices, edge
midpoints, triangle centres) against the other body, so plates crossing
face to face - where the crossing strip is smaller than a face triangle -
put no sample point inside and went unreported while inspect.interference
found the overlap. Where an edge of one body passes through the other's
surface, the middle of the chord it cuts lies inside; its distance from
that surface is the depth. Touching and grazing are unchanged. The sweep's
collision work model pays for the added edge pass.

---------

Co-authored-by: DahakBuilds <dahakbuilds@users.noreply.github.com>
2026-10-20 15:21:17 +00:00
JakobonlyandClaude Opus 5.5 2f5f65d6e3 Sketch: an angle dimension between two lines from one point measures the angle between them (#2724) (#3439)
* claim: #2724

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

* Sketch: an angle between two lines from one point measures the angle between them (#2724)

An angle dimension on two lines that share an end (two radii of a circle, the
two sides of a corner) took the acute reading of the lines' directions, so
radii 120° apart measured 60° and 150° apart 30°. With no text position given,
the angle is now measured from the shared end towards the lines' other ends,
the reading anyone would mean; the supplement stays the alternative reading.
Lines that share no end, and dimensions placed with a text position, are
unchanged.

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

* ci: re-run the checks; the run on the last push was cancelled

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:51:06 +00:00
bherbruckandClaude Opus 5.5 9e78e86b92 perf: renaming a body evaluates nothing (#3044) (#3095)
A body rename changes names only, but committing it went through the general
"design changed" path: the whole timeline was evaluated again. On an imported
assembly that means reading the STEP file again and meshing every body (about
100 s on the Prusa Core One, and the same again for undo).

When the only change from the previous design is one body rename (the
features' stored body names and the body-keyed maps the rename updates), the
model now renames the body in its results instead: every body keeps its
geometry and meshes, and the fingerprints are set to the renamed design's so
the next evaluation reuses everything. Undo and redo of the rename take the
same path. A rename the model can't follow that way (the body is changed or
named by a later feature, sheet metal, threads) still evaluates the design as
before.

`bench-open --ops script.json` times commands and their undo on an opened
design, with the view work each leaves.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:18:40 -04:00
amolinari-autotex 4dcca282da claim: #3245 (#3246) 2026-10-20 14:36:04 +00:00
bherbruckandClaude Opus 5.5 e98e065584 Open saved designs from the model they were saved with (#3053) (#3102)
* WIP: adopt the saved model on open (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: tests

* WIP: bench-open opens as Open does

* docs: opening from the saved model

* Tests: an edit after opening evaluates from the feature it changes

* clippy

* Read SOLIDCRAFT_NO_MODEL_CACHE through the env helper

* Open: no picture from the model when the model itself opens; cancel test evaluates

* Saved model: bodies passed through found without writing their record; states list runs of bodies

* Adopted results go into the evaluation cache: an undone edit, or the marker rolled forward, takes them again

* Model cache size guard: the saved model's writer

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:24:34 +00:00
rlartotandClaude Sonnet 5.5 885d2aafbb sketch.create: reject a non-numeric custom plane origin (#3248) (#3252)
* claim: #3248

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* sketch.create: reject a custom plane origin that is not three numbers

A custom plane {origin, x_dir, y_dir} whose origin was given as an
expression ("w/2") or a number with a unit ("20 mm") silently became
[0, 0, 0], while a bad x_dir or y_dir was an error. A missing origin is
still the world origin; one that is present but is not three numbers now
returns a clear error. Expressions are not evaluated, so the saved
format is unchanged.

Fixes #3248

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 14:24:18 +00:00
SS-ENandClaude Opus 5.5 c7bd467be7 joint.inspect: show agents what each joint is attached to (#3453)
* joint.inspect: what each joint is attached to, for agents

A read-only `joint.inspect {joint?}` gives each joint's definition: for each
side the occurrence, its component and path, the snap as saved, and what
each persistent face/edge name refers to (the feature that made it, and the
sketch and curve a swept side came from), whether the origin resolves and
why not; plus kind, offset, angle, flip, values with limits, expressions,
the motion links it is in, and its solve errors and conflicts. Its id ends
in `.inspect`, so agent gates treat it as reading only.

MCP `inspect_design` includes the definitions when the design has joints
(`joints: false` leaves them out). joint.list and joint.inspect share the
motion-link JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: catalog entries for Inspect Joint (en, ja, zh)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:21:54 +00:00
SS-ENandClaude Opus 5.5 fb811fbf21 Sketch trim: drag a path across curves to trim every piece it crosses (#3182)
`sketch.trim.path {points, dry_run?}` walks a polyline and, at each place it
crosses a curve trim may change, removes that piece exactly as `sketch.trim`
would there, in the order crossed and in one undo step. A piece already gone
is not trimmed again; projected geometry, infinite lines, offset outputs and
pieces trim refuses are passed over (reported in `skipped`). It returns each
trim's result and each removed piece as a polyline; `dry_run` reports them
without changing the design.

With Trim on, a press and drag in the viewport draws that path: the pieces it
would remove light up (a dry run of the command) and letting go runs it. A
click still trims the one piece under it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:20:12 +00:00
takuto-NAandbherbruck 843c0bf97a Reduce repeated sketch diagnostics and enable normal-CI profiling regressions (#2355) (#2359)
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 13:55:25 +00:00
rlartotandClaude Sonnet 5.5 95de388ba1 Sheet metal: refuse an edge flange on a bend line (#3234) (#3273)
* claim: #3234

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sheet metal: refuse an edge flange on a flange's bend line (#3234)

A pick on the edge where a bend meets the plate (or the flange panel)
found a panel edge that is already a bend line and made a second flange
on it, leaving a gap and a sheet of two shells. add_flanges_each now
refuses such an edge before anything moves.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 12:46:10 +00:00
takuto-NA d8eb65d26e Make constrained sketch edges follow the cursor (#3360) (#3362)
* claim: #3360

* Fix constrained sketch edge following and final drag coordinates
2026-10-20 12:43:59 +00:00
rlartotandClaude Opus 5.5 c64a611818 Boolean: fail an unverifiable intersection instead of keeping what it should trim (#3244) (#3344)
* claim: #3244

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fail an unverifiable intersection instead of keeping what it should trim (#3244)

When every route of the general boolean fails, it falls back on the sampled answer
(one body whole, or nothing) with a "not verified ... that part is ignored" warning,
even when the bodies' facets cross well past their faceting error. For an
intersection that keeps exactly the material it was asked to trim: teeth standing
0.27 mm out of a trimming cylinder came back untrimmed, and `solidcraft-cli run`
exited 0 with a part 16.918 mm in radius.

An intersection whose sampled answer is contradicted by facets crossing by more than
twice the depth the warning reports (6 x the check mesh tolerance, three times the
faceting error of two meshes) now fails, and the model is kept. Cuts and unions keep
the fallback: shelling a body in two pieces relies on it (a cavity tool grazing the
other piece), with the same depth and ignored volume as this case.

Fixes #3244

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Leave an empty unverified intersection as it was (#3244)

Only a doubted intersection that keeps one body whole is refused. An empty
one ("the bodies are apart") stays a result reported unverified: the clearance
and interference checks rely on it, and main's new
a_pin_through_a_d_bore_of_its_size_still_overlaps_the_flat (#2714) expects it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:37:43 +00:00
bherbruckandClaude Opus 5.5 47108fa063 Open: show a design as its bodies are meshed (#3053) (#3178)
* Open: show the design as its bodies are meshed, off the UI thread (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Open: hold screenshots until the design is meshed; profile lines

* Tests: time-bounded opens

* Open: a Loading parts n/N note while bodies are meshed

* Open: frame the design from outlines while it goes into the window

* Outline bounds: vertices and edges only; test

* Frame watch: the window thread's frame times by open phase (SOLIDCRAFT_PROFILE_FRAMES)

* Open: the picture grows in mesh order and stays once complete; refreshes pace themselves; named frame steps; watchdog test

* Open: outlines measured on the file worker and kept per body

* Open: draw the Home picture under the opened file's own key once meshed

* Format the open wait

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:27:42 +00:00
raykholo ca9ebb44db Sheet metal: refuse a flat pattern that no longer matches the body (#3324) (#3325)
* claim: #3324

* Sheet metal: refuse a flat pattern that no longer matches the body

A feature that is not sheet metal can change a sheet body's material
without its sheet model knowing: a mirror combined into it, an extrude
joined onto it, a fillet or chamfer on a corner. The flat pattern, the
DXF and the bend table then wrote the sheet as it was before, a part
other than the one in the design, with no warning.

The evaluator now notes, per sheet, the first such feature: one that
changes the body's shape while its sheet model stays the same. That
feature warns, and sheet.flat_pattern, sheet.export_dxf (also with
per_body) and sheet.export_bend_table refuse with "not supported yet",
naming it. A sheet metal feature, or machining the sheet keeps as
cut-outs, rebuilds the body from the model: it clears the note and
warns that the change is not kept. Moving the body, splitting its faces
and cosmetic threads leave the flat pattern as it is.

* Sheet metal: a modelled thread is refused in the flat pattern too

Only a cosmetic thread leaves a sheet body's material as it is. A
modelled thread cuts the thread profile into the hole, which the flat
pattern does not have, and a later sheet metal feature dropped it
without a word. It now marks the sheet like a fillet does: it warns,
the flat pattern, DXF and bend table refuse naming it, and the feature
that rebuilds the body says the thread is not kept.
2026-10-20 12:07:24 +00:00
jb69andClaude Opus 5.5 e44dd4ff9e Loft: refuse consecutive sections that lie in one plane (#3412)
A loft through two sketches on the same plane built a flat body of zero
volume and reported success, so the mistake surfaced later, if at all
(a measure of 0, or a boolean that did nothing).

`loft_wires` now checks each pair of consecutive sections: parallel
planes with the second section's centroid in the first's plane are
refused with "loft sections i and i+1 lie in the same plane (no volume
between them)". The tolerance scales with the section's distance from
its plane's origin.

Test: two circles on XY; the loft fails with that message and leaves no
body.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:43:14 +00:00
jb69andClaude Opus 5.5 ebada963a4 Parameters: walk through a flipped plane to its base's expressions (#3481)
`PlaneRef::walk_exprs` stopped at `Flipped`, so a sketch created with
`flip` on an offset plane (`{plane: "XZ", offset: "d", flip: "x"}`) did
not list the offset as an input. Changing `d` rebuilt nothing that read
the sketch, and the model went stale.

`Flipped` now continues to its base, as `Offset` and `AtAngle` do, in
both `walk_exprs` and `walk_exprs_mut`.

Test: a loft between XZ and a flipped XZ plane offset by `d` has the
volume for d = 30, and for d = 50 after `parameters.change`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:43:10 +00:00
raykholo 9666c0be56 Convert to Sheet Metal keeps a plate where it is (#3316)
* claim: Convert to Sheet Metal keeps a plate where it is

* Convert to Sheet Metal keeps a plate where it is

Converted without bend edges, a plate moved by one thickness: a 100 x 60 x 2
plate on XY came out at z -2..0 picked on its top face, 2..4 picked on its
bottom. The sheet's frame (its base panel's bottom face, thickness along its
normal) was put on the plate's far face facing away from the picked face, so
the material grew out into empty space.

The frame now lies on the picked face with z into the material, as Convert
with bend edges does. The sheet is the old one moved back by the thickness:
same orientation, flat pattern, flange directions and pocket layers.

A test converts the plate from either face and checks its bounding box; the
engraved-plate test now checks the plate's position, not only its thickness.
2026-10-20 11:43:07 +00:00
bherbruckandClaude Opus 5.5 83ecf91ae7 perf: autosave writes on a worker thread (#3044) (#3139)
Autosave serialised and wrote the whole design on the window's thread: about
120 ms on the Prusa Core One assembly (its 61 MB STEP text inside), a hitch
once per interval after every change, and more once saves keep the evaluated
model. A tick now takes a snapshot of the design (its Arc and what is hidden)
and writes it on a worker; a later tick takes up the outcome, and no new write
starts while one runs. Crash replays rebase onto the autosave only when the
design is still the one written. Save and close still wait for the write, and
dropping the autosaver lets a running write end.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:00:11 +00:00
953fd7d5f8 Typed parameter contracts and JSON Schemas for solid.box and solid.extrude (#2829) (#2611)
* Spike: typed parameter contracts and JSON Schemas for solid.box and solid.extrude

The schema RFC's spike (plan/schema-spike.md). solid.box and solid.extrude get
typed contracts with generated JSON Schemas (2020-12), checked in front of the
unchanged handlers by a per-session level: off (the default, which changes
nothing), warn (the call runs and gains param_warnings) or strict (the call is
refused and the design left alone). Also engine.describe and the MCP tool
describe_command, `solidcraft-cli mcp --param-check off|warn|strict`, a
FeatureResult type for feature replies, and an env-var audit recorder
(SOLIDCRAFT_PARAM_AUDIT) used to check the contracts against every test.

Local spike branch; not for upstream as is (see the results report).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Parameter contracts: put the audit recorder behind a feature; drop two ignored test keys

The audit recorder is now on only in a build with the engine's `param-audit` feature
and `SOLIDCRAFT_PARAM_AUDIT` set. Its code is still compiled, linted and tested without
the feature. `scripts/param_audit.py` summarises a test run's audit logs, and the schema
module's docs give the steps for converting a further command.

Two tests passed keys their command ignores, which the audit found: `position` on
`solid.box` (`hole_face_tests.rs`) and `symmetric` on `solid.extrude` (`face_tests.rs`).
Both are removed, so the tests build what they always did. Comments no longer call
the contracts a spike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 10:45:08 +00:00
Artem Sushchev ae2ab87b99 Cut: a body with a sealed void no longer comes out empty (#3175)
truck placed a face that no intersection divides (every face of a shell
that meets nothing of the other) by counting the faces a ray crosses,
taking each face's surface as stored. A revolved body's reversed faces
counted the wrong way, and an inward shell (a void, a cut's inverted
tool) was read as a solid: a cut beside a sealed void came out empty,
refused or, with a tool whose bounding box covers the target, taken with
the body gone. Intersections of such bodies came out empty as well.

In an AND the crossings now count as each face is oriented, and an
inward shell holds what lies outside it. An OR keeps truck's count.

The corrected cut let a folded offset through: an arc edge next to a
face it meets tangentially had its ends moved past its middle, and the
arc went the long way round (robustness seed 73's shell). The offset
refuses an arc whose middle crossed its chord, as it does a straight
edge that turned round.

Goldens: seeds 0, 45 and 77 had a cut refused as empty that now takes
what it should; their volumes are checked by hand in the PR.
2026-10-20 10:30:13 +00:00
e9b1a077d7 Delete: check what a delete breaks off the window thread (#2678) (#2679)
* claim: #2678

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: timeline.dependents shares the evaluation cache (#2678)

The check before a delete evaluated the edited design on a fresh model:
it recomputed the whole timeline and left nothing for the delete. It now
evaluates on a copy of the session's model. Adds Ahead::replies for
running such checks off the window thread (the UI part is still to come).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Delete: check what a delete breaks off the window thread (#2678)

Both checks a delete makes first (timeline.dependents for the timeline's
Delete, the selection.delete dry run for Delete on the selection) evaluate
the history after the deleted feature, on the window thread. A part with a
slow feature froze the window for as long as everything after it took
(5.9 s in the report). They now compute off the window thread like undo and
the history marker: within the app's 150 ms wait they carry on at once,
otherwise the window says it computes and the delete (or the confirmation)
follows when the report is in. A design changed meanwhile is checked again;
a second Delete meanwhile is left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run the checks again (the run for 3b94e8e0 was cancelled when the PR left draft)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 06:37:50 +00:00
37a8402cae surface.blend: seam alignment, chain-topology refusal and a dialog (#764) (#2488)
* kernel: a blend runs along chains of several faces and lines closed rims up (#764)

A blend's chain may run along the boundaries of several faces (a tube's rim
along the halves of its wall): each edge continues from its own best-facing
face, read station by station. Before, a chain had to be the edges of one face,
so every rim of a revolved or cylindrical surface was refused.

Two closed chains blend from matching seams: the second chain's start (searched
by arclength, then narrowed) and direction are the ones that put each station
closest to its partner, so rims whose seams sit apart no longer twist. A
picked `seam` point on each chain fixes the start instead. A closed chain
against an open one is refused. Closed chains leave towards each other's
centroid, not a point on the rim.

Stations are read on the edges' own curves, not on the chords of the length
table (they sat up to the chord's sagitta inside a curved edge), and the
reported gap is measured to the curves.

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

* kernel: closed blend chains run round the same way; Hermite strip and seam tests (#764)

The second of two closed chains runs round the same way as the first (by
their area vectors), so a picked seam turns the blend by just the angle
between the seams instead of folding it over. The nearer pairing decides only
when the loops stand almost at right angles.

Tests for the issue's gates: the blend between two offset box rims is the
analytic cubic Hermite strip (control points from the chord) and keeps the
top faces' normal along both seams; picked seams a quarter apart turn the
blend a quarter round.

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

* surface.blend: `seam` picks where each chain's stations start (#764)

A point on each chain, saved with the feature (one pending format change),
moved with the design like `align`, and refused when it is not two points.

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

* surface.blend: a test between two tube rims, their seams picked and saved (#764)

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

* Blend dialog: edges at each end, continuity and bulge per end, stitch (#764)

SURFACE › CREATE › Blend Surface opens a dialog: the edges the blend leaves
and the ones it arrives at, each end's continuity (G0–G3) and bulge, and
Stitch to both bodies. Edit Feature brings it back and keeps what the dialog
does not show (align and seam points). Labels in every locale catalog.

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

* ROADMAP: M8 Blend runs along several faces, pairs closed rims and has a dialog (#764)

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

* kernel: a blend station on the vertex between two support faces is read on the neighbour (#764)

A station where a tube wall's two halves meet sits at a corner of one half's
parameters; with a leave direction that turns round the wall (tubes off each
other's axis) the shape could not be read there and the blend was refused. The
neighbouring chain edge's face holds the same point and reads it.

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

* blend: review clean-ups (#764)

- ChainLine keeps one list of samples instead of four parallel lists.
- frames_on and boundary_gap take the chain's ChainLine instead of rebuilding it.
- A station is read on the neighbouring face only when it sits on the vertex the two edges
  share, each face once, and each face keeps its own walk.
- The seam rule (a closed chain starts at it, an open one runs from the nearer end) lives in
  ChainLine::start_at_seam; pair_with reuses the start of its both-ways search.
- support_faces projects with curveblend::project; loop_area sits with plane_through.
- surface.blend parses `align` and `seam` through one point_pair; the Blend and Ruled dialogs
  share body_and_edges.

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

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 04:26:12 +00:00
Erlis VidalandClaude Opus 5.5 9e15995a74 A cut ending in a face of its target no longer leaves the hole closed by a skin (#2827)
A cut whose tool ends exactly on a face of the target (a hole cut from a pocket's floor up to the
plate's top face) could come out of the general boolean with the target's face whole over the hole
and the tool's end face under it: a skin of no thickness that keeps the hole closed. No volume
shows it and the sampled membership checks see the hole either way, so the result was kept. The
result check now refuses a body with a planar face lying back to back on another of its own planar
faces with nothing behind part of it (`coplanar::has_skin`), and the retries take the cut.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 04:15:37 +00:00
Peppe Bergqvist 926a7353a3 Sheet metal: possible to create flange on a flange (#3274) 2026-10-20 04:02:20 +00:00
fe3b14e798 refactor: Aabb2 and point-built Aabb3 for every hand-rolled box (#2428)
* Iterate instead of indexing in nine loops

Replace index loops that look up neighbours by position with enumerate,
zip(cycle().skip(1)) for wrap-around pairs and zip(skip(1)) for
consecutive pairs. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: ring_edges for the edges of a closed ring

Every walk over a polygon's edges, the last back to the first, spelled
x.iter().zip(x.iter().cycle().skip(1)); eighteen of them now call
geom::ring_edges. Three redundant take(len) calls go with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: Ring, a closed ring that knows it is closed

A Ring holds items in order around a closed polygon, loop or fan; edges()
walks each with the next and the last with the first, corners() each
with both neighbours. Ring<Vec2> has signed_area and even-odd contains.

Loop2::polyline returns a Ring, as do doc's face_loops and
boundary_loops, kernel's coplanar wire samples, CapLoop's segments,
tess pieces' rings, the gizmo's plane quads and the navigation cube's
facets. Their walks take edges() or corners() instead of wrapping an
index; sketch's text-hole test and doc's poly_area become contains and
signed_area. Loop2::joints pairs each segment with the next.

Replaces the ring_edges free function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: Chain, Ring and Path; bounds for a chain of points

Ring becomes Chain<T, Closed> beside Path = Chain<T, Open>: edges()
closes a ring and stops at a path's end, Path::close makes the ring,
and bounds() (the empty box for no points) serves both. Eight
hand-rolled min/max folds over a loop's polyline call bounds(); doc's
flat_polyline returns a Ring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: area and inside on Ring for any planar point

Ring's signed_area and contains take any Planar point (Vec2 or
[f64; 2]). The cam profile in joints_cam is a Ring: its own even-odd
test goes, and its edge walks, gap and area take edges(). face_params'
loop area, the text path's winding, tess pieces' ring area and the
copies in three tests call signed_area or contains; the formulas are
the same term for term.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* doc: sheet's closed outlines go through Ring

dedup takes and returns a Ring (dropping a last point back on the first
is what makes it a ring's), near_outline walks a Ring's edges, and the
copy of it beside fold checks calls it. point_in goes: its callers ask
the Ring. Its formula anchored each crossing on the other end of the
edge, so the two can differ only for a point on an edge to the last
digit; one caller sets such points aside first, the others test a
region's interior point, a cut's midpoint or another outline's corners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* doc: cam tests build their outlines with Ring::from

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: area_vector on Ring<Vec3>; three Newell sums use it

Ring<Vec3>::area_vector is half the sum of a × b round the ring. heal's
loop_area, lip's outer-loop turning and the revolve's loop orientation
summed it by hand with a wrapped index; they take it from the Ring
(the half is exact, and two of them only read its sign).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: Aabb3 from points; boxes built and asked through it

Aabb3 collects from points (skipping non-finite ones, as add does) and
gains inflated, contains and meets. Fifteen hand-rolled min/max folds
over points or boxes in kernel, doc and engine become collect or union,
and their "within tol of the box" tests become inflated(tol).contains.
blend's refused-edge boxes and naming's face index hold an Aabb3.

A NaN point is now outside every box, where the old negated
comparisons let it in; an infinite point no longer stretches a box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: Aabb2, the plane's box; 2D folds and box tuples use it

Aabb2 mirrors Aabb3 (collect, add, union, inflated, contains, meets)
and adds relative, a point's place in the box. sketch's link and
link_loops each had that formula; link_loops' boundaries, prism's
segment and loop boxes, profiles' shape boxes and grid, and ends'
segment box hold an Aabb2 instead of a (low, high) pair. Folds in the
cam, sketch bounds, sheet's perforation grid and the sketch clipboard
collect one.

Boxes are built from the same terms (a round's box keeps c - r - tol).
As with Aabb3, a NaN point is outside every box and an infinite point
no longer stretches one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* kernel: prism's segment boxes are plain Aabb2s

The SegBox alias named a (low, high) pair; Aabb2 says it itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: Chain::bounds is an Aabb2; the boxes #2382 left as pairs follow

Chain::bounds returns an Aabb2. Its callers keep it: sheet's hole and
cut boxes (loop_box), letter boxes, island outlines and line-near-cut
test use meets, contains and inflated; profile's outline overlap and
max_clearance, profiles' box tests and the cam test read its corners.
flat_size and the flat pattern's JSON size are the union of the loops'
boxes (0 × 0 for no loop, where the pair fold gave -∞).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: Aabb3::intersection; hand-grown and hand-met boxes use the box

Aabb3::intersection is the box both hold. ops' four overlap boxes of a
boolean's operands take it, and seven boxes grown by hand (ops' body
boxes, the distance set's bounds, coplanar's face box, eval_more's
padded mesh box, compare's group and region boxes) call inflated with
the same amount on every side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 03:44:49 +00:00
e27d43553c Sketch Dimension: line to circle/arc edge (#1803) (#1863)
A line and a circle or arc can now be dimensioned to the circle's edge
(ConstraintKind::LineCircleDistance, saved as line_circle_distance): the gap
while the line misses the circle (0 is tangency), or how far the edge reaches
past the line when it cuts through (stored as `inside`).

Rebased onto main as one commit (the branch's own changes; its merges of main
dropped). Conflicts resolved: the test mod lines in cmd/sketch.rs (both kept,
sorted), and README.md, whose feature list main moved to docs/capabilities.md;
the line-to-circle dimension is listed there now.



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

Co-authored-by: jyviko <jyviko@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 03:44:16 +00:00
Nathan Daneandbherbruck 25ea9e6419 icons: add the silhouette master and wire it to inspect.silhouette (#2102)
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 03:25:17 +00:00
Colin Faulkinghamandbherbruck fa3ada1983 claim: #2708 (#2739)
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 03:25:05 +00:00
Karl Von Richter 759e22b582 Try exact local cuts after invalid empty numerical results (#1765) (#1858)
Two empty numerical cut attempts stopped the boolean retry sequence before
the exact geometric strategies ran. When the result guard refuses that empty
candidate, the exact strategies (sliced planar, pushed walls, local prism,
sphere lens, seam halves) now try the cut first; if none recovers it, the
original empty candidate and diagnostics are kept, before any displaced
numerical retry.

Rebased onto main as one commit (the branch's own changes, its merges of main
dropped). The only conflict was the new engine regression test next to
main's #1985 STEP export tests in boolean_regression_tests.rs; both are kept.
2026-10-20 03:25:02 +00:00
14b7529465 Loft guide rails (#201) (#3371)
* kernel: loft guide rails (#201)

A loft follows one or two guide rails between its sections: stations are added between each two
sections, each the plain loft there moved so that the point a rail guides lands where the rail
crosses the station's plane — turned about the loft's direction and scaled (or stretched across
to the rail) with one rail, carried, turned and sized between two. The sections and caps stay
exact; the smooth loft runs through sections and stations. Solid and sheet lofts.

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

* doc, engine: solid.loft and surface.loft take guide rails (#201)

`rails` (one or two {sketch, curves}) and `rail_fit` (scale | stretch), stored on Loft and
SurfaceLoft (one pending format note), parsed by the sweep's rail parser (now shared) and built
through the kernel's railed loft. Rails on a loft to a point, or on a surface loft with edge
sections or end continuity, are refused as not supported yet. The rails' sketches are walked as
the feature's references and their components placed like a sweep's.

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

* ui: Rail, Second Rail and Rail Fit in the Loft and Surface Loft dialogs (#201)

The rail inputs and fit row the sweep dialogs have, shared through dialogs_sweep; Edit Feature
brings the rails back.

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

* docs(roadmap): loft guide rails (#201)

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

* loft rails: review clean-ups (#201)

- Sweep and loft rails share one turn-and-size map (`sweep_path::rail_frame`).
- A station's middle and the loft's direction come from one curve through the sections'
  middles (exact derivative) instead of two more plain-loft evaluations per station; at most
  33 wires, so railed faces stay light.
- Nearest points on a section use `param_on` (kept only where the search comes nearer); the
  size uses `Aabb3`; one B-spline builder; anchors keep their nudge.
- The railed and plain lofts share one path in `loft_with_rails` and `loft_sheet_with_rails`.
- The engine leaves the not-yet-supported refusals to the design's evaluation; both lofts start
  their rails from the first section's first point.

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

* loft rails: refuse rails on sections with holes, with a test (#201)

Main's lofts now take sections with holes (#2447); rails shape only the
outlines, so a railed loft of rings is refused until the rails carry the
holes too.

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

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 03:22:30 +00:00
bherbruckandClaude Opus 5.5 d0afc31632 Save the evaluated model with every design: exact, stored once, scaled caps (#3053) (#3093)
* Model cache written by default: exact bodies, stored once, scaled caps (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* bench-open: say why bodies were not kept

* Exact planes and revolutions; budgets for large bodies (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native B-rep: rebuild shells as the kernel made them (closed and seam edges) (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: the model cache and the exact native B-rep (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* bench-open: time a save without the cache too

* Model cache entries are Deflate-compressed (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Model cache: one that does not fit the design file is left out, never a failed save (#3053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 02:31:08 +00:00
Artem Sushchev 80719744cf Union: when the slab past pushed-apart coplanar faces can't be cut off, trim it by editing faces (#2801) 2026-10-20 02:11:23 +00:00
2de99c728e Sheet metal: a corner relief no longer depends on the order the corners are cut in (#3280)
* Sheet metal: a corner relief no longer depends on the order the corners are cut in

The corner reliefs are planned against the base as it is before any of them is
cut, and the two reliefs at the ends of one edge are compared with each
other. Before, every relief was cut into the base as it went, so the corner
after it saw the relief of the corner before it: the square relief reaches
further from its corner than the round one does (4 / cos(α / 2) against 4), so
on a short edge it was left out where the round one was cut, and which of the
two corners kept its relief depended on the order the flanges were made in.

The reliefs themselves are unchanged: a round about the corner through the
angle the base fills there, or the square with that side (a kite where the
corner is not square, its cuts square to the two edges). What is new is that
they are planned first and cut together, so that a corner's relief depends on
the base and on the other reliefs, and not on the order they are cut in. Two
reliefs that would overlap are still refused, now with a note that says so.

Covered: the four relief shapes at corners of 60° and 120°, with the flanges
of equal and of unequal height, through the kernel and through the engine
(where the flat pattern and the DXF keep one clean loop).

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Sheet metal: cover the corner reliefs at a corner that is not square

The engine and the UI had no test at all for a base whose corners are 60° and
120°: the flat pattern, the DXF, the unfold and the rule manager's own square
relief were all untested there. They work; this says so.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Sheet metal: say what happens when a corner relief is left out

The guide now says that the reliefs at the corners of one edge are cut
together, and that two of them that would overlap leave the second one out.
A relief that cannot be cut is reported, not dropped.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Sheet metal: write the corner relief's expected corners out

The engine test hard-coded the corners of the trimmed base; it works them out
instead, and the numbers the test and the sketch share are written with the
precision they need.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: jeremymiribung-blip <jeremymiribung-blip@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-10-20 01:28:34 +00:00
bherbruckandClaude Opus 5.5 9e7a7b260b perf: Save and Save As write on a worker thread (#3044) (#3146)
Save ran on the window's thread: about 130 ms on the Prusa Core One design,
and more than a second once saves keep the evaluated model. The toolbar's
Save and Save As now run the command on a scratch copy of the session on a
worker; the design waits for it as it waits for a file picker (commands are
refused meanwhile), and the outcome is taken up as running the command here
would have left it: the name and links the save gave the design, its file,
its saved state (`Session::adopt_save`), the echo, the error status, the
recent files and the home thumbnail. A tab closing or the app quitting after
Save waits for the write, as does the app's exit. Scripts, the control
channel and the browser build still save in place.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:19:29 +00:00
e661e6f809 inspect.clearance: pins on one cylinder with their hole are answered or reported in seconds (#3027)
* inspect.clearance: pairs whose bodies did not change are not measured again

Each clearance check measured every close pair again, though between two
checks usually only a few bodies change or move. Results are now kept per pair,
keyed by both bodies' geometry and placement and the check's settings; a pair
whose two bodies are unchanged is taken from that cache. With 40 overlapping
parts, a check after moving one of them took 0.8 s instead of 23 s (741 of 780
pairs reused). Pairs that could not be worked out are tried again each time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* inspect.clearance: boxes and placed keys exactly as before the reuse

The pair boxes are taken over the mesh's triangles with finite corners, as a
distance shape's are, so pruning is unchanged. A placed body's identity now
includes its model body's own frame, so two placements are never mistaken for
one another.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* inspect.clearance: a large body is meshed once for all its pairs

Each pair that touches cuts the larger body down to the pair's region with a
kernel boolean, and that boolean meshed the whole body twice: once for its
checks and once inside the intersection. A sheet with 600 faces paid about
2.5 s per pair for meshes that were the same every time.

A body can now keep its meshes for reuse (Body::keeping_meshes); its booleans
take the meshes from there, and the intersection takes its shell meshes
from there too (truck-shapeops: and_with_history_meshed). Clearance uses such
copies for one check. Results are unchanged to the bit. On a 3 x 2 m sheet
with bends and 300 holes, the clip took 0.23 s per pair instead of 2.6 s, and
24 rivets took 5.6 s instead of 16.5 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* inspect.clearance: pins on one cylinder with their hole are answered or reported in seconds

A pin seated line to line in its hole, on its axis, and with a blend running into its wall
(a fillet under a head) sent the boolean through minutes of retries that ended unchecked; a
pin a hair off its hole's axis was passed as clear. Clearance now asks first whether the pair
holds such a pin (from the faces' surfaces alone). On its axis the boolean gets a few tries,
which answer plain pins at once; a hair off, or when those tries fail, the shared volume is
worked out apart by coaxial_overlap, which now also takes the zero-offset, equal-radius case,
moves the hole's wall when a blend keeps the pin's from moving, and checks its band more
tightly. What neither answers is reported as unchecked, naming the pin.

The kernel gains with_attempt_budget: the booleans run inside it get a number of tries of
the kernel's own intersection between them, the same on every machine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* inspect.clearance: the pin tests sit beside the other measured pairs, not at the file's end

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* inspect.clearance: the bodies left whole meet their booleans with their meshes kept

A body that meets several others at one size (a bracket with balls pressed into it) was
meshed again for each pair's intersect: only the clipping used the copies that keep their
meshes. The intersect, the pin work and the union check now take those copies too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Near-coaxial overlap: ten tries per intersection, enough for a flush head pulled back

#1984's headed pin made before its plate needs more than eight.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aac7mP9rthK3WsPpPRvCsF

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-20 00:09:50 +00:00
Colin Faulkingham b622a46624 Fix large modeled threads and thin crest reconstruction (#2209)
* Fix large modeled threads and thin crest reconstruction

Helix stations follow the pitch (radial chord error under 1% of it, 48 to
720 stations), and the faceted rebuild retries at the BSP's precision when
the usual weld tolerance collapses a thin crest facet.

Rebased onto main as one commit (the branch's own commits touched UI files
that later moved to #2263 and #2264). Adapted to main's UnitVec3 thread
axis: `*k` in thread_groove and `*cylinder.axis` in the new flange test.

* Fix icon review revisions for same-timestamp edits
2026-10-20 00:06:44 +00:00
fkirill 3ffd8efffc Loft: preserve holes through matching sketch sections (#2435) (#2447)
Rebase the complete PR change onto current main, preserving changes previously stored in merge commits.
2026-10-19 23:57:07 +00:00
amolinari-autotexandbherbruck 07be3f8852 Paste saved linked designs at the viewport pointer (#2430) (#2433)
* claim: #2430

* feat: copy saved designs between documents as linked components

* fix: keep occurrence movement controls with their document tab

* test: cover linked design clipboard across tabs

* fix: place linked design pastes at the viewport pointer

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-19 23:56:48 +00:00