Commit Graph

460 Commits

Author SHA1 Message Date
0xlillith 1b55174743 Render: isolate lens saves and retain bounded previews
Save only lens parameters on existing CPU/GPU jobs while keeping unrelated
draft settings dirty. Retain pinned checkpoints through preview and status,
and release host-only snapshots outside the status lock.

Use the admitted PNG bound for registered-executor checkpoint slots. Preserve
executor work credits, final publication and regrade ownership. Add regression
coverage and align help, translations and capability documentation.
2026-11-05 04:22:03 +01:00
0xlillith 8829c1c3c5 Render: restore saved shots, progressive images and exports 2026-11-05 02:08:14 +01:00
PaddleStroke af5644aee6 Ribbon: switch panel menus on hover after the first click (#11447) (#11713)
* claim: #11447

* Add an option to switch ribbon panel menus on hover

* Update profile membership count for ribbon hover setting
2026-11-04 22:22:38 +00:00
PaddleStroke c4a63a1d43 GPU path tracer: don't rerun a setup that crashed in the driver (#9236) (#9666)
* claim: #9236

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

* GPU path tracer: don't rerun a setup that crashed in the driver (#9236)

A driver that takes the process down while it compiles the path tracer
(RADV on an RX 7600) is beyond error scopes and unwind guards. A running
setup now keeps gpu-path-setup.json (its adapter key) in the settings
folder, removed when the setup answers or the app closes normally. A
launch that finds it for the same adapter leaves the setup failed with an
explanation and a Try again button; CPU ray tracing stays available, and
a driver update changes the key and so tries again.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:54:45 +00:00
JK3D d9421bf2ca Browser: an option to start folders closed and open rows only on request (#8768) (#8771)
* claim: #8768

* feat: Browser folders start closed and find a body only when asked, behind a preference

Preferences › Layout › Browser rows start closed (`browser_rows_start_closed`, off by
default) starts the Browser's folders closed, and placements too whatever "Components start
open" (#9274) says. Rows then open with their arrow, Expand All or Find in Browser, which opens
just the rows down to its body and closes again the ones the last find opened. A body picked in
the 3D view leaves the Browser as it is, and only the body Find in Browser found has the rows
above it marked, so a large assembly's placements are not named again after each edit.

With the preference off, folders start open, placements start as "Components start open"
says, and a pick in the 3D view opens the Browser down to its body, as on main. Find in Browser
looks the body's placement up once and opens its rows before drawing, instead of each row
testing the revealed path while it is drawn.

* Browser tests: read the row order from the Browser's content frame

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:29:23 +00:00
raykholo 3aee35fe01 Web app: keep unsaved work in the browser instead of the leave-page box (#10220) (#10221)
* claim: #10220

* Web app: keep unsaved work in the browser instead of the leave-page box (#10220)

A new preference, off by default, on Preferences > General > Saving (web
app only): "Keep unsaved work in this browser instead of asking when the
tab closes". With it on, each open design with unsaved changes keeps a
copy in the browser's private storage (keep-<page>-<design>.sc/.json),
written once the design has been still for 0.8 s (at most 4 s after the
first change it lacks) and again on pagehide or when the page is hidden.
While every copy is current, beforeunload doesn't ask. A copy goes when
its design is saved, closed, or the preference is turned off.

The next visit offers the copies newer than their design's last save in
SolidCraft's own window, "Restore unsaved design: <name>, changed
<time>?", one window for several, with Restore, Open saved version, and
Discard. A page holds a Web Lock while it runs, so another open tab's
copies are never offered or removed.

With the preference off, the single autosave and the browser's prompt
work exactly as before.

* Web app: the restore window comes from the UI kit's recovery module (#10220)

The kit's text and divider helpers are private to the UI crate, so the
"Restore unsaved design?" window moves to recovery_ui
(browser_copies_window), beside the desktop recovery window, and the web
shell only maps its answer. Also keeps the window handle for the page
listeners instead of using the one moved into the app.

* Web app: restore window buttons sit in one row (#10220)

* Web app: keep unsaved work and still ask, unless told not to (#10220)

Keeping a copy in the browser no longer replaces the browser's leave-page
box by itself. A second option, shown once work is kept and on by default,
leaves the box in place; off, the box appears only while a copy isn't current.

* Web app: kept-copy review fixes (#10220)

A failed write of a kept copy waits 30 s before the next try, a recovery
copy is dropped only after the remembered saved file has opened, the
preference is documented under its current label, and the ui.prefs list
is sorted again.
2026-11-04 21:26:23 +00:00
raykholo 5562d9c6c8 Quick-access bar: pin ribbon commands to the application bar (#10223) (#10224)
* claim: #10223

* Quick-access bar: pin ribbon commands to the application bar (#10223)

A command's menu (a ribbon button, a section drop-down row or a command
search result) has Add to Quick Access: the command's own icon joins the
application bar after Save, Undo and Redo. A click starts it as its
ribbon button does, a drag along the bar reorders the buttons, and the
button's menu has Remove from Quick Access. Empty, and so not drawn, by
default; it holds up to 24 commands.

The list is kept with the toolbar's customisations (`quick_access` in
the toolbar's saved document), so it is saved, exported with the
keyboard shortcuts and carried by settings profiles.

* Quick access: a pinned family variant no longer replaces the ribbon choice

A quick-access click now starts the pinned command with start_pinned, so it
starts exactly that command and leaves the sketch family remembered choice
(the main ribbon button) unchanged.
2026-11-04 21:26:13 +00:00
Arjan Duijs 0690ee5875 Web app: run as a modelling editor inside another page that keeps the design files (#8517) (#11273)
* claim: #8517

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

* web: editing embed for host pages (?embed=editor, #8517)

A host page that keeps the files itself and frames SolidCraft for modelling needs
the modelling UI without the application chrome. `?embed=editor` runs the web app
as such an embed:

- no application bar (logo, File menu, document tabs, update, help, report a bug)
  and no start page; the layout is docked;
- the ribbon, browser, timeline and properties stay; Ctrl+Z and Ctrl+Y still undo
  and redo;
- Ctrl+N, Ctrl+O, Ctrl+S, Ctrl+W and Ctrl+, are left to the host;
- nothing is kept in the browser (no stored designs, autosave, recovery prompt,
  preferences or leave-page prompt), as with the existing viewer embed.

The existing `?embed&open=` viewer embed is unchanged. SolveApp gets one flag,
`embed_editor`, for desktop and test hosts too.

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

* control: document.export / document.import / document.saved for a host (#8517)

An editing embed's host keeps the design itself. Three control methods
(window.solidcraft.request in the browser; the desktop control channel too; not
the agent chat's, whose method list is unchanged):

- `document.export` -> {name, revision, dirty, format: "json", text}: the active
  design as a `.json` design file holds it. `revision` is the one
  `document.revision` answers, so a host that follows the design compares one number.
- `document.import {text, name?}`: replaces the active design with that text,
  timeline and parameters as saved, opened saved with a new `document_id`. It runs
  as a design opened in a tab of its own, with the old tab closed without asking,
  so whatever was open in the old design leaves with it. A text that does not read
  leaves the design as it was. Waits behind a file loading or a design change, as
  commands do, and evaluates on the window thread.
- `document.saved {revision}`: the host saved that revision; refused when the
  design has changed since.

They are control methods, not commands: they act on the app's open design. The
browser API test gets a round trip through all three.

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

---------

Co-authored-by: aduijs <7501203+aduijs@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:23:25 +00:00
froglemonade2 1432131e95 English: command labels in title case, other labels in sentence case (#9085) (#10734)
* English: command labels in title case, other labels in sentence case (#9085)

The rest of #9085 after #9194 (the plural and the spelling) and #9907
(the Preferences page names). Command labels are in title case (422 of
430 were already); the seven in sentence case follow, and Fix/UnFix
reads Fix/Unfix. The UI text skill states the rule. The user docs name
the Preferences footer's button by its current label, Restore page
defaults (#10295).

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

* Navigation bar menu test: the echo names the command by its own label

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

* UI text skill: state the case rule in one short sentence, under the 3600 limit

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

* Localization doc: the case rule in full, with examples

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

* Localization doc: the example button is Restore page defaults now

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:39:52 +00:00
Jackson Oursland fb80454368 User guide: window tour gets a numbered screenshot and a navigation clip (#10323)
* Book: a numbered window screenshot and a navigation clip, from a capture script

The window tour now opens with a screenshot of the real window, its parts numbered in the
order the page describes them, and a short looping clip of orbit, pan, zoom and Home.

examples/demo/doc_shots.py makes both, and any later figure: each shot is a function that
starts a fresh app (its own settings) under Xvfb, drives it over the control channel with
the demo's Demo driver, and writes a still (numbered callouts, palette-optimised PNG) or an
H.264 clip (the drawn pointer of overlay.py, short button labels, idle cut by trim_idle.py)
into docs/book/src/media/. CONTRIBUTING.md says how to redo a figure when the UI changes.

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

* doc_shots: clips keep a moment on each result; the window tour again on 0.29.0

trim_idle.py takes --hold S, the seconds of each idle stretch to keep (default 0.1, as before,
so the README demo is unchanged). The guide's clips keep 0.8 s, so a reader can see what each
step did before the next begins. The window screenshot and the navigation clip are made again
with it, on SolidCraft 0.29.0.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:33:39 +00:00
Dahak Builds dcbedbd805 Don't blindly reopen a design that stopped the last session (#5064) (#10443)
A design that crashes or hangs the app while it is being reopened was
reopened blindly every launch, bricking the app each time. A launch that
reopens designs now marks them in the recovery folder; the marker is
cleared as each design finishes opening. A launch that finds the marker
left behind skips those designs once and says which one the last session
stopped on, so the app starts usable and the loop breaks.

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-04 17:16:15 +00:00
Daniel 4d738cd210 Sketch: an ellipse is drawn with its axes, and D measures its half-axes (#9291) (#9292)
* Sketch: an ellipse comes with its axes, and D measures its half-axes

An ellipse could only be sized by its minor radius, drawn like a circle's
radius, and there was no way to give its full axes. sketch.ellipse now also
adds the major and minor axes as construction lines through the centre.
The major axis runs from the major end to the point opposite it; the minor
one is perpendicular to it, ends on the ellipse, and both are bisected by
the centre. That adds six coordinates and six equations, so the ellipse
keeps its five degrees of freedom, and it needs no new constraint kind and
no format change.

With the axes in place every ellipse size is an ordinary dimension. D on an
axis line gives its length, the whole axis. D on the outline gives a half-
axis, the distance from the centre to an axis end, chosen the way a line
chooses horizontal or vertical but in the ellipse's own frame: text pulled
across the major axis measures along it (the major half-axis), text pulled
out past an end of it measures across it (the minor one). The end on the
text's side is used. Near the boundary between the two, a dimension being
placed keeps the half-axis it had (sketch.dimension's previous_axis, which
the Dimension tool sends), so the preview does not flip. A half-axis and
its whole axis determine each other, so the second becomes a driven
dimension through the existing redundancy path.

Deleting the ellipse leaves its axes, as deleting a Center Rectangle's
edges leaves its construction diagonals. Ellipses without axes, from
existing designs, keep the minor radius as before, and type radius still
gives it.

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

* Guide: an ellipse's axes and half-axis dimensions

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

* Sketch: test that an ellipse's half-axis preview keeps its axis near the boundary

The Dimension tool sends the half-axis its last preview chose, so near the
boundary between the major and the minor half-axis the preview keeps it.
This covers that path from the tool, from both sides of the boundary.

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

* Sketch: an ellipse's axes select and drag with it, as a rectangle's diagonals do

A double-click on a Center Rectangle's edge or diagonal selects its edges
and both diagonals, and dragging a diagonal moves the rectangle without
changing it, because its lines share corner points. An ellipse's axes are
held by constraints instead, so a double-click selected only the ellipse
(and Delete then left the axes behind), and dragging the major axis moved
the ellipse but stretched its minor half-axis.

Sketch::ellipse_axes now finds an ellipse's construction axes (the
detection sketch.dimension already used, moved into the sketch crate).
Sketch::chain treats an ellipse and its two axes as joined, so a
double-click on any of them selects all three. Dragging either axis moves
the ellipse and both axes as one, keeping both half-axes. Dragging the
outline still sets the minor half-axis.

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

* Sketch: a corner test reads its line's id instead of assuming l1

sketch.ellipse now adds two axis lines, which take the first line ids, so
the extend test reads the id of the line it draws from the reply.

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

* Sketch: an ellipse's axes cut a trim, as a Center Rectangle's diagonals do

Trim, extend and break take every other curve as a cutter, construction
lines included, so an ellipse's construction axes stop them as a Center
Rectangle's diagonals do. A test now states it for both shapes, and the
guide says it. The freeform cutter test keeps the axes it used to delete
and asserts the piece a trim removes: from where the major axis crosses
the circle to where the ellipse does.

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

* Sketch: test dragging an ellipse's outline with its axes present

Dragging the outline sets the minor half-axis, and the minor axis line,
held on the ellipse at one end, follows it; the centre and the major
half-axis stay.

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

* Sketch: other dimension types on an ellipse with axes keep the minor radius

Only auto, major and minor give a half-axis on an ellipse drawn with its
axes. Any other type (radius, diameter, horizontal and the rest) gives
what it gives on an ellipse without axes, as the parameter doc says,
instead of quietly becoming a half-axis chosen by the text. The arm for
a missing text_at is dropped: with no text the rule already picks the
minor half-axis.

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

* Sketch: test an ellipse's half-axis dead zone from the major side too

The tests only showed a previous minor half-axis being kept near the
boundary; the point they used on the major side is major by geometry
alone. A point inside the band where geometry says minor now shows a
previous major half-axis kept, in the engine and in the Dimension
tool's preview.

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

* Sketch: a placed ellipse half-axis is the one its preview showed

On an ellipse already sized, a half-axis is added as a driven dimension
on the click, and that command left out the half-axis the preview kept
near the boundary, so the click could place the other one. It now
passes it as the preview does.

The remembered half-axis also comes only from a draft whose text was
placed (one without text is the minor half-axis by default and biased
the first placement), is kept per sketch as well as per picks, and is
forgotten once a dimension is placed or the picks are cleared.

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

* Sketch: say how an ellipse's axes are recognised and what that costs

The axes are read from the constraints, with no mark in the design, so
the doc comment says which lines count, that deleting the midpoint or
point-on-curve constraint leaves an ellipse without axes, and that each
lookup scans curves times constraints.

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

* Sketch: an ellipse with one axis deleted still picks its half-axis by the text

With one construction axis deleted, D on the ellipse fell back to the minor radius wherever the
text was pulled, because the half-axis choice needed both axes. It now reads what is left of
them and chooses as before: the major half-axis goes to the major end (also from the far side,
when the point opposite is gone with its line), the minor one to its axis end, or as the minor
radius when the minor axis is the one deleted. An ellipse with neither axis keeps the minor
radius.

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

* Sketch: the one-axis half-axis test checks what is measured to

The test for an ellipse with one axis deleted checked only the reply's axis and value, which
both ends of an axis give alike. It now checks the dimension's kind and end: the axis end on
the text's side, the major end from either side once the point opposite is gone, a radius once
the minor axis is. With the major axis deleted and no text the reply names the minor axis and
measures to its end, where it used to be a radius with no axis named.

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

* Sketch: an ellipse's minor radius is drawn as its half-axis, without R

A radius on an ellipse is the minor half-axis, and it was drawn as a circle's radius: one
leader from the centre towards the text, marked R, which measures nothing a reader can see.
It is now drawn as the distance from the centre to the minor axis end on the text's side,
the way the half-axis is drawn while the ellipse has its axes, and labelled with the length
alone. This is what D gives once the minor axis line is deleted, and on an ellipse with no
axes.

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

* Sketch: an ellipse's half-axis keeps a radius's stored text

The half-axis frame read the stored text from the middle of the half-axis, so a radius placed
on an ellipse in an existing design would show its text somewhere else. The text is stored from
the centre again, as a radius's always was, and the side drawn to follows it. An unplaced one
sits where a linear dimension to the minor axis end puts its text, and stacked texts step out
from the centre as a radius's do. A view test reads the painted label, so the drawing cannot
go back to R unnoticed.

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

* Sketch: an ellipse moved, turned or scaled takes its axes along

Move, Sketch Transform and Scale moved only the curves picked, so an ellipse moved without its
axes left them where they were: its centre no longer halved them, the minor end left the
ellipse, and the sketch stayed unsolved. A Center Rectangle's diagonals share its corners and
always go along; an ellipse's axes share only the major end. The axes now go with the ellipse,
as they do when it is dragged. Copy is unchanged: it copies no constraints, for a rectangle
either.

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

* Sketch: a preference draws ellipses without their axes, as before

Drawing an ellipse with its construction axes changes what the Ellipse tool makes out of the
box, so the old ellipse stays one setting away: Preferences › Sketch › Draw ellipses with their
axes, on by default. Off, the tool draws the ellipse alone. sketch.ellipse takes axes: false
for the same, and keeps drawing the axes when it is not given.

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

* Sketch: sketch.ellipse refuses an axes value that is not true or false

A string such as "false" was read as not given, so the axes were drawn; it is now refused as
the text commands refuse a non-boolean bold or italic. The move test also covers an ellipse
whose minor axis was deleted: the major axis left goes along.

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

* Sketch: D picks a half-axis on an ellipse without axes too

An ellipse with no axis lines (both deleted, a mirrored copy, one from an older design) gave the
minor radius wherever the text was pulled, so its major half-axis could not be dimensioned on
it. The half-axis is now chosen by where the text is on every ellipse: the major one to the
ellipse's own major end, the minor one as its minor radius. With no text it is the minor
radius, as before.

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

* Sketch: with one half-axis dimensioned, D on the ellipse gives the other

D on an ellipse chose the half-axis by where the text was pulled even when that half-axis was
already held by a dimension, so the second D usually guessed the one already there and was
refused as over-constrained. With a driving dimension already on one half-axis (to an axis
end, the minor radius, or the whole axis along its line or between its ends), D with no type
now gives the other, in either order and with or without the axis lines. With both held it
chooses as before; an explicit major or minor is kept.

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

* Sketch: the held half-axis rule is documented and its cases tested

The Dimension command's parameters and the guide now say that with one half-axis held, D on
the ellipse gives the other. Tests cover the whole axis held as a distance between its ends
and as its line's length with the other axis deleted, and check that a mirrored ellipse has
no axes. The placement test undoes its first dimension, so its second half still tests where
the text is rather than the held half-axis.

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

* Sketch: Transform previews an ellipse with its axes

OK in Sketch Transform moves, turns and scales an ellipse's axes with it, but the window drew
where OK would leave only the picked curves, so the axes stayed put in the preview until OK. An
ellipse picked alone is now drawn with the axis lines it has.

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

* Sketch: the typed-radius ellipse test counts the ellipse's axes

Main's #10282 test drew an ellipse with typed radii and expected one curve; with the
ellipse_axes preference on by default the tool also draws the two axes.

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

* Sketch: an ellipse's remaining axis still drags and selects it

With one axis deleted, dragging the other axis did not hold the ellipse (with the minor axis
left, the drag squashed it), and a double-click on the ellipse or that axis selected only the
one curve. The drag and Sketch::chain now read whichever axes are left through
ellipse_axis_lines, as Transform and Move already do.

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

* Sketch: drop the unreachable third arm of the held half-axis check

A distance from the centre to the major end or the point opposite it is already matched by the first arm, so the third arm and its major_end closure never ran.

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

* Sketch: D on an ellipse's axis line is the whole axis wherever the value is pulled

An axis line took the free line's rule, so on a turned ellipse a value pulled beside or above the box the axis spans gave its horizontal or vertical distance. An ellipse's axis is a construction axis, so auto now always gives its length, which the held half-axis check already counts.

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

* Sketch: an ellipse's axis is never a side of a Fillet or Chamfer corner

A line drawn from an ellipse's major end shares that point with the major axis, so Fillet or Chamfer there took the two as a corner (two lines skip the construction filter) and rounded the line into the axis. The axes are left out of a corner, picked by its point or as a and b.

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

* Sketch: test the move reply, extend and the one-axis Transform preview for ellipse axes

The reply of sketch.move, sketch.transform and sketch.scale lists the axis lines, Extend stops at an axis, and Sketch Transform previews an ellipse with the one axis left after the other was deleted.

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

* Sketch: with Draw ellipses with their axes off, D on an ellipse is its minor radius as before

The preference brought back the old drawing only; D on an ellipse had changed with no way back. sketch.dimension takes half_axes (default true, refused when not true or false); false makes auto on an ellipse the minor radius wherever the text is. The Dimension tool and the context menu's Sketch Dimension pass it from the preference, and with it off a radius on an ellipse is drawn and labelled R as before.

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

* Sketch: read an ellipse's axes in one pass over the constraints

ellipse_axis_lines scanned every constraint for every curve, so 500 ellipses with their axes (1500 curves, 2000 constraints) took 1.6 s to read and Move on all of them 2.0 s. It now collects the lines the centre bisects first: 3 ms and 0.36 s.

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

* Sketch: the dimension and corner parameters say how an ellipse's axis lines take part

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

* Preferences: Draw ellipses with their axes says D gives the minor radius when off

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

* Sketch: ask only the ellipses centred on a line's midpoint whether it is their axis

is_ellipse_axis read every ellipse's axes to answer for one line. An axis is bisected by its ellipse's centre, so it now asks only the ellipses centred on a point that bisects the line. The answer is the same.

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

* Sketch: a horizontal or vertical dimension across a level major axis holds it for D on the ellipse

half_axes_held counted a distance, a length and a radius, but not a horizontal or vertical dimension across the major axis, which is its length when the axis lies that way. D on the outline could then take the held major half-axis and be refused as determined; it now gives the minor one. Across a slanted axis such a dimension is only a projection and does not count.

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

* Dimension: a draft on an ellipse is drawn with the axes preference it was made with

The frame read the preference as it is now, while the dimension added is the draft's.

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

* Sketch: a horizontal or vertical dimension across an upright minor axis holds it too

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

* Sketch: drop the unused Sketch::ellipse_axes; ellipse_axis_lines says how the axes are read

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

* Sketch: an ellipse's own axes do not cut it, so Trim takes a lone ellipse whole and stops at real crossings

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

* Sketch: Fillet and Chamfer offer no corner at an ellipse's axis end, as the engine takes none

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

* Sketch: a deleted ellipse leaves its centre on its axes, as a Center Rectangle's centre stays

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

* Sketch: ellipse axis docs say what they do, and the corner picker tests a curve's ends before its axis role

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

* Sketch: the trim-at-a-crossing test checks that part of the ellipse is left

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

* Sketch: an ellipse axis moved, turned or scaled on its own takes its ellipse along, as a drag does

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

* Sketch: a selection dragged by an ellipse axis takes the ellipse along, as the axis alone does

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

* Docs: the Sketch preferences page lists Draw ellipses with their axes

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

* Sketch: the ellipse axes help text says D gives the minor radius, drawn and labelled R as before, wherever the value is pulled

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:54:46 +00:00
Nathan Dane e3f99de8af Onyx: a tactile stone-grey theme with broad subtle blur (#10338)
* Onyx: add the finished stone-grey theme and document its blur range

* Onyx: distinguish grey construction lines from gold dimensions
2026-11-04 16:35:28 +00:00
Lars Jansen 7bd66b7c06 Sketch dimensions: enter the value in a small window with step arrows (#7246) (#7251)
* claim: #7246

* feat(sketch): enter a dimension's value in a small window with step arrows (#7246)

* Dimension window: fit the layer split, and keep the wheel off the camera over the window

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:12:31 +00:00
jyviko f74c6605c4 Fillet: Mitre or Swept corners where two rounds meet (#11498)
* SMLib wrapper: a swept or mitred turn for constant rounds (Turn)

Brep::blend_dropping takes a Turn: Mitre (the default, as before) or Swept,
which sets solidcore's FilletSession::set_swept_turns before the corners are
made. A swept turn solidcore refuses at CreateFilletCorners comes back as
Error::TurnRefused with its reason and place (NoRoom at a concave side edge,
else CornerUnsupported). Swept is for Blend::Round only.

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

* Kernel: fillet rounds turn in a mitre or swept (FilletTurn)

fillet_removing, fillet_max, fillet_max_each and fillet_each take a
FilletTurn and hand it to SMLib, through the reblend of earlier rounds at a
later corner too. A swept corner solidcore does not build is refused as
invalid input with its reason and place: no smaller radius builds it.

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

* Design: a fillet saves its turn, mitre (default) or swept

FeatureKind::Fillet gets `turn` (FilletTurn: mitre, the default and not
saved, or swept), one pending format file. Evaluation hands it to the kernel,
pattern copies included, and refuses a swept turn with a style, fit, corner
or setbacks the kernel's swept corner does not take (SWEPT_TAKES).

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

* solid.fillet: the turn parameter, and the command's input and output schemas

`turn: mitre|swept` on solid.fillet, refused when unknown or with options a
swept corner does not take. solid.fillet now has its contract (FilletParams)
and reply type (FilletResult), registered with checked!, and leaves
schema/unconverted.txt.

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

* Fillet dialog: a Turns choice, Mitre or Swept

A Constant radius fillet's Turns row sends `turn: swept`; choosing a narrowed
radius or a curvature corner puts it back to Mitre, and Swept puts those back.
Its tooltip says what each does where two rounds turn round a corner edge.
New strings in a pending locale file (en, de, ja, ko, zh); glossary entry.

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

* Fillet turn: the schema tests' fixes and the complete catalogs' strings

The no-contract example in the schema tests is solid.chamfer now that
solid.fillet has one; the reply test checks the mid-radii form against the
contract (solidcore refuses that law's corner yet, #430) instead of building
it; Arabic, Hebrew and Vietnamese, complete catalogs, get the new strings.

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

* Fillet schema test: variable and spline forms checked against the contract

solidcore fails a variable round on a box edge at this pin (status 1001, as
variable_radius_on_a_box_edge), so the reply test checks those forms against
the contract instead of building them.

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

* Fillet turn: the swept rig cases pass; the MCP no-contract example is solid.chamfer

cov-fillet-turn-swept-boss-base-r1 and
cov-fillet-turn-swept-pocket-sharp-corner-refused-r1 pass now that
solid.fillet takes `turn`: off the rig's known-failing list. The MCP bridge
test's example of a command without a contract was solid.fillet, which has
one now.

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

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:36:00 -05:00
bherbruck e438a9bbea Web app: choose the release, the release candidate or the edge build (#11419)
The web app is now offered at /app/ (latest release), /app-rc/ (newest release candidate) and /app-edge/ (current main), each with its own browser storage. /app/ is built from the latest release's web zip instead of main.
2026-11-03 07:23:17 -05:00
jyviko c5ccb9201d Wave 43/45: smlib: rustfmt after the C++ kernel's removal (and 1 more)
2 commits, squashed; the originals stay reachable from branch jyviko/merge-main-develop-solidcore (#11360).

- 19db2d7c0 jyviko: Docs and comments: solidcore is the kernel
- f1ee045de jyviko: smlib: rustfmt after the C++ kernel's removal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
2026-11-02 18:09:38 -05:00
jyviko ea772a0438 Merge develop into main: SolidCraft on the SMLib kernel (v1.1.0-alpha) (#11351)
* UI: rim and wall picking tests for a circle of arcs that is one wall

A circle drawn as two half arcs now extrudes to one full-turn wall with its
seam edge, and each rim is one closed edge (tangency-labs/smlib#67). The tests
keep their intent on that body: a rim clicked once is one item, measured and
highlighted whole and picked from either side; a wall picked either side of
its seam is the same surface, lit whole, and toggles off from the other side;
every face is its own patch.

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

* Fillets: a later round at a corner where earlier rounds meet blends them again with it

The owner's ruling of 2026-10-30: a later fillet on an edge at a vertex where earlier
rounds already meet in a mitre makes the corner rounding all of them at once makes, one
smooth patch, the mitre gone. A body made by constant-radius rounds keeps what they were
made on and which edges they rounded at which radius; a later round reaching such a corner
blends the earlier rounds' edges (from the earliest of those meeting there on) with the new
edges in one SMLib blend of the body before them. The result's history is put over the body
the later round was given: an earlier round's face is that face trimmed anew (its name
kept), the corner patch and the new rounds are the later fillet's. A picked mitre seam has
no round of its own: it is refused unless a new edge of its radius meets it.

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

* Tests: a later r3 round at an r2 mitre, through the Fillet feature, makes the all-at-once corner

Two box edges rounded r2 in one fillet, then the upright edge at their mitre r3 in a second
fillet (tangent chain on, so the seam comes with it): 10 faces, 7928.743041 mm³ as
fillet_radii of the box gives, the r2 rounds' names kept, the r3 round and the corner patch
the second fillet's.

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

* Kernel: a body's record of rounds goes with its history, and can be read back

The rounds that made a body are over the body its history is over: a new history (an
operation carrying a step's result back to its own operands) leaves the record out, so a
later round never reads one history against another body. Body::rounds gives the record and
Body::with_rounds_of takes one back, checked (edges of the body before them, a fillet's
radii, the body's history over that body), for a saved model to keep it.

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

* Model cache: a body made by rounds keeps them, so a later round blends them the same after opening

A design gave another body when opened from its model cache: the rounds a body was made of
lived only in memory, so a later round at their mitre was built sequentially (or refused, as
too large) after opening and blended with them when evaluated anew. The saved model now
records, per kernel body made by rounds, the body they were made on (a place in the bodies,
stored once though no state holds it) and the edges with their radii; reading it attaches
them back down each chain, and a thinned model keeps those bodies with the bodies made from
them. Model cache format 7.

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

* Tests: a corner patch of rounds 2/3/4 takes no more than the strips leave in its block

three_rounds_at_a_corner bounded the patch's extra cut at 3 mm³ beyond the strips, a figure
with no derivation; SMLib's all-at-once body at radii 2, 3, 4 takes 3.099 (V 7878.579574
against strips 7881.678). That body is the strips' outside the block its patch lies in: its
rounds are the strips' cylinders ending at a's section y = rc, b's x = 20 − rc and c's rails
where they meet a's and b's (the patch's four corners, now asserted), so the patch takes at
most what the strips leave in that block (11.86 at 2/2/3, 32.69 at 2/3/4). The lower bound is
that block; the upper one, at most the strips' volume, stays.

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

* Tests: the io crate's hand-built saved models list their rounds, as format 7 records do

Model cache format 7 (f3cf0c4f3) records a `rounds` list in every saved model, and reading one
without it is refused ("saved model: rounds"), as is a model without any of its other lists:
the states, the preview and the thinned model all came back empty. The io tests' saved_model
and timeline_model wrote format-6 records, so six tests (the round trip, the repeated part
stored once, and the four too-big-for-the-file cases) read nothing. Their bodies are none made
by rounds: the list is empty, and every expectation stays as it was.

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

* SMLib: pin eaa998a (tangency-labs/smlib#68, #69, #70)

Batch 4: drafts and loops, mitre corners, rim chamfers, rulings, tight box, stitch, point
classification, eased corners. Batch 5: a face's tight box divides its cells at the surface's
knots (thread boxes exact); a cylinder, cone, sphere or torus is extended along its own circles.
Batch 6: a revolution's extension closes its turn; a dropped point's foot is the converged one.
The C API is ABI 5 (smlib_blend_dropping and smlib_brep_classify_point are new; nothing existing
changed), so the binding expects 5.

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

* Tests: a strip's tangent and curvature run-offs are the distance long across the surface

SMLib measures an extension's distance across the surface (smlib_extend_sheet corrects
CreateExtendedSurface until the parameter line through the side's middle is that long), so each
run-off of the cylinder strip, swept 20 along z, adds 5 x 20: 10 pi/2 20 + 100 = 414.159 for
both. The parabola-length formula (418.18) was truck's.

Ruling d2 (2026-10-30).

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

* kernel: one smooth angle, and a round too small to change the part is refused

The blend's tangent angle is the shaded view's half degree (smooth_edges::ANGLE), one value
for smooth. A constant round lies r (sec(d/2) - 1) inside the crease it replaces (about
r d^2/8); below the edge's tolerance it changes nothing and is refused before SMLib (which
answered SM_ERR_INVALID_INPUT), naming the smallest radius that rounds,
tolerance / (sec(d/2) - 1). d is the least turn along the edge
(tangent_edge::least_angle_along, at tangent_all_along's stations).

Ruling d3 (2026-10-30). The block's tolerance is 2e-5 mm, not 0.1 um: at 1 deg the smallest
radius is 0.526 mm, so R2 and R20 reach SMLib, which still calls an edge within 2 deg smooth
(SmEdge::GetBlendEdgeuse); that test is ignored until SMLib uses 0.5 deg.

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

* Fillet rig: the knife-edge chamfers name SMLib's cause, a cusp's ball radius of 0

smlib_chamfer turns the distance into a ball radius d sqrt((1 - c)/(1 + c)), c the faces'
binormals' dot (capi chamfer_radius); at #851's cusp the binormals agree, the radius is 0 and
the call is SM_ERR_INVALID_INPUT before the fillet executive runs (gdb: chamfer_edges sees
radius 0; the dump's why is empty). SolidCraft hands every chamfer to SMLib, so the
ball-contact chord (the ball of radius d: 3.822 wide at d = 2, 159.2365 removed) is SMLib's to
build.

Ruling d4 (2026-10-30).

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

* kernel tests: a corner round wider than its faces overflows and takes the corner's other edges

R21 on a 20 mm cube's edge builds (6107.529370, closed form, to 2e-12). With R5 on the
corner's other edges the R21 takes the faces they lie on, so the body is R21's alone; SMLib
fails that blend (status 1001, fillet error 4, Unable to Trim Rail Curve) and the test is
ignored until it builds. It replaces the refusal test that expected TooLarge.

Ruling d8 (2026-10-30).

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

* Tests: a 1 degree crease rounds, and the knife edge's chamfers build, on SMLib smlib-batch-4-rulings

With SMLib's smooth limit at half a degree (422e6f1) the 1 degree crease rounds R2 and R20
(the test is no longer ignored), and with a cusp's chamfer the ball of radius d (d231547) the
The 0.5 one stays, for its own cause (the chamfer face left apart from the body). Two corner
cases the same SMLib (replay/1.0.1 and those commits) builds come off too.

Needs SMLIB_REV at that SMLib once it is pushed.

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

* kernel, doc: an overflowing round goes first; an edge it removes is gone, and the fillet says so

SMLib (smlib-batch-4-rulings 0e83cca, SMLIB_ABI_VERSION 5) rounds the edges whose rounds overflow
their faces first and then what is left of the others; smlib_blend_dropping says which picks'
edges the overflow removed whole. The smlib crate binds it (Brep::blend_dropping), the kernel
returns those picks (fillet_removing), and a fillet notes them as its warning: "the edge at
(0, 0, 10) was removed by the larger round, so it is not rounded" (a 50 x 50 x 20 block, R21 on
its bottom front edge with the front left upright: the block is R21's alone, in closed form).

Ruling d8: [21, 5, 5] on a 20 cube builds as R21 alone (6107.529370); the test is no longer
ignored. R25 with R2 at a corner (blend_corner) builds the same way, R25's alone in closed form,
where it was refused. The rig's 1080 notched block outline at R8 builds now and comes off the
known-failing list.

Needs SMLIB_REV at that SMLib once it is pushed.

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

* Fillet rig: the knife edge's 0.5 chamfer builds

SMLib's trim took a fillet meeting its face at more than 78 degrees for one with no side
(smlib-batch-4-rulings 971e567); the chord of the knife edge's 0.5 chamfer meets the flat face at
81 degrees. The case comes off the known-failing list.

Needs SMLIB_REV at that SMLib once it is pushed.

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

* Tests: a 15° tapered text builds, every letter grown, exact where its outline gives it

Owner ruling d6: the letter t of "tres long" was drafted inward by SMLib (its
loop's normal, summed from the cross products of its chords, pointed down;
fixed in SMLib). With the fix every letter grows, and the join builds at 3°
and 15° by the Round rule and by the default rule (tangent for spline
outlines), where before 15° was expected to be refused.

Exact where the outline derives it: a letter's section at height z is its
outline offset by e = z tan a, by the Round rule of area A + P e + C e² while
the offset trims nothing (Steiner; C half each loop's turning less
tan(t/2) - t/2 at each reflex corner), so the letter is
A h + P k h²/2 + C k² h³/3. Each extruded alone, the t at 3° and s, l and o
at 3° and 15° are checked to 1e-6 (SMLib measures them within 1.3e-9; its
mass properties integrate to 1e-8). The concave joins of r, n, g and e are
tighter than e already at 3° (their swallowtails trimmed: r 1.1e-3 under the
formula), the t's by 15° (9.9e-5 over), and on the block the letters grow
into each other, so the whole word is checked for what holds exactly: one
closed body with the block, and half-way up every point of each straight
letter's outline inside the solid (the t drafted inward failed that).

With SMLib's fix (smlib-batch-4-text-loops 82133b2).

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

* Kernel rig: a sloped basin's mitred rim rounds are checked tangent along their contact lines

1080-fillet-sloped-basin-1-mm-rim-r0-5-4-edges asked G1 at every blend seam. Its front wall
leans 19 over 20 and the others 10 over 20, so at each rim corner the two mitred rounds meet the
sloped corner edge at different heights (0.155625 and 0.276393 below the top): the deeper round
runs on past the corner edge and is cut by the plane of the other's wall, a crease by
construction, the gap below the shallower round filled on that plane. The check now measures
the rounds' contact seams, on the top and on each wall, where the rounds are tangent. The
volume is unchanged.

With SMLib smlib-batch-4-mitre (1c15364): the case passes (V 144530.427946 against
144530.427909, 15 faces, sound); before it, only the four corner creases failed the check.

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

* smlib: Brep::classify_point says exactly where a point lies against a body

Binds the fork's smlib_brep_classify_point (SmBrep::Point3DClassify at the body's own zone
tolerance): inside, outside, or on the boundary within that tolerance of a face, an edge or a
vertex. A box and a cylinder just off its curved wall in measure's tests.

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

* kernel: Body::classify_point decides inside, outside or on the boundary exactly

SMLib's point classification at the body's own tolerance, on the exact faces and edges, in
place of a mesh at a chord of 2e-3 of the body's size (about 0.4 mm on a 200 mm part). A point
within the tolerance of a face, edge or vertex is OnBoundary, never decided by a ray. A mesh
body's triangles are its exact shape: on them within the record's tolerance, else by ray
crossings. The kernel's own probes move to it; Body::contains goes once its last callers have.

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

* doc: a hole decides whether it exits at its drill point's shoulder exactly

Material past the depth needs a body with the point inside, air before it needs every body to
have it outside; a point within a body's tolerance of the exit face is neither.

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

* engine tests: probes ask for the exact point class

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

* ui, cli tests: probes ask for the exact point class

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

* kernel: Body::contains, the mesh test, is gone

Every caller asks Body::classify_point now.

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

* kernel tests: the rim chamfers running off their rims build, on SMLib 737c392

chamfer_wider_than_sloped_basin_rim_builds and a_rim_loop_chamfer_running_off_its_rim_builds
waited for SMLib to build the cliff overflow (CloseRemovalRegion refused it,
SM_FILERR_NO_ROOM). The pinned SMLib builds it (eddf0ede4, bba9914b6): both pass on aarch64
Linux and are no longer ignored.

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

* Fillet rig: the cases SMLib 737c392 builds come off the known-failing list

The gate on aarch64 Linux reports 16 fillet cases FIXED with the pinned SMLib: the sloped and
equal-slope basin rim chamfers and rounds past their rims (1080-*, 5835-*), the pocket rim
chamfer past the floor, and the three pocket rim rounds now mitred at the pocket's corners
(every-pocket-*). No other area's gate reports a case fixed.

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

* kernel: the re-blend's removed picks formatted as rustfmt lays them out

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

* kernel: blend_smlib laid out by the 2024 edition's rustfmt

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

* Skills: the tests skill's unit-test example stays under the 3600-character limit

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

* Fillet: the radius-too-large choice in plain nouns

"If too large: Stop and say so / Largest that fits / Narrow where needed"
becomes "Face limits: Exact radius / Largest radius / Narrowed radius", in
every language of the catalog, with the error and the tip that name them.

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

* kernel: a round that would cut through its wall into a hollow is refused

The owner's rule of 2026-10-30: a round running past a rim of its faces
into open air builds; one whose section would remove material through to
a hollow behind its edge (a slit into the hollow, a cut through the part)
is refused as too large, so the fillet names the largest radius that fits.

Decided exactly in the section of a straight convex edge between planar
faces: the round's centre is at depth r below both planes, and it opens
where its arc uncovers a boundary point strictly inside the wedge with no
material beyond it toward the centre (the body's exact point
classification). Looked at: straight edges parallel to the round's (a
hollow's corner), cylinders about parallel axes (a rounded corner), and a
picked concave parallel edge's own round, which fills the corner.

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

* Fillet rig: 1668's R7 past the inside corner is refused at 4 + 2 sqrt2

The round's centre (32 - r, 42 - r) leaves the hollow's corner (30, 40)
outside its circle, opening the wall, where sqrt2 (r - 2) = r: the case
expects the refusal naming 6.828. 2852's R30, refused naming 25.24, now
passes too: both come off the known-failing list.

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

* xtask: known-failing grades a nextest junit report against tests/KNOWN_FAILING_TESTS

`cargo xtask known-failing --junit PATH` reads nextest's junit (a file, or every .xml under a
directory, so all of a run's junit artifacts at once) and fails on a test that failed and the
list does not name, and on a listed test that passed (FIXED: take its line off, as the kernel
rig's gate does). A listed test that did not run is neither, since a pull request tests only the
packages it touches. The list has the rig's KNOWN_FAILING format (`<test> <cause>`, sorted, the
rig's own parser), with `<test>` nextest's `<binary id>::<test name>`; `cargo xtask lists`
checks it is sorted. The list starts empty.

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

* CI: the test jobs grade their junit against tests/KNOWN_FAILING_TESTS

Native Linux tests and the GPU path test shards no longer fail on any failed test: nextest's
"tests failed" exit (100) goes to `cargo xtask known-failing --junit`, which fails on a failure
the list does not name and on a listed test that passes. Any other nextest exit (a build error,
a bad filter) still fails the step at once. The tests skill says to list a failing test with its
cause, in place of ignoring it.

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

* Tests: the known-failing list takes develop's 10 failing tests

Develop 38fd4a4f6 (CI run 41157648345, after the SMLib eaa998a pin) fails 10 cargo tests; each
is listed with the cause its failure message gives. Seven are SMLib refusals or results off
their derived volumes (fillet, chamfer and shell), the cli action matrix and naming corpus
among them; the two corner_g2 tests are the fitted G2 corner patch bulging past the box, which
the explicit G2 corner fixes.

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

* Reference tests: the eight cases that fail on SMLib eaa998a name their cause as known failures

Develop 38fd4a4f6's Reference tests fail 34, 35, 77, 78 and 81 (STEP export finds fillet edges
4.6e-5 to 6.1e-5 off their surfaces against 2e-5), 37 (the apex blend 0.193 off), 80 (the
taper-to-fit area) and 83 (thicken status 1001). Each expected.json names its cause in
known_failure, which the oracle already reads: a known failure that passes fails the job.

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

* Sweep path: a curve of several pieces is turned round whole

The path chain oriented its first piece, not its first curve: a first curve
of several exact pieces (a fit spline, an ellipse, a projected free-form
edge) was never turned round, so a path whose next curve meets it at its
start was refused as "not connected end to end". The chain now orients
curve by curve: the first runs toward the second where they meet at one
end only (else from the end nearest the profile), each next one starts
where the previous ended, with one joint tolerance for both questions.

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

* SMLib: pin 2fc375f (tangency-labs/smlib#71, #72, #73)

A sheet's side is extended exactly the distance asked, or the call says it cannot (#71);
NVIDIA's -Werror build passes again (#72); a body splits by a sheet of several faces as it
is, smlib_split_body_by_sheet (#73). The C API is ABI 6 (the new entry), so the binding
expects 6.

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

* kernel: Extend grows a surface of several faces face by face

A surface body of more than one face (a fit-point spline's spans extruded, #3830) was refused.
Each face with a picked edge is now extended alone by SMLib's extension of its surface, and the
faces are sewn back together, each the face it was. A side that lengthens onto a face meeting it
tangent takes that face along, past its edge continuing the picked one; across a crease the
neighbour grows only when its edge is picked too, otherwise the extension is refused. Faces whose
lengthened sides no longer meet are refused. Up to a target keeps the pieces every grown face
lies in.

3830's extensions now build; its split still fails (the tool point picks one span, and a face
tool cuts with that face's surface alone), so it stays on the list with that reason.

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

* kernel: Split Body by a face of a smooth sheet cuts with the whole sheet

A face tool cut with that face's surface alone, so a face of a surface body made of spans joined
tangent (a fit-point spline extruded, #3830) cut nothing where its span ends inside the body.
Split Body by a face of a surface body now takes the faces joined to it tangentially (the exact
check Extend uses, within the one angle for smooth) and slices with them as they are, through
SMLib's slice by a sheet (smlib_split_body_by_sheet, tangency-labs/smlib 7a01f1b), its sides those
of the picked face. A sheet that only nicks the body cuts nothing, as before; a lone face, or a
face of a solid, still cuts with its surface extended.

3830 comes off the booleans list. Its answer counted the two halves of the cup and not the surface
that cut it, which stays as a body of its own (4879 removes its surface for that reason): three
bodies, the volume unchanged.

Needs SMLib 7a01f1b pinned (SMLIB_REV, prebuilt; its SMLIB_ABI_VERSION 6, which crates/smlib now expects) to link.

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

* Tests: a cylinder strip's run-offs are 100 pi + 100 to 1e-6, SMLib's exact area

The tangent and curvature run-offs of the quarter cylinder r 10 strip, 5 across the surface,
are its section swept 20 along z: exactly 10 pi/2 20 + 5 x 20 = 414.159265. measure is SMLib's
integration on the exact surface (1e-8), not a mesh, so the run-off check is 1e-6 relative and
the 'measured on meshes' notes go. Red until SMLib's extension is solved to its distance
(smlib-extend-converges 590d8a1): eaa998a's curvature run-off is 4.986 long, area 413.882.

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

* kernel: an extension SMLib cannot make exact says so, naming the length asked

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

* Sketch: a control spline can carry its own knots and weights

A sketch spline over control points was always the clamped uniform B-spline
over them, so a free-form edge whose knots are not evenly spaced (or whose
weights are not 1) could only come into a sketch as one curve per Bezier
span. A control spline now may save `nurbs`: its clamped knots and its
weights. Its points, polyline, exact pieces (one a knot span; conics for a
rational quadratic), the solver's distance and curvature, links, parallel
re-projection and inspect.sketch all read them; a loaded design's are
checked. One pending format file (spline-knots).

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

* Sketch projection: a free-form edge comes in as the one exact spline it is

A projected spline edge of several Bezier spans came into the sketch as one
curve per span (#1508), so a G2 round's bottom edge was 16 curves and a path
naming the corner named one span of it (#3604). The kernel now reads such an
edge (a spline, or an elliptical arc of several spans) as the one clamped
B-spline it is over the edge's interval, its knots and weights its own, and
the sketch takes it as one control spline whose ends sit on the edge's
vertices. Planar regions still read edges span by span. The 3604 case builds
with its original path and leaves KNOWN_FAILING.

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

* Fillet rig: blends past an outer rim of their faces build, on SMLib smlib-blends-past-rims

The owner's ruling: a round or chamfer that runs past an outer edge of its face into open air
builds, tangent where it has room and cut by the face beyond where it runs out. SMLib's branch
smlib-blends-past-rims builds them by the rig's analytic answers. Off the known-failing list:
the disc's rim past its 2 mm wall (190), the hole's top rim R12 past the plate's bottom, the
chamfer past the cylinder's bottom (2893), each closing on the wall continued as itself, and
926's chamfer ending on the boss's wall, which now builds to its answer too.

The sloped basins' R4 rim rounds (1080, mitred rounds built together where one overflows) build
to their analytic volumes (to 6e-9), and stay listed with that reason: their continuity check
(g1 at every blend seam) takes in the mitres' creases, G0 by the mitre ruling.
2893-chamfer-two-distances-elliptical-rim is graded as a solid (the owner: it builds) and stays
listed, waiting on the ruling on unequal chamfers along a rim whose angle varies.
5158-fillet-round-just-past-a-10mm-face stays listed with its cause found: the round passes
5e-6 under the box's top back edge, inside the body's tolerance.

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

* Naming: test that a round of a round's end has no seam with that round's faces

The r1 round of a block's top edge, chained onto the end curve of the r2
round of its arc at a cylinder (rig fillets/every-block-cylinder-crease-
top-edge-after-arc-round-r1), ends where it runs into the cylinder. That
edge is no seam of the r1 round: the cylinder is a face of the r2 round,
not of the r1. blend_seams takes it for one today (the r2 round's name
F3:blend:F1:+z|F2:side holds a | of its own), and the rig reads it as a
contact seam at 60 degrees.

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

* Naming: a blend's seams split its edge's name at the | between its two faces

A blend face's name holds the edge it replaced, <face>|<face>, and a side
of that edge can be a blend face whose own name holds a |. Whether a face
is a contact of the blend was read as the edge's name starting or ending
with the face's name next to a |, so the r2 round's own face F2:side read
as a contact of the r1 round F4:blend:F1:+y|F3:blend:F1:+z|F2:side. The
edge's name is now split into its two faces, a blend side read whole as
the two faces of its own edge.

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

* Fillet rig: five refused blends build, off the known-failing list

926 (the chamfer ending on the boss's wall), the oblique cut's r0.1
round, the every-block r1 round (its seam with the cylinder is no
contact seam, after the blend_seams fix), the up-down squares' concave
chamfers and the shelled box's lip chamfer build with SMLib branch
smlib-blend-refusals (f8650b8 and later). 1207 r0.5 passes SMLib's check
on its rails now and stays listed for the next cause: its end rib on the
boss, which starts where the round and the boss are tangent.

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

* Kernel rig: the trough and the pocket whose floors fall to a drain build

SMLib now offsets a vertex where four faces meet (two walls and two facets at the floor's
corners, four facets at the drain) to two vertices joined by a new edge (tangency-labs/smlib
branch smlib-shells). Both volumes match their closed forms. The pocket's lining, hanging from
the top's rim round the pocket, stands apart from the slab's outer walls with the cavity rising
between them to the open top: the case says two shells in the one body.

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

* Kernel rig: the plate with a hole through its arc side shells open at its far end

SMLib's merge now intersects the offsets of an edge's faces where they could not be mated along
it (the hole's offset poking out through the half-round side's; tangency-labs/solidcore#1795):
one closed valid solid with the one face open, as the owner ruled.

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

* Kernel rig: the ring cut frame and the up-down join shell

SMLib's inset now keeps only the regions that are cavity, not the one the offsets close where
they overlap (between the two faces of #545's 0.5 floor, round the up-down join's two empty
octants). The frame's core under the recess is hollowed to a sealed void, so the case says two
shells and its closed form, 21044 mm³.

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

* Kernel rig: #616's lid shells open at a rib's side, with the 0.15 lip

SMLib keeps the skirt top's offset where its loops wind the other way under the opening, the
cavity's floor (the owner's ruling of 2026-10-31: build it; a lip thinner than the wall is no
break-through).

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

* Known failing tests: the action matrix's shells and chamfers as they fail with SMLib batch 7

The ring cut frame's and the up-down join's shells build now, and so do the up-down join's and
modern-plastic's chamfers (SMLib smlib-blend-refusals). #616's lid in the corpus still fails
SMLib's loop check, the shelled box shelled again at an inner wall is unchanged, and the
oblique cut of a cylinder's fillet still fails SmSurfOfRevolution's Test #7: 3 actions of 7.

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

* SMLib: pin 26cc451 (batch 7: rounds past a rim, blend refusals that build, shells)

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

* Known failing tests: the shell after a cut whose end touches an arc side builds on SMLib batch 7

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

* Surface fair test: the energy falls strictly, the face moves, and the edges are read after the fair

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

* Sketch scaling test: the first dimension sets l1 to 40 and scales both joined rectangles about the origin

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

* Frames tests: a pick on a moved component or an instance removes the exact round and bore volumes

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

* Issue 1080 tests: the rib's rounds and the mitred corner chamfers and rounds have their exact volumes

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

* Engine tests: lip_after_boss checks each lip's and groove's exact band, hole_as_deep_as_the_plate each hole's exact bore

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

* Robustness tests: lip_after_boss and hole_as_deep_as_the_plate live once, in the engine's tests.rs with exact volumes

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

* Engine tests: loft_cut goes; loft_spacing's hollow elbow covers its section counts and more

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

* Kernel loft test: the square-to-circle loft has the oracle's exact ruled volume, without printing

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

* Tessellation lens test: the lens's triangles fill exactly the rhombus through its arcs' midpoints

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

* Pattern test: six chamfered holes take exactly five more holes' bore and chamfer rings

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

* Kernel pocket floor fillet test: the corner rounds and the floor loop round add their exact volumes, by Pappus at the corners

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

* Shell regression: the saddle-corner shell opened on the top box's side has its exact volume

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

* SMLib history test: every operand's order is the identity on its record's faces and edges

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

* Boolean history tests: the SMLib module's box-minus-sphere, box-minus-cylinder and box-union-box copies go; the top-level ones check more

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

* Modify history tests: shell, offset and thicken are one test each through the public API, with the volumes, refusals and history the SMLib copies checked

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

* Chamfer history test: the chamfer after each box cut is exactly one face on the painted block too

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

* Grid test: a pole side's face is the five triangles fanned from its one grid node, none degenerate

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

* Kernel tests: the twelve ignored print-only debug tests and surveys go; coplanar_curved_booleans checks the flush-capped cylinder cuts and unions they printed

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

* Kernel: the #883 board fixture generator is an example, not an ignored test

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

* STEP import test: a seam sphere whose meridian stops short of the pole imports as the whole 2 mm ball

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

* Material tests: the nine copies in material_properties go; the persistence file checks the library copy's card and formats 20 and 21 in one loop

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

* Sample test: one loop checks every sample builds cleanly and without a panic; the robustness copy goes

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

* Known failing: the non-antipodal seam sphere import, refused for a loop of no (u, v) area

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

* Tests: rustfmt the board fixture example and the coplanar asserts

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

* Kernel rig: the two 3871 cone cases were byte-for-byte the 258 cases; the 258 cases carry their sources

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

* Kernel rig: a volume or area can be graded as a hand-derived range where analysis only bounds it

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

* Kernel rig: fillets keeps one case per regime; 17 replications of a case in the same regime go

The sloped-basin chamfers and rounds at 0.5 mm rims repeat the 1 mm rim's regimes; two of the
three knife-edge chamfer tips, the r1 and box r3 curvature corners, the r1 tangent corner, the
1 mm pocket-rim and hole-rim chamfers short of the floor and the r1 together-round each repeat a kept case's regime
and grades. #854 moves to 851-chamfer-arc-top-edge-9-5, and the cases that cited a dropped one
cite the kept one or state its formula.

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

* Kernel rig: five cases outside fillets repeat a kept case's regime and go

A square cut is the slanted plane's ordinary crossing; the cam's half turn is the quarter turn's
contact at a symmetric angle; the r15 slab stands inside the same coincidence tolerance as the r10;
the 90 degree unfold and the 40 degree flange repeat the 72 degree and 20 degree cases' formulas.

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

* Kernel rig: 3243 rim and tooth join graded on its volume (rim plus the trimmed tooth less their 0.01 mm overlap)

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

* Kernel rig: 3782 emboss on a cylinder graded on its volume, area, faces and box (the wrapped 4 x 5 badge raised 1 mm)

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

* Kernel rig: 3604 G2 swept-tool cut graded on a volume range (filleted box minus the swept corner section)

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

* Kernel rig: 4505 coil joined through a cylinder wall graded on its volume, area and box (cylinder plus the wire's screw-swept segment outside r 76)

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

* Kernel rig: 4879 window cut through a thickened spline revolve graded on a volume range and its faces

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

* Kernel rig: 4623 join of crossing pleated loft skins graded on its volume and box (both skins less their overlap)

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

* Kernel rig: 831 tapered glyph extrude graded on a volume range and box (the font's S drafted in by 10 deg)

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

* Kernel rig: 2357 ring of 90 cylinder segments graded on its volume and box (the segments less their 90 joint overlaps)

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

* Kernel rig: 4542 fluted wall joined on a revolved base graded on its volume and box (the base by Pappus plus the fit-spline ring)

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

* Kernel rig: 1985 and 2243 STEP-export enclosures graded on their volume and box (the outline less the cavity, plus gusset and standoffs)

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

* Kernel rig: both 4092 loft, ribs and pad unions graded on their volume and box

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

* Kernel rig: 1525 graded on its volume (the convex body's exact 2 mm inward offset), faces and box

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

* Kernel rig: 1494 graded on its volume (box and cylinder less the 1 mm-offset cavity, sharp to rounded concave edges) and box

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

* Kernel rig: 4492 graded on its box and a volume range (the face's 19 mm sweep added, within the block's sides)

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

* Kernel rig: 745 graded on its volume (the end moved 1 mm plus the spline boss's exact area), faces and box

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

* Kernel rig: 4567 graded on its volume (the revolved spline less its revolved 2 mm offset), faces and box

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

* Kernel rig: the up-down squares shell graded on its volume (the columns less the 0.346 mm-offset cavity, sharp to rounded concave edges) and box

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

* Kernel rig: 545 graded on its volume (the frame less its 0.5 mm-offset cavity, sharp to rounded concave edges), its sealed pillar void and box

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

* Kernel rig: the shell-again case graded on its volume (the shelled box less the 0.5 mm-offset cavity in its walls) and box

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

* Kernel rig: the arc-sided plate shell graded on its volume (sharp to rounded at the hole's bottom), faces and box

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

* Kernel rig: 616 graded on its volume (the lid less its 0.5 mm-offset cavity, sharp to rounded concave edges) and box

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

* Kernel rig: 391 graded on its volume (the shelled housing, sharp to rounded at the flange, less the letters' 0.6 mm deboss) and box

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

* Kernel rig: shell/3576 was the ladder's drafted box with rounds shelled, recipe for recipe; the ladder case grades it and carries #3576 and #3573

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

* Kernel rig: the two 851 chamfers of 2 graded on their volume (the cone's sweep, and its union with the front edge's prism)

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

* Kernel rig: the chamfered corner of the lipped shell graded on its volume (the chamfer's triangle through the wall)

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

* Kernel rig: the concave round into the peg's cylinder graded on a volume range (the torus it must add, the band it can't leave)

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

* Kernel rig: the two folded 72 degree trays graded on their volume (flat area x thickness) and the walls' reach

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

* Kernel rig: the flanged rhombus graded on its volume (flat area x thickness, all four reliefs cut) and its walls' ends

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

* Kernel rig: the base with two adjacent flanges and a hole graded on its volume (base, bends and panels folded)

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

* Kernel rig: the flange beside the ledge graded on a volume range (the flange's two readings of its length)

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

* Kernel rig: the two G0 window patches graded on the box the part they close keeps

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

* Kernel rig: the hub's six swept grooves graded on its volume (the revolved hub less each tube's part inside it)

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

* Kernel rig: the cut mirrored loft graded on the box its sections pin

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

* Kernel rig: the three modelled-thread joins and the fine M48 graded on their volume (the basic profile's groove, Pappus)

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

* Kernel rig: the oblique-cut round graded by its seams (tangent, not curvature-continuous); the flanged rhombus says what it tests (its reliefs stay apart at 60 mm)

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

* Kernel rig: 3841 is 168's rim touching its coin along a circle, and 4092's reduced recipe is its report less a flange clear of the failing join; the kept cases carry their sources

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

* Kernel rig: the oblique-cut round keeps its old grade until the new blend algorithm lands

Its seams grade needs the continuity check to skip the body the round is not on. Fillet cases
wait for the new algorithm.

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

* Kernel rig: 1985's enclosure is 78 x 77 (74 and 73 inside, 2 mm walls); its volume 38063.827 by hand

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

* Kernel rig: the three spline walls' face counts wait for the one-wall decision; their volumes grade them

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

* STEP import: a face bounded by seams alone runs its loop the other way, never turns over

A loop that uses each of its edges once each way (a whole torus within its tube and ring seams)
is the same set of uses run backwards, so it says nothing of the face's side. One written
clockwise in (u, v) turned the face over, against its same_sense: the torus came in inside out
with a warning. Such a loop is now reversed and the face keeps its side.

Test: seam_only_torus_imports_as_a_whole_ring, both orders of the two seams, volume 2π²Rr².

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

* STEP import: a face bounded by a slit alone is the whole of its closed surface

A loop that runs out along a chain of edges and straight back (a meridian from a sphere's pole
stopping short of the other, out and back) encloses no (u, v) area, yet on a closed surface it
bounds all of it less a cut of none. The reader only read such a face when the slit already ran
pole to pole (and moved the sphere's frame onto it only then), so the 2 mm ball whose meridian
stops at 2.5 rad was refused for its empty loop.

The slit is now made the face's seams, so its loop bounds the surface's whole domain: on a
sphere, a revolution whose profile ends on its axis, or a spline closed round u that collapses
at both ends, the slit's meridian is run on to both poles, out and back (a sphere's frame put
on the slit first, whichever two points it joins); on a torus, the circle the slit lies along is
closed and the other circle through its start is the second seam. The seams on a sphere are its
own meridians (angle 0 on the equator), so their pcurves are exact lines on its form.

Tests: non_antipodal_seam_sphere_imports_as_a_whole_ball passes (its known-failing line goes);
seam_only_torus_imports_as_a_whole_ring adds a torus bounded by a slit along its tube.

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

* Rig: the test-peg's concave arc and front top edge rounded together stay within the body's box

A new fillet case, report-fillet-concave-arc-and-front-top-edge-together-solver-residues:
the reported part's bar (with the sketch solver's residues) whose sides run tangent into the
cylinder, its concave arc and front top edge rounded r2 together. The plane of the front face
bounds the whole body, so every blend lies within the unblended body's exact box (SMLib's tight
box); graded to 4e-5, the corner patch's own tolerance. On SMLib 2fc375f the corner patch
stands 0.0374 out of the front face (box y min 0.0205): listed in KNOWN_FAILING.

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

* Rig: the test-peg corner at r5 and r7

Two more fillet cases, the reported part's concave arc and front top edge rounded together at
r5 and r7, with the same exact-box containment check (graded to 4e-5, the corner patch's
tolerance). On SMLib 2fc375f the corner patch stands 0.128 (r5) and 0.230 (r7) out of the
bar's front face (box y min -0.0699 and -0.1723): listed in KNOWN_FAILING.

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

* Rig: blends that end where their face runs tangent into another

Fifteen fillet cases on the test-peg's shape with clean numbers: a bar 40 x 16 x 10 whose far
end is a half disc R8, a disc R8 standing 10 on its top, joined, so the front face runs tangent
into the cylinder along x = 40 and the front top edge (convex) meets the concave arc of the top
tangentially there. Each blend ends on a face tangent to the one it rolls on.

- Rounds: both edges together at r2 and r5; one after the other in either order at r2, and at
  r1.5 and r3; the front edge alone as a curvature round, whose end on the cylinder is a
  semicubical cusp. The containment invariant (the unblended body's exact box, to 4e-5) and
  G1 seams, or the analytic volume where the blend has a closed section.
- Chamfers: the front edge alone and the concave arc alone (analytic volumes: the section along
  x up to the wall, the cone's spandrel ring clipped by the sides), both together, and one after
  the other (1 then 2).
- STEP: the peg written to STEP and read back, its front edge rounded (analytic) and both edges
  rounded together.
- A cone: the peg drafted 5 deg, whose front face runs tangent into a cone along a generator,
  its front edge alone and both edges together.

The quadratures are tanh-sinh, converged to 1e-13; SMLib's own volumes of the passing analytic
cases agree to 4e-9. A sphere is left out: a plane meets a sphere tangentially at a point, not
along a seam, so it is not this ending.

On SMLib 2fc375f eight fail, listed in KNOWN_FAILING: every chamfer that ends tangent (status
1001, SmFilletNx1Corner), the two rounds together (the corner patch 0.0374 at r2 and 0.128 at r5
out of the front face, and the rounds meeting with normals 180 degrees apart where their
contact lines on the top cross), the same on the STEP-read peg (status 1001) and on the drafted
peg (its corner refused as unsupported).

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

* Rig: fillet corners graded curvature-continuous, the default to be

Six fillet cases that pick their edges with no type, so they take the fillet's default, which the
owner has ruled is to be curvature-continuous (G2) at every corner, with circular rounds kept as
an option. Each grades every seam of the rounds and of the corner patch G2, on one valid body:

- an oblique convex corner (a block drafted 15 deg);
- a four-edge and a five-edge apex corner (pyramids lofted to a point);
- a pocket's concave corner;
- a mixed corner (a step: one concave edge, two convex);
- a corner beside a cylinder (the top corner of a half cylinder).

All six fail on SMLib 2fc375f, listed in KNOWN_FAILING: today's default is the circular round,
whose seams jump in curvature by 1/r. On the five-edge apex the corner patch also meets two
rounds at 8.45 degrees (G0).

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

* Rig: the test-peg's front chamfer builds

With the SMLib fork's smlib-tangent-ends (a rail's end on its side edge is the rail/edge
crossing), report-peg-front-chamfer-d2 passes. Its line leaves KNOWN_FAILING, for the pin that
carries that fix.

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

* Rig: the curvature-continuous corner cases pick the curvature fillet

The six corner cases added in 3a854104a took the fillet's default type, on the premise that the
default would become curvature-continuous. The default stays the circular round, so each case
now asks for the curvature fillet itself (type curvature: curvature-continuous sections and
corner patches) and keeps its grading: every seam of the rounds and of the corner patch G2, one
valid body. They are renamed from cov-fillet-g2-default-* to cov-fillet-g2-*.

The oblique three-edge corner (a block drafted 15 deg) and the pocket corner pass. Four fail, as
KNOWN_FAILING now says:

- the corner beside a cylinder: the curvature fillet builds its sections only between planar
  faces;
- the four- and five-edge apex corners: refused because the default setback is shorter than
  where the rounds' contact lines meet (3.90 and 3.51 mm from the apex);
- the mixed corner: SMLib sets back a corner only where its edges are all convex or all
  concave.

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

* Rig: the test-peg's concave chamfer, alone and after the front chamfer, builds

With SMLib's tangent end as an event of the fillet's trace (blend-end-event 6d73e4f), the
concave chamfer's rail on the cylinder ends where it crosses the seam along which the front
face runs tangent into the cylinder, continued past the vertex, and its end rib on the front
face is built from there. report-peg-concave-chamfer-d2 and
report-peg-front-chamfer-d1-then-concave-chamfer-d2 pass and leave KNOWN_FAILING. The two
chamfers together still fail, now at the mixed-convexity corner where they meet: its line says
so. Rig against SMLib 0cc48bd: no area regresses (963/993 pass, 961 on a87c502), ladder 168/168,
kernel lib only its listed failures.

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

* Kernel rig: constant rounds of the exact classes, and a tee's round

A plane cutting a cylinder or a cone at an angle, a sphere or a cone coaxial with the other face, a
cone and a cylinder touching one ball, two pipes touching at a point: their rounds' centre lines
are rational, so the rounds are rational surfaces of degrees fixed by the construction ((4, 2),
(2, 2) revolved, (6, 2), (12, 2)). The tee's centre line is not rational; its round is graded on
its volume. Volumes from the divergence theorem over the exact pieces, at rel 1e-6. The six that
SMLib 0cc48bd traces are listed in KNOWN_FAILING with what they come out as.

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

* Kernel rig: the exact-class rounds pass on SMLib's classified exact rounds

The rounds now come as the rational surfaces their classes give, closed ones as two faces split
at their seams, the touching pipes' as four: one splines entry a face, each of the class's degrees.
Their KNOWN_FAILING lines go.

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

* SMLib: pin 7ac037e (the new blend's tangent-end event, zero-speed G2 ends, and its exact-class rounds)

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

* Kernel rig: the fillets known-failing list is the 22 cases that fail on SMLib 7ac037e

Twelve listed cases build on the new blend's end event and exact classes, or were already
built on develop: they come off the list.

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

* Kernel rig: #316's bottom rounds as large as the wall, or larger, shell with a step wall at the opening, not refused

Where the round meets the open bottom the cavity's wall steps down along the bottom's normal, so the
round meets it at 90 degrees and the opening is exactly the picked face. The cases are renamed and
graded on the exact volumes, and come off the shell known-failing list.

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

* Merge fixes: every crate and test target compiles on the merged tree, but for crates/geom's hlr

world_state, view::bounds, home_camera and scene return Result on develop; new
main code takes that with ? or session_world. New fixtures carry the spline's
nurbs. ForeignCopy::of returns Result. occurrence_placement_with walks the path
with its moves, since the placement index holds pending_moves only.

The hlr port is separate and still to come: this commit does not compile as it
stands in crates/geom/src/hlr.

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

* Drawings hidden lines: read develop's B-rep records

Edges come from the record's edge list, cut to their interval (C3::of_edge), and
faces from its face list; a cylinder, cone, sphere or torus chart takes its bounds
from where its NURBS form lies on its label (S3::of_face). A rigidly placed
revolution has main's revolution chart; other placements stay refused. Moving a body
to its centre goes through Brep::moved. Loops are read against the natural normal of
the face's parameters where a chart wraps.

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

* Hidden-line fixtures: build develop's records from the fixtures' data

Fixtures keep their bounded curves and surfaces and their own edge numbers; a private
builder makes the canonical record, and the local prepare puts the edges back in the
fixture's numbering. Assertions are unchanged.

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

* Hidden lines: tests of the bounded edges, face charts and placed revolutions

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

* drawing-hlr doc: what is drawn and refused on develop's records

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

* hlr tests: a placed revolution is refused when its placement is not rigid

The new model has one record for main's Revolution, which was drawn, and its
PlacedRevolution, which was refused. An identity placement is now the former, so
this test's fixture takes a non-rigid one. The refusal it asserts is unchanged.

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

* Hidden lines: a traced intersection is drawn only where its spline lies on both surfaces

A leader off either surface is refused like a curve with no spline to follow. Tests: a traced rim
drawn on the circle and plane within tolerance, the same leader 1 mm off refused, and kernel bodies
with arcs through the production prepare against closed forms.

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

* Hidden lines: kernel bodies through the production prepare against closed forms

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

* Tests from main: the world state is a Result here

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

* A blend's pick measures close edges on their curves, not their polylines

A polyline can be a chord off its edge, more than two edges are apart near a
tangent point. A pick exactly on a ball's rim took the straight edge the rim is
tangent to, and solidcore built a chamfer of that edge with a clean check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
(cherry picked from commit c786db6bf4)

* Merge fixes from the first SMLib test pass

Model cache key covers the crates develop's kernel adds under doc (profile, smlib, step).
A piece taking a name also takes the feature that made it: SMLib lists a cut's pieces in another
order, and the swap left the name on a piece of the wrong feature.
A replaced analysis keeps its status slot. A command with no world picks does not place the model
first, so assembly.mass_properties answers for a flattening placement. FeatureCommon loses the
touching key the merge added; only the contracts that read it take it.
Two of main's tests listed as failing on SMLib, with why.

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

* Merge fixes from the ui test pass and the gates

Main's thread.direction keys go with develop's thread hand, which replaced them. Vietnamese for the
messages develop added while main made vi a complete catalog. CGA and 1-bit themes set develop's
comb and section hatch colours. A ribbon button with a persistent effect is pressed only while its
tool runs, so persistent_ribbon_pressed alone decides how its effect is shown; other analyses stay
pressed while on. Two of main's ui tests listed as failing on SMLib, with why.
Gates: fixed thread tables become slices, one collapsed if, rustfmt.

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

* A silhouette takes its facing from the surface's normals

A flat triangle beside the outline can tip the other way from the surface it approximates, and its
edges joined the outline. On SMLib's sphere that made 192 segments instead of the equator's 64, 100
curves and 95 profiles from the tight fit of #7989. Each corner's normal is used where it is close
to the triangle's own; a mesh without such normals (none, zero, not numbers, or averaged across
creases) keeps the triangle's normal, as before.

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

* Clippy and rustfmt on the merged tree

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

* Stepwise open checks every feature under the session's mode

OpenSteps::start evaluated under the default check mode, and finish kept those results under the session's mode: an each_step or off session opened in steps recomputed nothing. start now takes the mode (the app passes its session's), and a mode that changed before finish evaluates the design again under the new one.

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

* Clippy without SMLib: the planar-sheet edge reader is built only with it

Its one caller is in the smlib-only sheets module, so without the feature it was dead code and
clippy -D warnings refused the kernel.

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

* SMLib: pin d01d7b0 (the new blend's one Gⁿ section generator: the chord, circle and curvature sections, and the variable rounds as the union of their balls)

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

* Kernel rig: the section orders, chord, circle and curvature section, on planes and against a cylinder

Eight fillet cases for the blend's cross-section by its order of contact with the faces, each
graded on its analytic volume at rel 1e-7 and its exact continuity (the order, and not the next):

- a box's top back edge chamfered 3 (G0), rounded r3 (G1) and curvature-rounded 3 (G2): the
  areas cut off the corner s^2/2, (1 - pi/4) s^2 and 113/756 s^2, the last by the default
  thumbweight's definition;
- a pipe R10 dipping into a plate, its concave edge chamfered 2 (the chord between the contacts of
  the ball SMLib rolls for that setback, radius 1), rounded r2 and curvature-rounded 2, each
  section's added area by Green's theorem along the plate, the section and the pipe's arc; the
  order-2 section is built from the faces' feet of the point at distance d from both, which on
  the pipe are not linear in d;
- a boss root curvature-rounded 2, the square corner's section revolved (Pappus);
- a variable round r1 to r5 along a box edge (rel 1e-5): the balls' envelope is a circular cone,
  and the volume is the cone's sections integrated along the edge.

Three fail today and are listed: the two curvature rounds whose faces are not a square corner of
planes (SMLib's quintic blend curve, not the order-2 section: rel 6.5e-7 and 1.2e-5) and the
variable round (great circles, not the envelope's: rel 4.8e-5). The other five pass on SMLib
7ac037e.

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

* Kernel rig: the curvature fillets' expected bodies from the order-2 section at thumbweight 0

SMLIB_BLEND_CURVATURE's section is now the order-2 G^n section at thumbweight 0. Between two
planes square to each other it cuts A = 0.154192655384275 s^2 off the corner, its first moment
about either face 0.0291753295725360 s^3 (Green's theorem on the section): the curvature cases
built on that corner (862, 2708, every-box-edge, every-hole-top-rim, every-l-bracket,
every-block-cylinder-crease, the three-edges-at-bar-corner reports, 3604's fillet) take their
volumes from it by their own methods, the peg's front blend 23.3237146876 (its section along x
up to the wall). Between planes the section's t^3 term vanishes (their feet are linear in the
distance): the box edge case is exactly G3. The boss root is a certified fit, G3 to the plate and
G2 to the wall at Continuity Analysis's tolerances: at least G2. The boss root and the pipe on
the plate pass on SMLib's section and leave the known-failing list.

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

* Curvature fillets: the section's default thumbweight is equal area, so the round's volumes are the circle's

On the round's rails the order-2 section takes off what the ball's circle does: (1 - pi/4) s^2 at a
square corner, first moment 0.0474559669443 s^3 (w = -0.5252123889). The kernel and engine tests
and the curvature rig cases take their volumes from it by their own methods; the pipe on the plate
from its own wedge (70.52878 degrees, w = -0.8544103493). The G2 corner patch's seam test is listed
until the corner step.

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

* Kernel rig: four G2 corners wait for the corner step: their patch meets the new section's rounds G1 at one face seam

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

* Kernel rig: the turn at a corner of two rounds, Mitre (the default) and Swept, at a pocket's floor and a boss's base, by exact volumes

A round's spandrel A = (1 - pi/4) r^2 and its first moment about its wall M = r^3 (5/6 - pi/4):
Mitre adds L A -+ 2 M per edge (cut at, or run on to, the corners' bisector planes), Swept turns
the section about a convex corner's edge, (pi/2) M a corner, and refuses a concave corner rounded
less than the ball. The pocket's tangent chain round its R3 corners is a torus each. Graded to
1e-7, where Mitre and Swept differ by 6.6e-6. The Swept cases are listed until solid.fillet
takes a turn.

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

* Kernel rig: a junction family: oblique wedges 30 to 150 degrees rounded and chamfered (exact), bosses upright and tilted, pipe tees and a Y junction

Four are listed with their causes: the equal-pipe tee (its exact conic-pair rounds meet at the
ellipses' crossings in a corner the old corner code cannot join), the tilted bosses (1001, before the
blend rework too) and the Y junction (refused for room at its triple points).

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

* Variable rounds: graded by the union of their balls (their envelope), not the balls' great circles

The smlib crate's laws and ladder 005, 006, 007 and 012 take the volumes the balls' envelope
removes; the law with a corner is its own test, listed as failing until the corner step.

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

* Kernel tests: variable rounds graded by the union of their balls, the removed volume to 1e-4 of itself

removed_by_balls integrates, in each plane square to a right-angled edge, the corner the union
of the balls leaves (by rays from the edge, the least entry over the balls), cut to the body's
section. The law with a corner through a middle point is its own test, listed as failing.

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

* Kernel tests: a spline radius whose balls fold over one another at its peak is refused

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

* Kernel rig: the variable round on its cone passes (the round is the balls' envelope)

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

* Tests: three rounds of different radii at a corner listed: the corner's transition kinks with the balls' touch circles

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

* Ladder: the variable rounds graded to 1e-6 by the union of their balls; 012 by its law going on past the edge

At the default 1e-3 the great circles' volumes (4e-6 off) passed too. SMLib builds each within
1.6e-7. The golden takes the new volumes and areas; counts unchanged. The G2 corner test listed
as failing passes on SMLib's step 3 and leaves the list.

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

* Reference tests: the variable rounds 77, 78 and 81 pass on SMLib d01d7b0; their known-failure marks come off

The new section builds their edges on the surfaces, so STEP export no longer finds them 4.6e-5
to 6.1e-5 off. develop's Reference tests (60393a718) report all three as passing while marked.

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

* STEP import: a ball's halves whose seams are off each other's quarter lines each form within one turn

A sphere split into two halves along a circle about its axis, each half covering a pole with no
seam, gets a seam per half from the first vertex of its loop to the pole. The circles between the
halves group their frames so they turn together, and when the two seams are not a whole number of
quarters apart no one turn fits both: the group kept its frames, a half's form ran past one turn,
SMLib's trim (SMLib and solidcore alike) across the form's end crossed the face, and the mesh
folded over the ball (a rotor's ball showed torn; its half had 196 times its area in triangles).

When no one turn fits the group, each spilling face is now turned on its own to start on a quarter
line. On the rotor file this also clears three other faces that were filled leniently and a
0.076 mm edge gap; regression test in step_in/tests.rs with the area and volume of each half.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEu4Lr3moCCisdZShKz
(cherry picked from commit b3188e005d427043b918267449c2336a1777e54f)

* SMLib: pin 5e8c9c1 (the new blend's corners: Ball and End/Join; exact fold and room checks with fit sizes; overflow in place; the round as its definition)

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

* Kernel tests: a law's dip shows its corner ball's sphere, a peak's two rounds cross (the reasons had them swapped)

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

* Reference tests: the variable rounds 77, 78, 80 and 81 expect the union of their balls

A variable round is the envelope of the balls rolled along the edge, which takes less than a
quarter round in each section. The expectations are now that union, the law continued past the
edge's ends along its end slopes, integrated by Gauss-Legendre panels in x and t; 2, 4 and 8
panels agree to 1e-9 of the body's values, and 77's removed volume matches a brute-force union of
balls to 2.5e-10. The nearest-ball search now scans the balls the ray meets around ball x
(|x - s| < r(s) sqrt(sin 2t)) on their own, which near the faces and where 78's radius closes to 0
are narrower than the global scan's step. Regenerating keeps each case's known_failure. 79, a
constant radius, is unchanged.

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

* Known failing: the law corners' kernel and crate tests and rig 1207 pass on SMLib 48ea616

SMLib builds a variable round's law corners as its balls' union (the corner
ball's zone at a dip, the sides' crossing at a peak): the kernel's corner
through a middle point (39.5410) and the crate's 4 -> 2 -> 4 law (39.5421) pass.
Rig 1207 (the bar's top edge rounded after the boss's arc, r0.5) builds and
grades on the same SMLib; it failed on c820346.

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

* Kernel rig: every fillet and chamfer case rounds its tangent chain

The owner's ruling of 2026-11-01: the rig and the ladder round with tangent_chain true, how a
user's default pick behaves. The 38 fillets-area recipes and the ladder's 07-fillets/016 and 018
that passed false now pass true. The shell recipes keep theirs: a shell's tangent_chain is its
open-face chain, a different feature.

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

* Kernel rig: the partial-selection cases kept as -partial-chain copies, tangent chain off

Where a case's point is one edge of a tangent chain rounded alone, a copy keeps tangent_chain
false as its own case (SMLib rounds exactly the picked edges, ending at the smooth vertex); their
expectations stay as they were, to be re-derived:
- fillets/1207-fillet-box-end-edge-into-unblended-round-partial-chain;
- fillets/every-fillet-after-fillet-curvature-front-then-side-r3-partial-chain;
- fillets/1207-fillet-bar-top-edge-after-boss-arc-round-partial-chain, renamed from
  ...-no-tangent-chain: with the chain on it is exactly 1207-fillet-bar-top-edge-after-boss-arc-round,
  which the rig already has;
- ladder/07-fillets/020 and 021, the r5/r3/r7 and r5/r3/r2 corners (016 and 018) with the r3 on
  its edge alone: with the chain on, the r3 runs on along the r5 round's end arc and down the
  vertical edge the third round was to take.

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

* Kernel rig: the concave arc between the block's tangent sides kept with tangent chain off

fillets/every-block-cylinder-tangent-concave-arc-r2-partial-chain: the pick takes one piece of the
concave half circle, which SMLib splits; with the chain off its other pieces are left out, and the
blend is refused at the piece's end (the base gate's outcome). With the chain on the case rounds
the whole half circle. The copy keeps its expectation, to be re-derived.

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

* Kernel rig: two chains that now build graded by their exact volumes

With the tangent chain on, two cases round more than the edge they pick, and their old
expectations were the single edge's:
- 1207-fillet-box-end-edge-into-unblended-round: the r1 chain is the end face's top edge, the r3
  round's end arc and the back upright; 34 A + (pi/2) A (3 - c) by Pappus on the arc:
  V = 7953.139216 (it built 7953.139216, the old 7957.505 passed only inside 1e-3), 10 faces.
- 851-fillet-top-front-then-front-left-upright: the upright's chain runs up the first round's
  end arc on x = 0 and along the left top edge, so the corner becomes the R2 ball's eighth:
  V0 less the first round's spandrel to the wall (35.344727) less 27 (4 - pi) + 2 pi/3:
  V = 9530.777979 (built 9530.777987), 10 faces.

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

* Kernel rig: a curvature chain round a round's end refused for its size; the concave half circle builds

- every-fillet-after-fillet-curvature-front-then-side-r3: the side edge's chain runs round the
  first curvature round's end curve, whose radius of curvature has a weighted mean of 3 (its
  setback) and is unbounded at the ends, so somewhere below 3: the size-3 blend's contact line
  folds back on itself. Refused, "larger than the faces beside its edges allow", as ladder
  07-fillets/016's r7 on the r5 arc. (It was solid or refused for running on tangentially.)
- every-block-cylinder-tangent-concave-arc-r2-no-tangent-chain: the chain takes every piece of
  the half circle and ends at the convex edges it runs on into; it builds the analytic 19091.9620,
  so the outcome is solid (the refusal alternative was the chain-off one's).

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

* Known failing: two 851 chains whose corner piece is a ball of tube-centre radius 0

851-fillet-arc-then-top-front and 851-fillet-top-front-then-upright: with the tangent chain on,
each R2 chain runs along the end curve of an R2 round on a curved face (the arc round's end on the
front face; the first round's end on the wall), where the rolling ball sits on that round's own
centre line, so the piece is the ball about (39.2538, -19, 8): the three-edge rolling-ball corner.
SMLib fails with status 1001; the same piece on a plane end arc builds (front-left upright).
Expectations unchanged.

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

* Ladder: 016 and 018 refused, their r3 chain takes the vertical edge; 020 and 021 recorded

With the tangent chain on, 016's and 018's r3 runs on along the r5 round's end arc and down the
vertical edge, so the third pick (60, 40, 8) is 3 from both of that round's contact lines and
names no edge: refused, asking for a point along one edge. The corners they were made for are
020 and 021 (the r3 alone), whose golden entries are 016's and 018's (021 measured on f12a101).

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

* Kernel tests: three rounds at a box corner end on one rational patch on the ball's sphere, not an analytic sphere

SMLib's Ball corner is always the rational patch exactly on the sphere (owner ruling, blend
design §23), so the corner counts as an 'other' face beside the three cylinders and six planes.
The removed volume still checks the corner against r³ − πr³/6.

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

* Kernel rig: the curvature round on a pipe on a plate is listed: a seam grades G3 where exactly G2 is wanted

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

* Kernel tests: the box-and-fillet wire fixture records the round's cylinder frame as SMLib now keeps it

The round's cylinder keeps the frame it was made on when it is turned (SMLib's SwapUV no longer
re-derives it): its origin sits at x = −20 on the same axis line, (x, 2, 8) along x, radius 2.
Same cylinder; one number in the record moves.

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

* Known failing: two kernel tests' reasons as they fail on SMLib 5e8c9c1 (a partial selection running into an unblended tangent edge)

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

* Kernel rig: two curvature rounds get 600 s, past the 180 s default, while G2 certification is slow (tangency-labs/smlib#96)

On the CI runners the boss root and the hole rim, both size-2/size-1 curvature rounds, build
correctly but take more than 180 s: the order-2 section's span certificate and the round's
definition are evaluated station by station. The per-case limit is the one the thread cases use.

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

* Lid ring sketch test: find the ring under the pick, not by main's kernel's name

On SMLib the lip join's exact history names the lid underside's ring a piece
of the lid's own start (F14:start#[...]), as the naming rule says for a face
that came from the feature's input; main's kernel named it after the case's
top it rests on (F2:top). The test looked the ring up by that name and so
stopped before the attachment it guards. It now takes the underside piece
under the pick point, which the cap certificate attaches on either kernel.

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

* Viewport culling test: each box's triangles from its mesh, not 12

SMLib meshes a 10 mm box in 108 triangles, so the hard-coded 12 a box stopped
the test before it reached the culling. The expected counts are now each
body's span in the scene.

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

* Timeline chip test: a fillet that really reduces

The R20 fillet with fit reduce on one top edge of a 10 mm box builds on SMLib
without a reduced-radius warning, so the fixture had no warned feature. It
now rounds the four vertical edges at R6, reduced to the full round (5), as
the copy-warning test beside it does, and checks the warning says so.

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

* Oracle: 34-chamfer-then-fillet and 35-fillet-curved-curved pass on SMLib 5e8c9c1 (their STEP edges are on their surfaces)

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

* Known-failing: the shell of a fully rounded box opened at one face fails on SMLib 5e8c9c1 (tangency-labs/smlib#102)

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

* Known-failing: the action matrix's oblique cylinder cut fillet passes on SMLib 5e8c9c1; two shells still fail

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

* Kernel rig: the boss root's curvature round gets the rig's 1800 s cap: on the CI runners, 8 cases at a time, its G2 certification passed 600 s (tangency-labs/smlib#96)

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

* Kernel rig: a case's continuity can ask for an order per seam (seams: [{order, edges, exact}])

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

* Kernel rig: the pipe on a plate's curvature round meets the plate G2 and the pipe G3, 40307.8856312 (option B's order-2 jets, tangency-labs/solidcraft#10845)

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

* Edge names: a seam names its one face once; the curve-id test reads every edge kernel-neutrally

A seam's two sides are the same face, and edge names list each face an edge
bounds once, so SMLib's extruded circle names its side's seam F2:side:c1, with
no separator. The name grammar now says one face also means a seam. The #5858
test no longer counts one '|' an edge: every part of every edge name is a
whole face name of the edge, one per face it bounds, two for the rims and one
only for the side's seam (checked to run bottom to top on the circle).

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

* rustfmt on the merged tree

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

* Known failing on SMLib: two of main's new tests assume truck's box mesh and fillet torus

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

* SMLib: pin f916a8d (a round on a definition face is its definition; the G1 setback fill certified convex, setbacks too short refused naming the least; the G2 certificate halves a span no level can certify at once)

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

* Fillet: a setback too short for its corner is refused naming the least that builds (smlib_blend_fit_size)

SMLib's setback corner takes a G1 fill only once certified convex, and refuses a setback too
short for that as SMLIB_ERR_NO_ROOM with SMLIB_FIT_SETBACK_AT_LEAST, the smallest setback that
builds. The kernel reads it (solidcraft-smlib binds smlib_blend_fit_size) and names it; the
Fillet dialog says it in plain words. Other room refusals keep their words. The r5 box corner
set back 5.5 is refused naming 5.962, which builds.

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

* Kernel tests: three rounds of different radii at a corner meet in SMLib's setback corner

Each round runs whole to its section square to its edge at its setback (the owner's rule, at
least 1.5 r, scaled to the least at which the G1 fill is certified convex), and the hole is the
setback split: one patch per round and the triangle in three quadrilaterals, 15 faces, G1.
Outside the box the sections span the body is the three strips', so its volume is theirs within
that box's. A later r3 round at the mitred corner makes the body all three at once make
(7928.142563938, 15 faces), as the engine and the model-cache tests have it.

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

* Kernel tests: SMLib's own corner between curvature rounds is curvature continuous, as the G2 patch is

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

* Kernel rig: the boss root's and the hole rim's curvature rounds take the default timeout again (37 s and 34 s on the box under the rig's parallel load, tangency-labs/smlib#96)

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

* Agents: model decisions use exact geometry; a mesh is for display, export, previews and mesh bodies

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

* xtask: mesh-decisions, model decisions use exact geometry; today's 176 mesh decision sites are listed and the list only shrinks

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

* Sheet metal: cuts and holes pass over sheets and bends by their exact boxes, never a mesh's

A primitive cut skips the sheets kernel::apart (exact bounds grown by the record tolerance)
finds clear of its tool; the bend check leaves the clear case to kernel::boolean, which answers
it from the same exact boxes with no intersection work. The mesh-box helpers are gone, and their
four lines leave xtask/mesh-decisions.txt (172 sites left).

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

* Agents: AGENTS.md loses a stray merge-conflict marker line at its end

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

* Locales: Vietnamese for the setback-too-short refusal, now that vi is a complete catalog

Main made vi a complete catalog; develop's new message (ui.fillet_error_setback_short) had no vi
entry, so every_complete_catalog_carries_every_english_message failed on the merged tree.

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

* Kernel rig: exact volumes for 1213's bent-tube round and 4782's bar round, rel 1e-6

1213: the loft is a cubic skin through the five sections (ellipses between
them), not a torus; its volume 131601.597487935 by quadrature less the exact
rolling-ball removal 233.662461919 gives 131367.935026 (was a Pappus estimate
on a torus, 131360.148, at rel 5e-4).

4782: the section's area in closed form, 25 pi/3 - 5 sqrt(75) + 25, so
V = 630.2934873 (was a grid estimate, 630.293125).

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

* Kernel rig: the boss root's and the hole rim's curvature rounds take 600 s again: under CI's parallel load they pass the 180 s default (develop 820b7b6)

They take 37 s and 34 s alone on the box, but CI runs 8 cases at once on slower runners. Their
G2 certificate cost is the open item (tangency-labs/smlib#106, moved to solidcore#2344).

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

* After the merge: clippy's auto-deref, and the mesh-decisions list for the merged tree

Develop's list was taken on develop alone. Main's code (tap-marking reach, cut and hole box
checks, join pieces, print faces, placed measuring, selection items, imported mesh payloads) and
the branch's own exact_edges and named_vertex use meshes too; three listed functions were renamed
by main (face_outline and plate_recesses into FaceSlice, paste into build).

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

* After the merge: main's new tests on the SMLib kernel

- Cut To the far side of a curved wall (#8634): the tool stops past the first crossing of the
  face ahead of the profile; SMLib's whole bore is one face that a line crosses twice, so the
  single-crossing rule found none and the cut took nothing.
- Clearing view analyses goes through refresh_display, as main's #10687 made the others, so an
  open feature editor keeps OK and Cancel after Zebra.
- vi, a complete catalog on main, gets the setback-too-short message.
- fillets/KNOWN_FAILING: 4782's cause is now its continuity check (SMLib f916a8d builds the
  expected volume).
- Known failing, as main's tests assume truck: the spline wall's spans as one mesh patch, a
  hole touching an edge at a point leaving one body, and a copy of a copy's triangles matching
  a fresh mesh.

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

* Twisted sweep: circles about a straight path are exact cylinders (solidcore#2184)

A twist about a straight path maps a profile circle centred on the path's
line onto itself, so its swept wall is exactly the cylinder. The skin
through the stations stayed within 3.9e-9 of it (6.6e-9 on the outer wall),
and a boolean with another body on that cylinder failed with status 1001
(solidcore#2184; the kernel's handling of a B-spline face lying on a
cylinder stays open there).

sweep_controlled now builds those walls exactly when the path is lines
along the profile's normal and there is no end scale or rails: a profile
whose every loop is such a circle is swept untwisted, and such holes in
another outer loop are cut as exact cylinders from the skinned sweep of
the rest. Other loops, and an axis-centred arc inside a loop of other
curves, are skinned as before; a failed cut falls back to skinning all.

Regression test in smlib_sweeps_tests.rs: #2184's tubes joined at 10, 16,
18, 20 and 30 degrees give one closed body of 11.5625 pi with cylinder
walls; a twisted hexagon with the bore keeps a cylinder bore.

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

* After the merge: a hole that misses is found by exact boxes; a face is a one-face patch; main's mesh-box helpers gone

- `hole_misses` (main's #10701, through brett's branch) decided from the targets' mesh boxes. It now uses their exact boxes (`Body::exact_bounds`). An unknown box still leaves the hole not known to miss.
- With #10303's two shortcuts and `hole_misses` off the mesh, `body_box`, `mesh_box` and `padded_box` have no caller left, and are deleted.
- `project`'s intersection link passes its one face to `meeting_edges` as a one-element patch. This branch picks one face, not #9009's mesh patch.
- xtask/mesh-decisions.txt drops the six sites that no longer use a mesh here: `apply_traced_with`, `cut_end_past_face`, `beyond_cylinder_reach`, `hole_misses`, `body_box`/`mesh_box`/`padded_box`.

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

* After the merge: split_move's Result reaches its callers; to_local's duplicated move check goes

`frames::split_move` returns a Result on this branch: an unplaceable model is an error, not a silent no-split.
- Move/Copy of a component (main's #9914) propagates it, as its other calls do.
- `to_local` had the solid.move check twice after the merge (brett's Option one and ours). Ours stays, after the no-picks/not-placed return.

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

* After the merge: main's two new tests take world_state's Result, as the rest of their files do

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

* Native B-rep: a ball patch is its own surface kind, read from and written to SMLib's flat kind 6

SMLib's flat arrays now carry the corner of three equal rounds as `SURFACE_BALL_PATCH` (kind 6): its net as a NURBS face's block, with its sphere's centre and radius as its definition (ids 36 and 166). The record reads it as `ball_patch {center, radius}`, a label like the analytic kinds, with the net as its `nurbs`. A plain sphere with a form won't do: the net is not a piece of SMLib's sphere NURBS, so SMLib's `IsNurbSurfaceSphere` would refuse it.

Written back, a ball patch is kind 6 with the same definition, which SMLib takes once its net is certified on that sphere. Elsewhere:
- Offsets classify it as its sphere, so its offsets stay exact and collapse at the centre.
- Face kinds count it as a sphere: fillet_three_edges_at_a_corner's corner is now `sphere`, no longer `other`.
- STEP, which has no ball-patch entity, gets the net as the face's B-spline surface, pcurves included.
- Iso curves and SSI of the label are refused; the face's form is what evaluates.

Test: a_rounded_cubes_ball_patches_go_round_with_their_spheres. The cube's eight corners are ball patches about the corners pulled in by r. Points of each patch lie on its sphere to 1e-12. The patches are kind 6 again after import, with the same definitions bit for bit, and the closed-form volume holds.

Format: pending/ball-patch-faces.rs. Docs: docs/native-brep.md and the rig README's `surface_types` (`ball_patch`).

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

* SMLib: pin v1.1.0-alpha (a18bbe5): the generic blend's step-4 corners, the Ball's sphere patch through the flat arrays, all-target CI

Since f916a8d:
- #120: every target again, plus the warnings fix;
- #121: the Ball's patch as flat kind 6 with its sphere, read by the previous commit;
- #122: tests gated on counts;
- #123: the Mixed corner;
- #124: the End at a smooth vertex;
- #125: four or more rounds, and open setbacks;
- #126: the equal-area thumbweight guard;
- #127: the trunk merge.

The SMLIB_PREBUILT checksums are from the release prebuilt-a18bbe5…, written by `scripts/fetch-smlib.sh --pin`.

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

* smlib export tests: rustfmt's line for the rounded cube's edge midpoints

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

* Kernel rig: the fillet and ladder results on SMLib v1.1.0-alpha, the new failures recorded with their reasons

Fixed (off the known-failing list): the Mixed corner's peg cases and the concave arc with the front top edge (smlib #123), and the 40° boss junction (smlib #124). Eight cases in all.

New failures, recorded as they are, each with its reason. They passed on f916a8d.
- **Six cases grade a blend seam G0 where G1 (or G2) is wanted,** at the end of the round's contact seam, after smlib #124's End at a smooth vertex. Two of them also shift in volume.
- **Four cases come back unsupported** after smlib #125, which leaves setbacks to the Setback's corners only.
- **Ladder 07/012** makes 9 faces, 20 edges and 13 vertices, where its golden had 8, 18 and 12. Its golden entry is the new result, with the reason added.

Whether each change is by design or a defect is being established. A defect is fixed in solidcore, where the End and the Setback are being brought over (#2364, #2365), not in the frozen SMLib.

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

* Kernel ladder: 07/021 and 06/018 recorded as they are on SMLib v1.1.0-alpha, each with its reason

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

* Kernel ladder: a golden case marked unstable is gated on its status, and 06/018 is marked

On SMLib v1.1.0-alpha, 06/018 makes 597 or 594 edges from the same input:
the union's post-process keeps pointers to edges it has deleted. A golden
entry's `unstable` reason makes the gate compare that case's status only
and name it in its output; recording keeps the reason, and only an edit of
the golden removes it. v1.1.0-alpha.1 fixes the cause and removes the mark.

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

* After the merge with main: its new code on develop's exact bounds, types and thread fields

- The sheet cut's cull (#10301) and the tapped wall's (#10227) use each
  body's exact box (`exact_box`), not its display mesh.
- model.threads replies with develop's thread fields: form, hand (right,
  left or both) and starts.
- surface.boundary's schema checks a network curve's continuity as the
  handler does, and words a list entry's error as it does.
- Main's new tests take develop's Hole fields and world_state's Result.
- mesh-decisions: contact_sweep's triangle check is now `depth` (main
  split `hits`).

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

* Known failing: the 14 cargo tests SMLib v1.1.0-alpha fails, with what each sees; two cases that pass now

Develop's CI after the a18bbe5 pin (run 41522001173) fails 14 tests that
passed on f916a8d, and passes two listed ones. Each new line says what the
test sees on v1.1.0-alpha; v1.1.0-alpha.1 fixes the setback hole's cause.
shell_tangent_chain_that_opens_every_face_explains_how_to_keep_walls and
oracle 37-pyramid-apex-blend pass and leave their lists.

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

* Cargo.lock: develop's profile, smlib and step crates at main's 0.30.1

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

* Kernel rig: main's six new sketch-only commands are exempt from a kernel case

sketch.construction_line (and its horizontal and vertical forms) and
sketch.spline.insert_point, remove_point and reverse change only the 2D
sketch.

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

* Cuts leave the bodies their tools can't reach, by exact boxes (#10225 on exact geometry)

- The per-target cut loop skips a body whose exact box lies clear of
  every tool's, as main's version did with mesh boxes. The sheet culls
  (#10226, #10301) and this one now leave the far sheets and bodies alone:
  a_cut_leaves_bodies_it_cannot_reach_as_they_were passes and leaves the
  known-failing list.
- a_cut_still_runs_where_its_tool_touches_grazes_or_meets_a_curved_face:
  a tool 0.5 µm clear of the block is now left out (exact boxes; 0.5 µm is
  far past the bodies' tolerances); touching and 0.1 mm in still cut.
- Known failing, with reasons: main's edge lookup by a stored point
  (#11286, on the display mesh) is not taken; SMLib refuses the
  wrong-way chamfer for its tangent chain first.
- EXEMPT: main's eight new sketch-only commands.

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

* Kernel rig: main's new view, rendering, appearance and settings commands are exempt from a case

62 of the commands main added change no geometry: the view and render
panes, rendering lights and results, appearance tools, settings profiles,
point clouds (drawn as points, never a body), the selection, two sketch
edits, component links and History bar visibility, edit.repeat,
timeline.play, inspect.compare_picture (a picture against the outline),
and component.paste_from_design, which pastes the app's cross-window
clipboard that no command fills. Each joins EXEMPT's first section under
its family's reason.

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

* Kernel rig: construct.plane.axis_point gets construction cases

The plane through a box edge and its far corner, graded against its
closed-form normal (0,-2,3)/sqrt(13) through the origin, and the refusal
of a point on the axis.

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

* Kernel rig: assembly cases for mass properties, align, find limits and moving items between parts

- assembly.mass_properties: two boxes (2 g/cm3) in a rigid group: mass
  0.006 kg, centre (18.333,5,5), inertia diag(0.1, 1.0333, 1.0333) kg mm2
  by the parallel-axis theorem.
- occurrence.align: a block's end face onto a base's top face: a quarter
  turn about y, centre (70,20,20), box (65,15,10)-(75,25,30).
- joint.find_limits: a slider between two stops: min -20, max 10 mm.
- component.move_features, browser.reparent and
  component.move_sketches.to_parts: items moved into a turned and moved
  part keep their world place (volumes, centres and boxes by hand).

The mass case assigns a material it creates, so material.create and
material.assign leave EXEMPT. PATHS names the new command modules.

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

* Kernel rig: export and print face cases in the import area

- file.export_step, export_stl, export_3mf, export_obj: a 30x20x10 box
  written and read back (the meshes converted to a solid): V 6000,
  A 2200, 6 faces, box (5,-10,0)-(35,10,10).
- file.export_urdf: a hinged arm on a grounded base: 3 links, 2 joints,
  and the base's mesh, written in metres, reads back as the
  1 x 0.5 x 0.2 box (V 0.1, A 1.6).
- body.print_face: a cylinder's flat end is accepted, its side refused.

PATHS adds the URDF writer and the print face modules.

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

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: brett19 <brett19@gmail.com>
2026-11-02 22:53:32 +00:00
dudedagger42 6be5be7f0c Keys: option to keep model keys out of sketches (#7675)
* Keys: a sketch and the model kept apart on Scope

`command_for` fell back to the first enabled command bound to a key, so a model key pressed in
a sketch (F, Fillet) still ran the model command. A key now runs only a command of the scope
the view is in, or one that works everywhere; otherwise nothing fires. Inspecting, views and
file commands (`inspect.*`, `view.*`, `file.*`) count as everywhere, so Measure's I still works
in a sketch. #4432's `sketch_and_model_keys_are_kept_apart` is ported to `Scope`.

Not ready: main's `the_extrude_key_in_a_sketch_finishes_the_sketch` (#2815) expects E in a
sketch to finish it and open Extrude, which this rule stops.

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

* Keys: keep model keys out of sketches as an opt-in preference

The strict sketch/model split is now Preferences > Shortcuts > Keep model
keys out of sketches, off by default. Off, a key resolves exactly as on
main (a feature key such as E still finishes the sketch). On, a sketch
runs only sketch keys and keys that work everywhere (inspect, view, file).

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

* rustfmt

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 19:24:51 +00:00
austin62jake ce6ad21106 macOS update: say to quit SolidCraft before replacing it (#7584) (#7886)
* claim: #7584

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

* macOS update: say to quit SolidCraft before replacing it (#7584)

After Download and open, the update window told people to drag SolidCraft
to Applications, replacing the old copy, then restart it. Finder refuses to
replace an app that is running ("the item “SolidCraft” is in use"), so the
message now says to quit SolidCraft first, then drag the new copy over and
open it again. "Quit" rather than "close": closing the window on macOS can
leave the app running.

The id update.dmg_opened is unchanged; every catalog that carries it is
updated, and the user guide's update tables get the same step.

Fixes #7584.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 18:50:09 +00:00
Mateusz Sych dadefec702 Joint dialog: Find Limits fills the limits from where parts collide (#5968) (#7047)
* claim: #5968

* Joint dialog: Find Limits fills the limits from where parts collide (#5968)

joint.find_limits sweeps each value of a joint both ways from where it is,
on a copy and with its own limits left out, and answers where the parts it
moves first touch another part, where a closed loop stops closing, or that
nothing is met within a turn (angles) or the parts' reach (distances).
Edit Joint's Limits section gets a Find Limits button that runs it on the
joint as the dialog has it, off the window thread, and fills the rows.

joint.sweep now tells touching bodies apart from the other bodies of the
same two parts: on the Hinge Assembly sample the pins touch where it starts,
and the plates used to pass through each other unnoticed.

* find_limits: read the session only

* find_limits: a joint with nothing to meet, edits during a search, tiny ranges

* find_limits: keep touching at the start per pair of parts

Per body, the Hinge Assembly sample's plates, which only come flush in
the end plane the two leaves share, stopped the search. The leaves never
pass into each other: the sample's answer is a whole turn.

* find_limits: touching boxes cost no collision work; stale results and rounding

* find_limits: each pair of bodies of resting parts held to its own depth; loops without bodies

* find_limits: a 32-bit budget it can pass, a repaint with the result, a bad form says so first

* find_limits: each pair of resting bodies gets its own margin

* find_limits: Arabic, Hebrew and Vietnamese strings (complete catalogs on main)

* find_limits: search the joint with Edit Joint's side picks

Edit Joint now keeps the joint's sides as picks in its inputs, and its OK
commands compare them with what it opened on. Built without them, the
commands Find Limits applies first asked for the sides again, so every
search failed.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-02 18:28:28 +00:00
PaddleStroke 89f1234f28 Browser: placements start closed (#9274) (#9658)
* claim: #9274

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

* Browser: placements start closed (#9274)

A large assembly opened with every placement unfolded, so the browser was a
long list to scroll through. Placements now start closed; opening one shows
its own placements still closed, and closing and reopening it leaves them as
they were. Find in Browser, a pick in the view, Expand All and Collapse All
open and close them as before.

Preferences > Interface > Components start open (browser_components_open)
keeps every placement open from the start, as before.

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

* Browser: scenarios and the icon audit open placements, which now start closed

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

* Browser: tests that read rows inside placements set the open-placements preference

Three tests added on main since this branch started (joint folders, Features
folder marks, group rename) look at rows inside placements, which now start
closed.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <tenbee91@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-02 16:39:25 +00:00
bherbruck c7547eb3a8 Extrude: a face picked off the profile's plane is the target by default (#11201)
With a profile or face picked, clicking a face, vertex or parallel plane that is
not in the profile's plane now sets Extrude to To Object, not one more profile.
Clicking into the Profiles box first makes the next picks profiles again.
Preference extrude_auto_to_object (Tools) turns it off.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:00:40 +00:00
Colin Faulkingham b6aabb88ff Insert Design: place reused links from their parent origin (#10813) (#10825)
* claim: #10813

* fix: place reused linked designs from parent origin (#10813)
2026-11-01 21:47:53 +00:00
Kushagra Kumar 446590dca1 fix(engine): report design variant in timeline.inspect and accept in timeline.add (#10748) (#10792)
* fix(engine): report design variant in timeline.inspect and accept in timeline.add (#10748)

* ci: refresh checks to clear cancelled workflow run
2026-11-01 21:00:44 +00:00
kaltenbrunnermarkus b557f22f97 Sketch: a construction/normal switch in the construction line tools (#10258) (#10551)
* Sketch: a construction line with a length can be a normal line (#10258)

`construction: false` on a construction line command that has a
`length` makes the segment a normal sketch line, ready to close a
profile, instead of construction geometry. The default stays
construction, and an infinite line is always construction.

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

* Sketch: a length box in the construction line tools (#10258)

The horizontal, vertical, parallel and perpendicular construction line
tools show a Length box beside the pointer from the start. It stays
empty (shown as infinite) and leaves the keyboard to the tool's
shortcuts until a digit is typed; then the box takes the keyboard.
Without a length the tools work as before: one click, an infinite line.
With a length the first click sets the start and a second click the side
the segment runs to, sent as the command's `length` and `near`, so the
segment gets its length dimension. The angle tool has a Length box
beside its Angle box. A typed length keeps the start's snap, and the
preview draws the segment the click will create.

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

* Sketch: a construction/normal switch in the construction line tools (#10258)

A Construction | Normal switch sits under the Length box of the
construction line tools. Normal sends `construction: false`, so a
segment with a length becomes a normal sketch line, ready to close a
profile, without converting it afterwards. The switch starts at
Construction with each start of a tool. An infinite line is always
construction, so without a length the switch is greyed out and its
tooltip says why. It belongs to the tool alone: the Sketch Palette's
drawing line type (X) is left as it is.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:15:21 +00:00
PapaEcho ce72abacf9 Construct: plane through an axis and a point (#10178) (#10472)
* claim: #10178

* feat: add plane through axis and point (#10178)
2026-11-01 09:15:25 +00:00
Leo 24b91bd453 Sketch Concentric: accept a circle or arc and a sketch point (#10487) 2026-11-01 07:57:43 +00:00
C.Vengels 7bafc99390 Send to Slicer: the Print face input takes one face for each body sent (#10468)
With several bodies to send (after Add all bodies, say), the Print face
input refused every face. It took a print face only when exactly one
body was sent; any other pick was cleared at once, with a note in the
status bar only. Each body's print face then had to be set with Ctrl+B,
or by sending that body alone.

The input now holds the print face of each body being sent. A face
picked in it becomes its body's print face, in place of the one it had.
A click on a body's print face takes it out and clears only that body's.
A face of a body that is not being sent is refused with a message
(`ui.print_face_body_not_sent`, in place of `ui.print_face_one_body`).
Each change is still one `body.print_face` command, so it undoes on its
own. Picks in this window are no longer widened to the other pieces of
the face's surface, so a click on a print face takes it out rather than
adding its neighbours.

The user guide (guide/files.md) says so. The new message is in
assets/locales/pending/print-face-each-body.json, in all 31 languages;
the old one is removed from the catalogs.

Test: dialogs_save_mesh::tests::the_print_face_input_takes_one_face_for_each_body_sent
(fails without the change: the second body's face is refused).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:44:15 +00:00
kaltenbrunnermarkus 9d01f6e005 Sketch: construction lines with a length (#10258) (#10427)
* claim: #10258

* Sketch: construction lines with a length (#10258)

The horizontal, vertical, angle, parallel and perpendicular construction
line commands take an optional `length`: the line becomes a construction
segment from `at`, running towards `near`, held by a length dimension
that follows its expression. Without `length` the line stays infinite,
as before.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 04:13:03 +00:00
acebear17 9475e0e1a4 Docs/design approach 10063 (#10409)
* claim: #10063

* docs: add a design approach and workflows chapter to the user guide (#10063)

Adds guide/design-approach.md after the window tour: the parametric idea, principles for designs that change well, the timeline and rollback marker, bodies vs components and occurrences, common workflows with links, a term map for users of other CAD software, and where to find help.

* docs: match the chapter to the app's labels and sketch colours (#10063)

Checked in the web app (0.29.0): the timeline strip is headed History, the toolbar shows New Sketch and Parameters for Create Sketch and Change Parameters, and fully constrained geometry turns white in the default dark theme rather than dark.

* docs: tighten the design approach chapter's wording (#10063)

The glossary covers most, not all, of the chapter's terms; Redo moves forward; the timeline is shown as the History strip; Report a bug drafts a report for the user to file and sends nothing itself.
2026-11-01 02:27:40 +00:00
raykholo e4a67d1e30 Assembly: move sketches to the part they're drawn on (#10218) (#10219)
* claim: #10218

* Assembly: move sketches to the part they're drawn on (#10218)

`component.move_sketches.to_parts` moves each given sketch, or every sketch in the design, whose
plane is an exactly attached face of a part's body into that part through
`component.move_sketches`, as one undo step. Sketches with no single part to go to stay where they
are, and the reply lists what moved and what stayed, with the reason. The Sketches folder's menu
offers it for the folder's sketches (or the selected ones), and a sketch's menu for that sketch or
the selected sketches; the status bar says how many moved and why the others stayed.

`component.move_sketches` now keeps a face-attached sketch where it is on its face: the sketch is
authored in the new component's placement (the face's own when the component owns the face), and
its picked point and repair plane are carried into it. Before, a face-attached sketch moved out of
the design was left without an author placement and failed to evaluate.

* Tests: the moved part is free to move; face anchor path

* Tests: move the to_parts test to the component command tests

* Shorter label: Move Sketches to Their Parts
2026-10-31 23:42:47 +00:00
semmelrinde 80fb02f0b8 Sketch Ellipse: Major radius and Minor radius boxes while drawing (#10279) (#10282)
* Sketch Ellipse: Major radius and Minor radius boxes while drawing (#10279)

The Ellipse tool showed no value boxes. After the centre it now shows
Major radius, after the end of the major axis Minor radius; a typed value
places the point at that size and becomes the ellipse's dimension, as for
a circle's Diameter. sketch.draw.dimensioned takes major_radius and
minor_radius for sketch.ellipse, so Dimension new shapes covers it too.

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

* Sketch Ellipse: test the typed radii in the tool and in sketch.draw.dimensioned, and say so in the guide (#10279)

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

* Sketch Ellipse: rustfmt the new engine test (#10279)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:18:35 +00:00
Nathan Dane 4594d81f7a Gruvbox: light and dark themes with three contrast choices (#10292)
* claim: consolidate Gruvbox contrast variants

* Consolidate Gruvbox variants into light and dark contrast choices

* Check both original Gruvbox viewport gradient endpoints

* Reset Gruvbox scenario state before independent theme checks
2026-10-31 21:58:30 +00:00
raykholo 2e3448f348 Sketch: an option to make the part active when sketching on its face (#10215) (#10216)
* claim: #10215

* Sketch: an option to make the part active when sketching on its face (#10215)

Preferences › Sketch › "Sketching on a part's face makes that part active", off by default.
When it is on, a sketch started on a face of another component's body activates that component
at the picked placement first and is made there, as after an explicit activate. The part stays
active, so what is built from the sketch lands in the same part.

For scripts the same is `sketch.create` `activate_part: true`; the reply's `activated` names the
component and placement. On an error the active component goes back to what it was.

* Tests: a free part, no edge projection, the sketch on the face plane
2026-10-31 21:52:09 +00:00
Ben Rog-Wilhelm 8be56316f6 Sketch Chamfer: several corners at once, as Fillet does (#8998)
The three sketch chamfers take `points: [corners]` (a point reference or
{a, b} each), as sketch.fillet does: every corner is cut alike in one
edit, and a corner that can't be cut, or is named twice, refuses them all.
The Chamfer window sends every picked corner instead of refusing a second.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:28:14 +00:00
froglemonade2 0476154973 Update Links: linked designs that changed while the tab was away come back as one undo step (#9743) (#9753)
* claim: #9743

* Update Links: linked designs that changed while the tab was away come back as one undo step (#9743)

Coming back to an assembly's tab read each changed link in an Update Links
run of its own, so each became its own undo step: one Undo took back one
component and left another copy of the same part at the new version.

component.update_links now takes `components`, a list, and goes on past a
design it cannot read: that link keeps its bodies, is named in `failed` and
gets a warning. The tab return sends one request with every changed link,
records as read only the ones that were, and says on the status line which
one could not be read.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:04:05 +00:00
HiroKenEz 8f259d5b83 Sketch: the Line tool can switch to a tangent arc and back without changing tool (#9489) (#9553)
* claim: #9489

* Sketch: the Line tool can switch to a tangent arc and back without changing tool (#9489)

With Preferences > Sketch > "Rest on a line's last point to switch to a
tangent arc" on (off by default), leaving the chain's last point and resting
back on it for 0.35 s makes the next click a tangent arc, by the existing
sketch.arc.tangent command, and the same gesture goes back to lines. A label
at the cursor says which. sketch.arc.tangent's end can now name an existing
point, so an outline drawn this way closes.

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

* Sketch: the cursor label names the command in words (#9489)

The label asked the catalog for a message id that doesn't exist, so it showed
the id. It now takes the command's translated label, and the test checks the
words it paints. Moved clear of the pointer's own badge.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:21:59 +00:00
HiroKenEz 5bb3b6717f Equation curves: the variable can carry its range's unit (#9333) (#9549)
* claim: #9333

* Equation curves: the variable can carry its range's unit (#9333)

A curve can set variable_unit "range" (or tick the new box in its window): the
variable is then a length, so a ^ 2 / (4 * t) is a length when a is one. Start
and end must both be lengths, or one a length and the other a plain number in
the design's unit; anything else is refused. Saved only when set, as one pending
format change; without it curves behave as before.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:54:06 +00:00
austin62jake 3c0d4e120e Linked designs: one changed while the assembly was closed is read again when it opens (#9739) (#9872)
* Claim #9739

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

* Linked designs: one changed while the assembly was closed is read when it opens (#9739)

A link now keeps the SHA-256 of its design file's bytes as the assembly
last read them (DesignLink::file_sha256, a pending format change), set by
Insert Design, Relink, Extract to File and Update Links. component.links
reports `changed` from it, hashing a file again only when its time or
size changed (vfs::design_path_stamp, behind the design-path rule). The
app treats a link it meets for the first time whose file changed as not
read yet, so opening an assembly, in its own tab or in place of the blank
design, reads those parts again as a tab coming back does.

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

* Linked designs: read what changed last when a design opens in place of the blank one (#9739)

A read is taken up only by the design revision it started from, and
adopt bumped the revision after starting it, so the read was dropped.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 07:28:24 +00:00
momysnow da23eb1377 Sketch dialog follows "Dialog buttons at top" (#9860) (#9884)
* claim: #9860

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

* Sketch panel follows Dialog buttons at top (#9860)

The Sketch panel drew Finish Sketch, Look at and Slice in a footer under
its options even with Preferences > Interface > Dialog buttons set to
Top. Panel::palette now reads the same published preference as the
feature cards and draws its footer row under the header, above the
options, when it is Top. With the preference off nothing changes.

Changes a default: no

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

* Docs: Dialog buttons at top also moves the Sketch panel's Finish row (#9860)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 06:23:41 +00:00
PeppaWutz 9d8ab72da7 Ribbon tooltips: help for every remaining toolbar command (#9060) (#9868)
The last 106 commands on the backlog get the two-sentence help under
their tooltip, in English, German, Japanese, Chinese, Arabic, Hebrew and
Vietnamese: Surface, Sheet Metal, Plastic, Inspect, Construct,
Configure, Parameters, Library, View and the few single ones (Bill of
Materials, Frame Member, Pattern on Path, Convert Mesh, checkpoints).

With that the backlog is empty, so it goes: the test is now
every_toolbar_command_has_help, and the docs that mentioned the backlog
say so. The help tests use sketch.drag, which is not on the ribbon, as
the command without help.
2026-10-31 05:04:31 +00:00
Nathan Dane 160662ffec Tab colours: per-tab surfaces and fade (#8876) (#9808)
* claim: #8876

* Resolve theme tab surfaces with cached Oklab transitions

* Apply tab surfaces to unauthored Render preview backgrounds

* Allow quantized endpoint colours late in cubic Render fades

* Keep tab surface publication from restyling ordinary widgets
2026-10-31 04:39:12 +00:00
snowsh1ne 2f6ca7e46b Linux: optional system title bar (server-side decorations) (#9263)
On Linux the window always had no OS decorations and drew Windows-style
caption buttons in the application bar. SOLIDCRAFT_TITLEBAR=system, or
"titlebar": "system" in graphics.json, now opens the window with the
desktop's own title bar and buttons (server-side decorations where the
compositor offers them). The default is unchanged; Windows and macOS
are unaffected.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:12:39 +00:00
Nathan Dane d7cba9e96d Preferences: theme options list and the opacity option (#8876) (#9807)
* claim: #8876

* claim: #8876

* Theme options: persist per-theme values and expose preferences contracts

* Restore selected theme mode after stopping a followed theme

* claim: #8876

* Preferences: render theme options and apply readable panel opacity

* Use floating-point scale expectation in theme preferences scenario

* Keep panel opacity colour construction inside theme tokens

* Complete theme options translations in Arabic Hebrew and Vietnamese

* Let standalone theme sliders fill their option cells
2026-10-31 04:01:29 +00:00
thocsmith 3b77567932 Docs: joint flip needs x for point origins; checkpoints are single use, 16 kept (#9746) (#9844)
joint.create's flip text and the assemblies guide said two origins at one
point with the same z stay as they are; a point origin without x takes its
x from z, so a part turned about z turns back. Say so, and point to giving
x on both origins or joint.as_built.

edit.checkpoint and edit.restore_checkpoint now say that restoring or
forgetting uses the checkpoint (and later ones) up and that only the last
16 are kept, and the "no checkpoint N" error says why. The editing and
mechanisms skills no longer read as if a checkpoint can be restored twice.

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 03:48:13 +00:00
Nathan Dane ca005237a1 Theme files: options, overlays and requires (#8876) (#9805)
* claim: #8876

* Theme files: add options, overlays and requirements
2026-10-31 02:25:34 +00:00
PaddleStroke 3877ef08b9 Measure: drag the labels away from the part (#8510) (#9395)
* claim: #8510

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

* Measure: drag the labels away from the part (#8510)

The distance and X/Y/Z labels in the view follow a drag of the pointer and keep
a leader to what they measure; a double-click or a new measurement puts them
back. A press on a label picks nothing, starts no box selection and opens no
Select Other.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:00:23 +00:00
PaddleStroke cce337f83f Sketch: alignment guides from any hovered point while drawing (#9105) (#9673)
* claim: #9105

* Sketch: alignment guides from any hovered point while drawing (#9105)

While a drawing tool waits for a point, hovering a line or arc end, a
rectangle corner or a free point wakes the same temporary horizontal/
vertical tracking guide as a circle centre or line midpoint. Idle
hovering keeps centres and midpoints only. Preference
sketch_tracking_points (on) turns it off.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:00:07 +00:00
Colin Faulkingham 8311ae5c5d BOM: group purchased children under inseparable parts (#9744) (#9794)
* claim: #9744

* Fix grouped BOM children for inseparable parts
2026-10-31 01:17:58 +00:00