* STEP import: mesh band faces that wrap around a closed surface correctly
Fixes two related bugs that caused SolidCraft to fail to mesh a face wrapping
all the way around a closed, non-periodic surface (a revolved ridge band, or a
tube-shaped B-spline surface): a missing face, a folded/self-overlapping
triangulation, or a hole in the rendered body.
1. Ring edges that run slightly off their surface. STEP files write a surface
and the curves bounding it to the file's own declared uncertainty, so a
ring edge can legitimately sit a few micrometres off the surface it
bounds. The code finding where a ring edge crosses the planned split line
compared against the edge's raw (off-surface) point using a hardcoded
tolerance far stricter than the file's own precision, rejecting a valid
cut. Now matches against the point on the surface directly below the ring
crossing, respecting the file's declared tolerance.
2. Closed B-spline surfaces, their slits and their slivers. truck only treats
the rotation of a revolved surface as periodic; a face on a B-spline
surface whose control net closes on itself (a tube) wasn't recognized as
wrapping around at all. This produced a folded face, a hole (a boundary
loop landing a full period away from where it should cut), and one
near-zero-width sliver face costing the entire body its tessellation
plan. Adds periodicity detection for closed B-spline surfaces, period-
aware hole-loop alignment, dropping of zero-width slit boundaries, and
isolating a too-thin-to-triangulate face so it no longer takes the rest
of the body down with it. The GPU shader's periodic wrap mirrors the CPU
math so the two don't drift apart.
Regression tests: band_faces_split_when_ring_edges_run_off_their_surface,
band_face_with_a_zero_width_slit_is_still_meshed,
a_sliver_face_too_thin_to_triangulate_does_not_cost_the_body_its_plan.
Ablation-tested: disabling either mechanism in (2) reproduces its target
test's original failure message while leaving the other test unaffected.
* STEP import: mesh the band's split faces, not just split them
jyviko asked whether a band wall missing from the interactive app's render
was a shading issue or a genuinely missing face. It was missing: the ring-
edge split fix made the band's topology correct, but plan_face still
produced zero triangles for the two new sub-faces.
Two causes, both in plan_face (crates/kernel/src/tess/mod.rs):
1. The tolerance for how far an imported boundary may lie off its surface
was tied to the requested mesh's chord tolerance instead of to the
body's own size. A finer display tolerance could then reject a boundary
a coarser one had just accepted, so the face would drop out exactly when
zooming in on it.
2. Near a sharp corner of this flat-panelled band, several boundary points
that are legitimately a few micrometres apart (within the STEP file's
own declared precision) all projected onto the same surface point. The
boundary loop then repeated a point in (u, v), which a triangulator
can't follow, so the face's plan failed outright. One face without a
plan costs the whole body its plan, and the fallback mesher can't
handle a closed B-spline surface either, leaving both halves of the
split band with no triangles at all: present in the topology, invisible
in every renderer, and absent from the body's volume.
Fix: scale the off-surface tolerance to the body's own extent (new
Rules::extent), and collapse a run of boundary nodes that land on the same
surface point to one (drop_collapsed) before triangulating.
Regression test additions to band_faces_split_when_ring_edges_run_off_their_surface:
the body's volume now matches the analytic pentagon prism (was a third of
it - only the top cap - before this fix); display_plan leaves no face
without triangles from coarse to fine tolerance; and the actual tolerance
and facet angle the viewport draws with gives the split band its full
lateral area on both halves.
Confirmed on review_band_big.step, the fixture behind the review screenshots:
volume's relative error against the analytic pentagon prism drops from
1.4e-4 to 4.1e-6, and the band wall now occludes the floor grid behind it
in a snapshot the same way the top and bottom caps always did.
Full kernel suite: 739 tests, 0 failed.
* STEP import: hold an imported boundary to the file's own tolerance, not the part's size
jyviko: the off-surface acceptance added in the last commit
(rules.extent * OFF_SURFACE) scaled with the part, so a 2 m part
silently accepted a boundary 2 mm off its surface while a 20 mm copy
of the same part accepted only 0.02 mm. A STEP file's own declared
uncertainty does not grow with the part, so the bound shouldn't either.
Body gains boundary_tol: Option<f64>, Some(file's declared tolerance)
for a body read from STEP (crate::step_in::Units::tol, already used
by the ring-split fix in split.rs), None for anything this kernel
built. It's set before the import's own unmeshed-face check runs, and
carried over by the two face-split operations that re-cut an imported
surface (splitface.rs, outline.rs), since the file's uncertainty still
describes those boundaries afterward.
plan_face's accept now reads: the file's own tolerance when the body
declares one, or the old chord-tolerance/extent bound otherwise. The
old bound's tol * 10.0 term had to go too, not just the extent term:
at a 2 m part's typical display tolerance that term alone is ~20 mm,
nearly ten times the extent bound jyviko objected to, so keeping it
would have left the exact 2 m/2 mm example still silently accepted.
Two new tests in step_in/tests.rs:
- band_faces_split_when_a_scaled_copy_runs_off_its_surface: the same
band fixture at a realistic 2 m scale with the same absolute slop
and declared uncertainty as the small one, meshes the same way at
every chord tolerance from coarse to fine.
- off_surface_budget_follows_the_file_not_the_part_size: the actual
regression. The same off-surface error is accepted or refused the
same way whether the part is 20 mm or 2 m; a 2 mm error against a
1 µm declared uncertainty is refused at both sizes now (accepted at
2 m under the old bound, refused at 20 mm - that asymmetry was the
bug). Checked at both a coarse and a fine display tolerance.
Known gap, not fixed here: a STEP-imported body saved into a
.solidcraft design and reopened loses boundary_tol (native.rs's
decoder has no field to read it from) and falls back to the old
extent-based bound on reload. Today's design format has no concept of
this value at all; persisting it is a real format change, out of
scope for this fix.
Full kernel suite: 776 tests, 0 failed. clippy -D warnings clean.
* Display plans for STEP threads and pointed ends (#3847)
Three faces cost a threaded or pointed STEP part its whole display plan, so it
was drawn from the reference mesh: cracked, faceted, and never refined up close.
- A loop through a pole (a cone's apex on the boundary) seemed to wind around
the surface: the jump at the apex, unwrapped to the nearest copy, was
arbitrary. The loop is turned to end where it leaves the pole, so the step
that closes it is an ordinary one (`seam::close_through_pole`).
- Projection onto a spline with hundreds of spans (a helical flank) restarted
from a 16 x 16 grid and settled on another turn; it now starts from two
points per span, capped.
- Sag refinement no longer inserts a point exactly on a boundary side, which
split the side and failed the face.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* tess: a refused face's edges are divided finer and it is planned again (#2814)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* tess: sliver faces plan empty; a face no method plans is filled flat from its nodes (#2814)
A face whose boundary runs out and back along itself (two edges between the
same vertices, a rounding apart, as STEP files write slivers) encloses nothing
and is planned with no triangles. A face still refused after its edges are
divided finer (a cusp, a sliver whose sides sag across each other, a small cap
at a pole) is filled flat from its shared boundary nodes, so the rest of the
body keeps its display plan instead of falling back to the reference mesh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* display mesh tests: a rim the chord tolerance leaves a line is divided again (#2814)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* portable B-rep: up to 8192 control points along a spline direction (#2814)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* solidcraft-cli: --debug logs what the engine reports to stderr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* STEP import: a spline written over a tiny parameter range is read over one (#2814)
Some writers parameterize splines over a few millionths (a humanoid robot's
edges span 5.4e-6). The kernel tells parameters apart only to 1e-6, so over
such a range it evaluated the curve's end wrongly, and trimming an edge to its
vertices dropped a control point: the bodies could not be stored exactly and
the whole model cache was left out of the design. The knots are now stretched
to span one; the curve is the same and edges are trimmed by their vertices, so
nothing read from the file refers to the old parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* model cache: a body keeps its file's declared accuracy (#2814)
A body restored from the cache dropped the accuracy the file it was read from
declares, so the planner held its boundaries to the kernel's own bound and a
reopened design drew differently from the import. It is stored with the body's
runtime values, and only when there is one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* tess: a boundary within a hundredth of its body lies on its surface, at every quality (#2814)
#1721 held a body read from a file to the accuracy the file declares. A writer
declares what it aims for, not a bound its edges keep: a 251-body robot
assembly declaring 0.01 µm has boundaries 10 to 30 µm off their surfaces, and
95 of its bodies lost their display plans. The bound is a hundredth of the
body again, what Normal quality always allowed, and now the same at every
quality, so zooming in still never drops a face (#1721's point). A file
declaring more raises it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* STEP import: keep faces whose edge vertices sit slightly off their curves
Some exporters write an edge's vertex beside the end of its curve, further off
than the accuracy the file declares (a surface export declares
0.01 mm and puts a vertex 0.19 mm off two spline ends). The reader refused
such edges and left the face, and with single-face bodies the body, out.
A vertex may now sit up to the file's tolerance or 1/1000 of the model's size
(the diagonal around its VERTEX_POINTs), whichever is larger, never more than
the existing 1 mm ceiling. Spline and open-conic edges have the end control
point moved onto the vertex, so every such edge meets its vertices exactly.
The file's declared accuracy still governs closed-curve, seam and circle
checks.
* tess, io: review clean-ups (#2814)
- A sliver face is one whose loops are all slit: drop_slits finds it in the
retry after a failed plan, so the pre-check on every face (folded) is gone.
- The flat fill uses cdt::triangulate (a lenient fill that leaves out sides
crossing one already kept) and its flood-fill inside test, and the plane
from geom::Plane.
- Refinement only builds its indexes when a face was refused, takes the
faces to plan again from edge_faces, and re-samples edges on the pool;
Rules::finer for the finer rules.
- One PackageWriter::fits for the model cache, the preview and the thumbnail.
- The band split's ring tolerance lives in split.rs; comments say what the
off-surface bound is now.
- The CLI logger prints Level::as_str().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
---------
Co-authored-by: David Lackovic <lackovicdavid1234@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Ray <raykholo@users.noreply.github.com>
An agent's design changes over the control channel (engine.execute, engine.batch) are evaluated
ahead on a copy of the session on an `agent` worker, then run on the window thread reusing every
feature computed there (doc::KeptResults, whatever the bounded evaluation cache dropped); the
worker also makes the display meshes of the bodies they end with. Exports run on a copy. While a
change computes, or the agent cursor acts one out, ui.*, inspection and screenshots answer; later
design changes wait their turn. A chip names the command with Stop. A person's edit meanwhile goes
first; a replaced design refuses the request. Preference: Preferences > AI, agent_off_thread, off
by default.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #3654
* Control channel: control.running lists what the app is running, across connections (#3654)
control.pending answers only for the connection that asks, so a second client,
the MCP bridge after a reconnect or a script saw {"pending": null} while the app
was busy. control.running answers for the whole app, the same on every
connection and at once (the connection's own thread answers it, also while it
is busy with a timed-out request): every request the app has been sent and not
answered yet, oldest first, with its connection, id, method, command id (or a
batch's step count), seconds since it arrived and whether it still waits its
turn. A timed-out request stays listed until the app answers it, also after its
connection closed. Parameters are never shown.
The window marks a request started when it takes it (ControlRequest.started),
which tells queued from running. The MCP server gets a read-only `running` tool.
---------
Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
* claim: #2089 (3MF production extension: review follow-ups)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 3MF import: production extension review follow-ups (#2089)
Follow-ups from the review of #2127:
- Only the root model's build is read; other parts' build sections are
ignored as the spec asks (a slicer's child build pointing at a thumbnail
no longer matters).
- A model part that can't be read fails only the placements that need it;
the package-wide caps (part names, 1 GB of model XML) still refuse the
package. The root XML is dropped once parsed and each part is read
against the remaining budget.
- Loops are reported apart from depth, keyed by (part, object).
- Messages name parts as written; objects are kept in id order so parts are
read in the same order every run; %XX escapes in part names are decoded;
the model unit is a local of the parser.
- Tests: a component without p:path stays in its part (T1), the same id in
two parts is no loop (T2), a slicer-shaped project (T3), the placement
budget across parts (T4), a loop through three parts and a chain past the
depth cap (T5), broken parts and child builds (T6), escaped names; the
hostile-parts test asserts its messages; the mutation test also mutates
the parts' XML and zips it again.
- Guide: what slicer-only data is not read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #3353
* Sheet metal: mirrored and copied sheet bodies stay sheet metal
Mirroring a sheet metal body into a new body gave a plain solid: the
opposite-hand bracket had no flat pattern, DXF or bend table
("`Body1 (2)` is not a sheet metal body"). Copies from a body pattern or
Move with copy lost their sheet model the same way.
A copy now takes the sheet model with it (SheetBody::copied). A rigid
copy moves the flat frame. A mirror moves the frame with its x axis
turned round, so the thickness side stays, and mirrors everything in
flat coordinates (x -> -x): base, flange bend lines (run from their far
end, with their end extensions, slopes and tears swapped), panel shapes,
cut-outs, pockets, bores, letters and base sketch lines. Arcs stay exact
and loops keep their sense. A perforation field of a mirrored copy runs
its grid from the right, so its holes are the mirror image too. The flat
length, bends and K-factor are unchanged, and the copy takes further
sheet metal features of its own.
Mirror with combine is unchanged: the joined body has no single sheet
model yet. Mirroring or patterning flange features is still refused.
Sheet models are derived from the timeline, so no design format change.
* Format: note the saved from_right of a mirrored sheet's perforation field
The new `from_right` flag is saved in the design, so it gets a pending
format note. No migration: a field without it runs from the left as
before.
Sketches drive surface extrudes, revolves, sweeps and lofts, so the Surface
tab gets the same Sketch panel as Model and Sheet Metal. A sketch started
there keeps the tab until it opens.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #2501
* English (UK) as a language: a plain catalogue of the 25 messages UK English spells differently (#2501)
assets/locales/en-GB.json holds only the English messages whose spelling differs in UK English (Centre, Customise, Organisation, Licence, colours, towards, ...), in UK spelling; every other message falls back to English. One registration in i18n.rs (English first, then by id), a test that shows UK spelling and English for the rest, a row in ATTRIBUTION.md, and the docs/localization.md note. The translation-coverage checks skip a regional English catalogue, which has no gaps to report.
Fixes#2501
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Build on #1426's import components and nested STEP assembly instances.
Replace placeholder solid labels with meaningful part or instance names.
Translate IGES groups and subfigure instances through the existing STEP
component path, preserving nesting and placements and sewing surfaces
within each group. Bound assembly walks and reuse work buffers.
Related to:
- tangency-labs/solidcraft#1386
- tangency-labs/solidcraft#1426
Co-authored-by: Codex <noreply@openai.com>
* Ribbon: New Sketch on the Sheet Metal tab again; tests pin sketch enter/exit
Before the ribbon redesign the Sheet Metal tab started with New Sketch. It
gets its own Sketch panel again, like the Model tab's, and a sketch started
there keeps the tab until it opens.
Workspace tests pin the ways into and out of a sketch: New Sketch then a
plane, a double-click in the timeline or the browser, and the browser's Edit
Sketch all open the SKETCH tab looking at the sketch. Finish Sketch from the
palette or the ribbon lands on MODEL with the earlier view back. Escape and
Enter keep the sketch open (#1804), and Undo after Finish reopens it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: ribbon layout lists Sheet Metal's Sketch panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Workspace test: press the keys well apart
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #3242
* MCP batch: rollback restores the design the batch began in (#3242)
A failed batch with rollback: true reported rolled_back: true even when the
design never went back. Two defects, both fixed:
- file.new (and any document-replacing command) replaced the session with it,
taking its checkpoints down too, so a batch that failed after file.new had
nothing to restore. Session::replace_design now carries the checkpoints over
to the fresh session: the checkpoint belongs to the batch, not to the
session that happened to be holding the document.
- rolled_back echoed the requested flag. It now reports whether the design is
actually back: the app answers engine.batch with a new restored field, and
the MCP tool reports restored && rollback. An app that predates the field
falls back to undone/completed.
* Batch: answer with main's rolled_back field after the merge (#3242)
Upstream #2900 put the real restore check on engine.batch as
`rolled_back` while this branch named it `restored`; the merge kept
main's control test and this branch's field, and CI caught it.
Standardize on main's wire name: the app answers `rolled_back`, the
MCP tool reads it (fallback for older apps unchanged), docs and tests
follow. The checkpoint carry-over across file.new is unchanged.
---------
Co-authored-by: DahakBuilds <dahakbuilds@users.noreply.github.com>
* CI: give the slowest tests a shard each
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: spread the slow tests on pull requests too, and keep both junit files
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: pull requests skip the goldens and the action matrix sample unless the kernel changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: which pull requests run the goldens
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: run the tests in one job on a big runner, slowest first
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: allow the 32 vCPU runner label in actionlint, read junit.xml in the golden hint
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* ui: spacing, radius and type tokens, per-tab accents, agent and on-accent tokens
* ui: per-tab accent behaviour and a larger application bar with a Start page button
* icons: silhouette master and flat assistant and agent link icons (open for review)
* icons: apply review notes (sparkle assistant, refined plug, silhouette halo)
* icons: wire the silhouette master to inspect.silhouette; record the accepted icons
* ui: application bar to the design: separators, Save, house, assistant icon, readable hovers
* ui: ribbon structure: Model to View tabs, contextual Sketch and Sheet Metal, migrated pins
* ui: ribbon painting to the compact design
* ui: smaller icon button highlight and assistant mark on the bubble corner
* ui: short ribbon labels that wrap at spaces
* ui: ribbon buttons fit their labels and Finish Sketch shows its full name
* ui: constraint gallery, workspace menu, section list headings and richer tooltips
* ui: lighter selected tab label, ribbon surface and dimmer bar separators match the dark design
* ui: document tabs as rounded chips with a dimmer inactive label
* ui: document tabs fit their names and show close only when active or hovered
* icons: close and plus masters for the document tabs (open for review)
* ui: centre the new-tab plus and overflow button on the bar
* ui: a quiet rule under the ribbon tabs on non-contextual tabs
* ui: the application bar mark separates its faces with the bar colour, not white
* ui: open ribbon section label uses the selected tint and a chevron
* ui: dim the tab close and plus icons until hovered
* ui: every ribbon dropdown uses the chevron, the workspace card included
* ui: the active document tab uses the design's surface colour
* ui: the bug button's right padding mirrors its icon side
* ui: square constraint cells and a rule before the grid
* ui: constraint cells back to their earlier size, keeping the rule
* ui: smaller constraint cells and icons, centred in the section
* ui: constraint cells fit the height of the large buttons' icon and label
* ui: sketch Create titles share the other labels' line
* ui: format the application bar and ribbon changes
* ui: active tab surface token and the colour and hover tests that follow the redesign
* tests: the merged parameter and narrow toolbar tests follow the compact ribbon
* i18n: the Malay Gap label has an English source and its Japanese and Chinese translations
* apply_off_thread tests: the evaluation lingers in tests, so still computing never depends on machine speed
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Save As and Open use the browser's file dialogs where the File System Access API exists; Save writes back to the same file, its handle kept in IndexedDB across reloads. File > Download design added. Other browsers keep storing designs in OPFS.
* 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>
While editing a dimension's value, a single click on another driving
dimension now inserts its parameter name (a double-click no longer needed,
and its second click does not insert it twice). A lone value such as `10`
or `10 mm` is replaced by the name; an expression in progress keeps what
was typed and gets the name at the cursor (replacing selected text), so
`10 + ` becomes `10 + d10`. The cursor ends after the name and the field
keeps the keyboard.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* 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>
`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>
* Mouse gestures: a ring of tools on the right button, per studio and customizable
Preferences > Navigation gets a Mouse gestures switch and a Customize window (number of
tools and their place, per studio). Gestures need the SolidWorks mouse mode and turn the
right-drag orbit off. The ring is 280/350 pt, one zone per tool, alternating tints (three
when the count is odd). The radial context menu is unchanged.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dwcq9eNUMZ1jNeKvWd1bcZ
* Mouse gestures: the ring opens on the right-button press; the right-click menus are off while gestures are on
The ring opens at once on the press, even while a tool or dialog is active. While gestures are
active the viewport's right-click context menu, the right-click that finishes a tool and the
radial context menu no longer fire. With gestures off nothing changes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dwcq9eNUMZ1jNeKvWd1bcZ
* Mouse gestures: the ring opens after 4 px with the right button held; a plain right-click keeps its menu
The ring no longer opens on the press. It opens where the button went down once
the pointer has moved 4 points with the button held, even while a tool is in
progress. A right-click that does not move opens the usual context menu (or
finishes the tool) again; the release that ends a gesture opens no menu. The
radial menu stays off while gestures are active.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dwcq9eNUMZ1jNeKvWd1bcZ
* Re-trigger CI (the run was cancelled when approved-for-ci was added)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dwcq9eNUMZ1jNeKvWd1bcZ
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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.
The two reliefs that would overlap are still resolved one after the other: the
second one is left out, with a note. The corners come out in flange-index
order, so which of the two keeps its relief depends on the order the flanges
were made in.
Two corners that share an edge never come this far: each relief reaches
exactly r along that edge, and a relief is refused unless 2r fits in it. Two
corners that do not share one can: the square relief at a corner of 120°
reaches 2r from it along the bisector, so at the two 120° corners opposite
each other in a rhombus the two reach each other.
Two reliefs that would overlap are now left out together, each with a note
naming the other, so that which of them is cut does not depend on the order
the flanges were made in.
Fixes#3417.
Co-authored-by: jeremymiribung-blip <jeremymiribung-blip@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
The retries in boolean_whole_inner mesh every candidate to compare its
volume. A nudged union at the coarse tolerance could come out with a face
on a cone whose trim loop ran metres along the cone's continuation (edges
up to 12.7 m outside a 155 mm part); meshing it took seconds to minutes per
face and once never finished, though its volume (+-1e10 mm3) failed the
check anyway.
check() first samples the candidate's edges (16 per edge) and discards a
candidate with a point further outside the operands' mesh box than 5% of
their size (or a non-finite point): no edge of a union, cut or
intersection can lie there. The reason goes into the new
BooleanDiagnostics::rejected (omitted from JSON when empty). Results of
sound candidates are unchanged.
On the reporting design the PitchTail join drops from 18.7 s to 4.3 s with
the same result; the regression tests synthesize a cone face whose loop
runs off its surface.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
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>
* 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>
* 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>
* 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>