Commit Graph
813 Commits
Author SHA1 Message Date
lillith 7d66a4d0ab Clarify Drawings preservation manifest and published index hash
Source preservation only. The manifest now records the actual published index README rather than the initial selection note, and states its pre-Git-normalization hash scope. Original product proposals are unchanged. The canonical index links all branch checkpoints; validation and owner handoffs remain pending.
2026-10-29 11:00:46 +01:00
lillith 0b3bc6e968 Preserve remaining Drawings original source proposals and canonical code index
Canonical preservation of W10, W21, X4 and supporting W38 source proposals. Sources remain deliberately unregistered and unexecuted, with real bases and alternatives documented in proposals/drawings/README.md. Existing contributor claims and PRs remain authoritative; this is not a merge-ready integration candidate.
2026-10-29 10:57:57 +01:00
github-actions[bot] fe3fb381b1 Regenerate generated files and fold pending files [skip ci] 2026-10-28 18:21:33 +00:00
cronos b72165fd30 Sketch: show temporary centre and midpoint alignment guides (#7903)
* Sketch: show center and midpoint alignment guides

Add transient per-viewport horizontal and vertical guides with a sketch preference. Related tests, native UI, WASM and supplementary Linux checks pass. Full CI remains blocked by unchanged Windows engine dead-code Clippy errors.

* Sketch: complete alignment-guide translations

* Tests: compile chosen-program mutex only on Unix
2026-10-28 16:07:05 +00:00
semmelrindeandClaude Opus 5.5 c8c9534022 Create Sketch: the grid previews the origin plane under the pointer (#6975) (#7404)
* claim: #6975

* Create Sketch: the grid previews the plane under the pointer (#6975)

While Create Sketch waits for its plane, the grid now lies on the origin or
construction plane under the pointer, so the sketch's plane shows before
the click. A plane's browser row under the pointer lights that plane in the
view, and previews it the same way.

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

* Create Sketch: the planes at the top of the History preview too (#6975)

main now draws the Origin rows at the top of the vertical History as well
(history_rows). Hovering a plane there lights it like its row in the browser:
the plane is handed over the same way the hovered bodies are.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:58:33 +00:00
Christian HammacherandClaude Opus 5.5 aae2c5e417 Assemblies: edit a linked component in place, with the assembly around it (#7672) (#7819)
* Links: update linked designs that changed while the assembly's tab was away

A design coming back to its tab compares the modification time and size of its linked
design files with what they were when it went to the background, and runs Update Links
on the worker thread for those that changed: saved in another tab, another window or by
a checkout. Preference update_changed_links (Design, Linked designs) turns it off.

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

* Docs: the assembly guide says linked designs update when their tab returns

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

* Links: Vietnamese strings, now that vi is a complete catalog

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

* Links: remember what each design last read, so an update that fails is tried again

A design's linked files are now recorded as the design last read them: when Update Links reads a
link without an error, or when the link is first seen. Coming back to the tab reads again what
differs, one link per run, so a file that cannot be read holds up no other and is tried again
the next time, and a part changed while the assembly was shown is taken up on its next return.
Closing several tabs together reads nothing on the way, and the web build, which keeps no file
times, looks at nothing.

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

* Links: no read during a save-changes prompt, records for adopted designs and missing files

A tab shown to answer "save changes?" reads no link before its save, and Cancel there or on a
batch lets the tab left in front read what it skipped. A design opened in place of the blank one
records its links at once, a link whose file is missing is recorded as missing so it is read when
the file comes back, and a read queued for a design that is no longer shown by the time it starts
is dropped. More changed links than can wait at once are read on the next return.

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

* Assemblies: edit a linked component in place, with the assembly around it

Activating a linked component opens the design it comes from in a tab of its own, with the
rest of the assembly drawn faded around it where the component sits, so the part's own history,
browser and commands are at hand. Finish Editing in the browser saves the part, goes back to the
assembly and closes the part's tab when the edit opened it; coming back to the assembly updates
the changed link (or Finish Editing runs Update Links when that is turned off). Update Links in
the part also reads the assembly's links again, off the window thread, and redraws the assembly
around the part; a result for an assembly changed meanwhile is dropped with a message.

The part is an ordinary tab, so saving, autosave, recovery, closing and quitting treat it as any
other design. The model scene's ghost triangles are now drawn (only the preview's were).
Preference edit_linked_in_place turns it off. Builds on the update of changed links when an
assembly's tab returns.

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

* Edit in place: a double-click opens it, Activate only activates

A double-click on a linked component edits it in place, in the browser as on a face in the
view (around the placement clicked), and the component's menu gets Edit in Place. Activate and
the browser's radio button make it the active component as they always did, so a linked
component can still be activated without opening its design.

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

* Double-click on a component's face: edit a linked one in place, activate any other

A double-click on a face in the view activates the component the body belongs to, at that
placement, as a double-click on its row does; a linked one is edited in place. Bodies of the
design itself or of the active component are left alone, and so is a double-click with a
modifier. One preference, double_click_components, covers both gestures.

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

* Edit in place: fixes from review

An edit whose part never opens is forgotten instead of starting on a later open of the same
file, and the web build starts the edit after its synchronous open. Finish Editing goes on only
for a save into the linked file and in the part's own tab, waits for a "changed on disk"
answer, and says so when the part was saved elsewhere. Showing the assembly no longer drops a
refresh, which now records the links it read so coming back reads nothing twice. The faded
assembly is worked out once per picture, a bad mesh index skips its whole triangle, and the
last two status texts come from the catalogs.

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

* Edit in place: Finish Editing on the MODEL tab and the mouse's back button

Finish Editing is a command now, component.finish_edit, with its schemas: the app window
answers it, anywhere else nothing changes, as with view.fit. While a linked design is edited in
place the MODEL tab ends with a FINISH EDITING panel, pinned at the right as FINISH SKETCH is,
with a new left-arrow icon; the browser keeps only the line naming the assembly. The mouse's
back button, or a keyboard's Back key, finishes too unless a dialog, tool or sketch is open.
The assembly guide describes editing in place.

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

* Edit in place: fixes from the second review

Saving the assembly waits while it reads its links for the part
Finish Editing waits for dialogs, tools, sketches and prompts open in the part, also when run as a command
The assembly reads the edited part explicitly when it does not check its links on its own
The FINISH EDITING panel cannot be customized
A tab opened by an edit stays that edit's to close when the part is entered again
The mouse's back button finishes editing; the keyboard's only while no text field has focus
Refreshing the assembly keeps its selection and active component

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

* Edit in place: fixes from the third review

component.finish_edit run as a command opens nothing and answers with an error when it cannot finish; engine.batch and engine.script refuse it
A double-click or Edit in Place while a dialog, tool or sketch is open in the assembly keeps it open
What changes in the assembly while it reads its links without changing its document stays: picks, activation, components dragged but not captured
Showing the assembly while it reads its links starts no second read
A new or opened design in the part's tab ends the edit
Finish Editing keeps the part's tab open while its agent chat runs

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

* Edit in place: Update Links run as a command in the part refreshes the assembly too

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

* Edit in place: the ribbon button reads Finish Edit, as Finish Sketch does

A bare "Finish" took the catalogs' surface-finish translation (German "Oberfläche").

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

* Edit in place: fixes from the fourth review

Finish Edit waits while the part's agent is in the middle of its turn, instead of keeping the tab of any chat open
A part saved while its assembly refreshes is read again when the edit finishes: the refresh records the files as it found them
A double-click on a linked component while something is open in the assembly says so instead of activating it
Commands are recognised in any spelling the engine takes, so engine.batch and engine.script cannot slip Finish Edit through
Update Links run as a command refreshes the assembly from the control channel only, not for the app's own reads
Moves of components the refresh removed are not carried over, and a refusal is one message
The command description and the guide say that the command does not save the part

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

* Edit in place: the command is Finish Edit, as Finish Sketch is

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

* Edit in place: a command to start it, and fixes from the full review

Added component.edit_in_place {component, path?}: the double-click and Edit in Place in the menu run it, and as a command it asks nothing, so a network location not allowed yet or a design open elsewhere makes it fail instead
Finish Edit asked by the part's own agent finishes once that agent's turn ends; the button still waits for the turn
The parameters and design properties panels, with text typed and not applied yet, hold up entering and finishing an edit
A refresh is not taken up under an undo, a queued command or a feature still being computed in the assembly
Refusals say what holds them up (a refresh, a file being read or written) instead of nothing
The browser strip names the assembly that is refreshing, not any assembly
The schema description says the command does not save the part
Korean strings for every new message

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

* Edit in place: fixes from the second full review

The MCP batch tool refuses component.edit_in_place and component.finish_edit as steps instead of sending them one by one
Edit in Place asked while the assembly's agent is in its turn opens the part once the turn ends; a double-click then is refused rather than stopping the agent
Both commands answer queued: true when they wait for the agent's turn, and stopping the agent forgets what it queued
An undo, a queued command or a delete check running in the design holds up entering and finishing an edit
A refresh redraws every edit of the assembly, the parts in other tabs too
In an assembly never saved, a double-click activates the linked component as before
The control and MCP docs list the new batch and script refusals, and the commands list every way they fail

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

* Edit in place: fixes from the third full review

The MCP batch tool refuses the two commands however a step names them, an execute step too
A queued Edit in Place is checked fully when asked for, a second one is refused, and both queued requests wait for what runs in the design instead of being dropped
Disconnecting the agent forgets what it queued, as stopping it does

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

* Edit in place: help text for the Finish Edit button

main now requires help for every toolbar command (#7134), and Finish Edit
is one. Written in every language the branch's catalog carries.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:57:44 +00:00
raykholo b68d42e5ec Theme: paste a theme into the Import tile (#8226) (#8227)
* claim: #8226

* Theme: paste a theme into the Import tile (#8226)

Ctrl/Cmd+V on Preferences > Interface imports a copied theme file, the tile's
menu offers Paste theme, and a theme file dropped on the tile imports instead
of opening as a design. Pasted text that is not a theme keeps its usual
meaning. The tile reads "file or paste".

* Theme paste: the new strings in Arabic, Hebrew and Vietnamese too (#8226)
2026-10-28 15:28:48 +00:00
github-actions[bot] 7d652d0e1f Regenerate generated files and fold pending files [skip ci] 2026-10-28 17:39:36 +00:00
P'tite TêteandP'tite Tête a4d1b5b6a9 Viewport: fewer ambient occlusion samples while the view moves (#7165) (#7214)
* feat: Fewer ambient occlusion samples while the view moves (#7165)

- Draw AO with 8 samples while the view moves, 32 once it has held still for 0.25 s
- Share the settle tracking with adaptive supersampling and repaint when it settles
- ui.screenshot waits for the full-quality frame (at most half a second)
- Test the sample choice, the uniform slot, and ghosting at both counts

* fix: Set the AO sample count in the scale test frame

- scale_tests.rs, new on main, builds a gpu::Frame without ao_samples
- Use AO_SAMPLES, as the other still-frame builders do

---------

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-10-28 15:05:34 +00:00
dudedagger42andClaude Opus 5.5 4a34a258e1 Logo: an option to draw it in the theme's accent (#8133)
* Logo: an option to draw it in the theme's accent

Preferences > Interface > Logo: Theme's colours (the default, as before) or
Accent, three shades of the theme's accent. ui.prefs logo_accent.

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

* Logo: US spelling for the option's label, as upstream's English catalog uses

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

* Translations for the new options in the five complete catalogs (ar, he, ja, vi, zh)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:04:53 +00:00
HerperlerpicusandClaude Opus 5.5 3a951bc8c3 Sketch: dimension new shapes as drawn (squashed onto current main) (#5987)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:57:54 +00:00
HiroKenEzandClaude Opus 5.5 4fa051c9fc View angles: an optional readout under the orientation gizmo and a command to set them (#6032) (#6249)
* claim: #6032

* View angles: an optional readout under the orientation gizmo and a command to set them (#6032)

View > View angles (also Preferences > Graphics, or view.angle_readout) shows the
view's pitch, yaw and roll in degrees and Ortho/Persp under the axis view or cube,
updated live; off by default. Clicking the readout opens a small card to type
exact angles, which runs view.apply.

The angles are the camera's own (Camera::view_angles / with_view_angles), about
the up axis: yaw is the heading of the line of sight (Front 0, Left 90, Right -90,
Back 180), pitch the eye's elevation (Top 90, Bottom -90), and a positive roll
turns the model clockwise. view.apply documents this convention and now refuses a
pitch past +/-pi/2.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:49:47 +00:00
github-actions[bot] 545232be37 Regenerate generated files and fold pending files [skip ci] 2026-10-28 17:08:18 +00:00
Nathan Dane c1dee61482 Render shots: strip with thumbnails, Update and Revert for camera changes (#6430) 2026-10-28 12:59:40 -04:00
PaddleStrokeandClaude Opus 5.5 8be7bb0201 Insert design: file names get the dialog's width (#8097) (#8122)
* claim: #8097

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

* Insert design: file names get the width the size and actions leave (#8097)

The web app's "Files in this browser" window listed stored files in an egui
Grid sized from cell content: the wide action buttons took most of the fixed
window width, the name and size columns were cut, and the rest was empty.

Add ui_kit::name_list on the kit's table grid: the name column takes the
width the other columns leave, the other columns are as wide as their widest
cell (measured as drawn, a changed measurement discards the frame), and a
name is cut with an ellipsis and its full text on hover only when it does
not fit. The window is a little wider (still capped at the browser width).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:23:31 +00:00
Lars Jansen 67d5051792 Ribbon: keep the SKETCH tab shown outside a sketch (#7904) (#7906)
* claim: #7904

* feat(ui): keep the Sketch tab shown outside a sketch; its drawing tools start one (#7904)
2026-10-28 14:14:08 +00:00
raykholo 5f90d06452 Preferences: reverse vertical pan direction (#5240) (#5454)
* claim: #5240

* Preferences: reverse vertical pan direction (#5240)

Add "Reverse vertical pan direction" beside the horizontal setting in
Preferences > Navigation. pan_reverse_y defaults off, so older settings
files load unchanged, and flips the vertical part of two-finger pan on
every canvas through navigation::handle, independent of the horizontal
setting. Labels in en, ja, zh, de, and ko.

* Reverse vertical pan: strings as a pending locale file, with Arabic, Hebrew and Vietnamese

* Control protocol: pan_reverse_y in the sorted ui.prefs list (#5240)
2026-10-28 13:59:38 +00:00
Nathan Dane 2897d3b22e Render library: Appearances card with a sphere grid, scopes, favourites and recents (#6429) 2026-10-28 13:41:24 +00:00
9bcb2ffe68 Drawings: first landing from the drawings branch (#8249)
* Drawing sheets: sizes, the ISO 5457 border and its marks (#3875) (#5108)

* Drawing sheets: sizes and the ISO 5457 border (#3875)

A sheet is a paper size and an orientation: ISO A0 to A5, Letter, Legal,
ANSI A to F, architectural A to E1, or a custom size (finite, above 0 and
at most 5,000 mm a side), landscape by default and either way for every
size.

A border profile is the rule for its border, and layout() works it out
for a sheet: the frame inside the margins, and the reference fields with
their labels. The ISO profile has a 20 mm filing margin on the left and
10 mm elsewhere, a 0.7 mm frame, fields of 50 mm out from the centre
lines with the corner fields taking what's left, rows lettered from the
top without I and O (AA, AB and on past Z), columns numbered from the
left, and an A4 sheet labelled on its top and right only. A profile can
divide the frame equally instead, or have no grid.

The records sit in geom, next to the section records, so saving,
rendering and export can all use them. Nothing is saved or drawn yet:
there is no command and no format change. Margins that leave no drawing
space, values that aren't finite, fields outside 1 to 1000 mm, more than
200 equal fields and unknown JSON fields are refused.

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

* Drawing sheets: ISO 5457's centring and trimming marks (#3875)

A drawing border profile now also holds the grid reference border and the
centring and trimming marks, either of which can be left out. The ISO
profile follows ISO 5457:1999 (read in its identical adoption,
IS 10711:2001):
- the grid reference border 5 mm outside the drawing border on every
  side, with the field labels between the two;
- four centring marks on the sheet's centre lines, from the grid
  reference border to 10 mm inside the drawing border;
- at each corner a trimming mark of two overlapping 10 x 5 mm
  rectangles, in the margin outside the grid reference border.

The labelled sides are now a rule worked out for each sheet: ISO 5457's
(the top and right of A4, all four sides of the others) or fixed sides.
So one profile labels every size correctly. validate() checks what
doesn't depend on the sheet, for when a profile is read; layout() calls
it, then checks the rest against the sheet.

Refused:
- a grid reference border that isn't finite, is negative or is wider
  than a margin;
- a centring mark that isn't above 0, would reach past the sheet's
  centre lines, or is on a drawing border those lines miss;
- a trimming mark that isn't above 0, is wider than it is long, is
  wider than the margin left outside the grid reference border, or is
  longer than half the sheet's shorter side.

The grid rule's JSON is now externally tagged ("none",
{"from_centre": {"field_mm": 50}}), as the sheet size is, so an unknown
field is refused in every form; the internally tagged "none" ignored
one. Nothing is saved yet, so the shape change costs nothing.

The names follow the glossary, and ISO 5457's "drawing sheets": the
module is drawing_sheet (was sheet, which means sheet metal elsewhere
here), DrawingBorderProfile and SheetLayout (were BorderProfile and
BorderLayout), sheet_border and drawing_border (were paper and frame),
and Sheet::size_mm (was paper_mm). The first part of S1 is still a draft,
so the renames are made here rather than in a commit of their own.

The extra centring marks ISO 5457 asks for on sheets larger than A0
aren't here yet. Still nothing is saved or drawn.

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

* Drawing sheets: a lighter built-in border, the filing margin a choice (#3875)

Cameron's decisions after seeing the sheet in the app, 2026-10-23:
- No trimming marks in the built-in profile: the filled corner blocks
  take a lot of ink. trimming_mark_mm stays, so a profile can still ask
  for them.
- Centring marks stop at the drawing border: from the grid reference
  border to the drawing border, never into the drawing area (on A4 they
  crossed the tables). centring_mark_mm keeps its meaning, how far a
  mark reaches inside the drawing border, and now takes 0, the built-in
  value, as grid_border_mm already takes 0 for "on the drawing border".
  A mark of no length (a reach of 0 with no grid reference border) is
  refused.
- Margins of 10 mm all round. ISO 5457's 20 mm filing margin on the
  left, for drawings that get bound, is a named choice: filing_margin,
  off when not given. On, it stands in for margins.left_mm, which is
  kept, and still checked, for when it's off again. The 20 mm is
  FILING_MARGIN_MM, and validate() and layout() both take the margins
  from one helper, laid_out_margins().

A3 landscape is now 400 x 277 at (10, 10), with eight 50 mm columns.
With the filing margin it is 390 x 277 at (20, 10) again, the same as a
20 mm left margin set by hand. A profile with all three choices on
(ISO 5457's full border) lays out exactly as the built-in profile did
before, and the tests keep the old hand values for it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Drawing W23a workshop plate oracle fixtures (#4950) (#4962)

* test(oracle): reviewer-aligned parameterized workshop plate fixtures (#4950)

Independent analytic W23 expected values and seven verified models,
including four R10 corners and the 2 mm tangent top chamfer.
Document accepted Drawing states and commit regenerated docs/oracle.md
from successful workflow run #29 (90/90 pass).

* fix(oracle): correct W23 chamfer centroid and clarify drawing session ownership

* Paper scene: plain records of what is drawn on a sheet (#4890 A1) (#5513)

geom::paper holds what a sheet shows as plain data: groups of strokes and fills, each group tagged
with the object that drew it (an opaque id) and its kind, with one validation pass over a bounded
record count. Seg2's checks and the coordinate rule move into profile.rs so section and paper share
them, and drawing_sheet takes its side and line limits from paper, so each limit is written once.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Drawing sheets: centring marks across the whole margin (#3875) (#5554)

The built-in sheet's centring marks now run from the trimmed sheet's edge to the drawing border.
From the grid reference border, as before, a mark was the same length as a grid field mark, and on
A3 it sat on one (the sheet's centre falls on a field edge), so it no longer read as a mark once the
grid reference border's outline is left out (Cameron, after the prototype on #3875).

- DrawingBorderProfile.centring_mark_start: sheet_edge (the built-in's, and when not given) or
  grid_border (ISO 5457's figure 5); ISO's full border in the tests asks for grid_border.
- A mark still needs a length: one that stops on the drawing border needs room outside it on the
  side it starts from, every margin for sheet_edge, the grid reference border for grid_border.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Drawing sheets: no centring or trimming mark longer than the largest sheet's side (#3875) (#6410)

A border profile capped every length at 5000 mm (the largest sheet's side) but two: a
centring mark's reach inside the drawing border and a trimming mark's length. A file could
give either a huge whole number, such as 18446744073709551615. The read's number check passes
whole numbers, so the profile took it as 1.8e19 mm, and saving wrote it as a decimal the next
read refused: a drawing that saved but didn't open again. K2's fuzz test of drawing files
(#3890) found it.

Both now take the cap the margins have, `MAX_SIDE_MM`, in `validate`, so the file is refused
when it's read. Layout's own checks against the sheet are unchanged, so a 5000 mm mark still
validates and is refused only on a sheet too small for it.

Tests: the hostile-profile test refuses 5000.000001 mm and 1.8e19 mm for each mark, and takes
5000 mm on its own; 4 planted faults (each cap removed or made one short) are all caught.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Bill of materials types: phantom, reference, purchased and inseparable (#3874) (#5107)

* Bill of materials types: phantom, reference, purchased and inseparable (#3874)

A component's bill of materials type is a part property (`bom_type` in its metadata, set with
component.metadata.set and read by component.metadata, as part_number is), so older releases
keep it and no format change is needed. It isn't a design property: the Design Properties
dialog doesn't list it and document.metadata.set refuses it. A word this build doesn't know
reads as normal and is kept.

assembly.bom counts by type: a phantom has no row and what it holds counts where it is placed,
in the same rows as the same parts there; a reference counts nowhere, and nothing inside it
does; purchased and inseparable components are one row, and in parts only an inseparable
component's purchased parts are listed too. Rows of one part number but different types are
different rows. Rows and children carry bom_type. With no types set the rows are as before.

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

* Glossary: bill of materials and bill of materials type (#3874)

The bill of materials types are a new idea, so the glossary gets their
term, with the five types, and "normal" gets its fourth meaning in the
table of words with more than one meaning.

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

* Bill of materials types: a plain line for each (#3874)

The guide's table and the glossary entry say what each type is for in
plain words: something you make, something just to group, something to
use as a reference, something you buy, something made as one piece.
The names stay the five standard ones.

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

* Bill of materials: kits fold together only when their contents' types match (#3874)

With group_children, two kits with one part number share a row when what they hold reads the
same. That reading (kit_key) compared each child by part number and count, but not by its bill
of materials type, though rows themselves are told apart by type. So a kit holding a made bolt
and a kit holding a bought one, both KIT, folded into one KIT x2 row listing the first kit's
bolt twice: the other type was lost, and which one depended on creation order. 0xlillith found
it in review of #5107.

kit_key now includes each child's type. Equal part number, type and contents still fold.

Test: two KIT kits each holding a BOLT fold into one row of two; once one bolt is purchased they
are two rows, each listing its own bolt. Planted fault (the type left out of the key): caught.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Drawing files: a drawing saved as a container or as plain JSON, never read as a design (#3890) (#7098)

* File format families: drawings share the design format's versioning (#3890)

The format code works for a family of files: its own tag, its own numbers and
its own folder of change files, with one reading of a file's format, one
refusal of a file a newer build saved and one upgrade for every family. Designs
(solidcraft/N) are one family and keep everything they had: FORMAT and FORMATS
are as they were, and pending_names, format_string, version_of and read mean
what they did. Drawings (solidcraft-drawing/N) are the other, with format 1 as
their first record and each later change one file in
crates/doc/src/drawing/format/pending/, as design changes are. Nothing saved
changes.

A design tag never reads as a drawing's or the other way round, and a file that
names no format is a design from before format strings, never a drawing. Every
refusal names its family; a design's read word for word as before. A design
listing a pending change this build doesn't have is now refused as a newer
build's before its content is checked, as a newer number already was, so a
newer build's fields never read as damage. The list reader is
take_names(v, key, noun), the shape #4811 uses for its own list.

For the maintainer, the release step changes: scripts/fold-format-pending.sh
now folds every family that crates/doc/build.rs lists (FAMILIES), each into
its own next number: designs into crates/doc/src/format/v<N>/ as before,
drawings into crates/doc/src/drawing/format/v<N>/ from 2. With nothing pending
in a family it says so and moves nothing. Its test folds a drawing change into
v2 beside the design changes. docs/releasing.md, CONTRIBUTING.md and AGENTS.md say so.

build.rs lists each family's changes from its own folder, each folder's
versions contiguous from its own base.

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

* Drawing record: format 1 of the drawing family (#3890)

A drawing is now a record of its own (crates/doc/src/drawing.rs): its name,
the designs it draws (sources: an id and the design file's path, relative to
the drawing's folder as a linked design's is), its properties under the design
metadata's rules, its units (each design's own, or one length unit of the
expression unit table), its content language (a BCP 47 tag) and its sheets.
A sheet holds its paper, a copy of its drawing border's profile, the
resolution its pictures are rendered at (300 dpi unless it says, 25 to 2400),
the component it documents and whether it is left out of the sheet count or
of printing. This is format 1 of the drawing family; each later kind of
record adds one file in crates/doc/src/drawing/format/pending/.

The rules: one id counter for every object, raised above the largest id when
a file is read and never rewound; every reference is an id that must resolve,
or the file is damaged and refused, never patched; 1 to 1,000 sheets, at most
64 sources, names of 1 to 256 bytes, paths of 1 to 4,096 bytes without NUL.
The same drawing always saves as the same bytes.

Drawing::from_json reads at most 16 MiB of text, then runs the family's format
check, the number check, the upgrade, the typed record (unknown fields refused
everywhere) and Drawing::validate. The number check is now one function every
family calls, Family::check_numbers, told which field holds other tools' data;
a design's refusals read as before.

Nothing saves or opens a drawing yet: the container is the next step.

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

* Drawing ids: random and collision-free from format 1 (#3890)

A drawing's objects now take random ids, the form jyviko chose on #3890 from
#1017: no counter, so objects added on two branches never share an id and
never edit one line. The allocator is one module for drawings now and designs
later (#626 Phase 3), crates/doc/src/ids.rs: an IdSource gives nonzero u64s by
splitmix64 from a seed, each checked against the ids in use and drawn again
when it is one, or when it is 0. A run never draws a number twice, so at most
one draw per id in use comes before a free one, and a seeded run over a file
the same seed made still gets a new id. IdSource::seeded gives the same ids
for tests and replays; IdSource::from_entropy seeds a session from the
standard library's hash keys, the time and a count, with no new dependency.

In a file an id is 13 characters of Crockford base32, lowercase
(0000000000001 to fzzzzzzzzzzzz): ids::encode writes it and ids::decode reads
only that spelling, case-sensitive, so an id has one form to search for.
Anything else, 0 included, makes the drawing damaged. The drawing loses
next_id (a file that holds one is refused), and Drawing::new and allocate_id
take the IdSource. Messages name ids as the file writes them. The component a
sheet documents stays its design's own number until designs move over.

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

* Drawing record: a drawing too large to reopen isn't saved (#3890)

from_json reads at most MAX_DRAWING_BYTES (16 MiB), but to_json had no limit. Other tools'
properties are kept as they came, and indenting them can grow a drawing read in at about 4 MB
past 16 MiB (two million zeros in one array), so the next read refused what the save had just
written. 0xlillith found it in review of #3891.

to_json now returns a Result and refuses JSON longer than a read takes ("drawing too large to
save (at most 16 MiB)"), so every drawing saved opens again. Nothing writes drawing files yet, so
the error reaches the future file writer before it publishes anything.

Test: that case is refused, and a smaller compact drawing saves and reopens unchanged.

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

* Drawing files: a drawing saved as a container or as plain JSON, never read as a design (#3890)

K3 of the drawing storage plan (W11.2): the file a drawing is saved in. Nothing writes one from a
command yet; opening and saving come with K5.

- A drawing container is a design container holding `drawing.json` (Deflate, as `document.json`)
  in place of `document.json`, and `container.json` says so:
  {"content": "drawing", "format": "solidcraft-container/1"}. A design's `container.json` is as it
  was, with no `content`. The plain JSON form is the drawing's own text. Both give the same bytes
  every save, so a drawing can live in version control as text.
- `io::file_kind` tells a design from a drawing by `container.json` or by the JSON's format. The
  design reader refuses a drawing ("this file is a SolidCraft drawing, not a design: open it as a
  drawing") and the drawing reader a design; plain JSON only asks when it isn't a design, so
  reading a design costs nothing more. A container's content this build doesn't know, or a drawing
  container that also holds a design's `document.json`, is refused.
- `read_drawing` (bytes, either form), `read_drawing_file` (one seekable handle, the design
  reader's checks) and `write_drawing_container`; `DRAWING_EXT` (`scdwg`) and
  `is_drawing_path` beside the design's.
- The container writer's set-up and finish, the file opening and the container sniffing move into
  one function each, which designs and drawings both use. The container's own checks (budgets,
  entries, the ZIP directory) are the design container's, word for word.

Tests: a drawing round-trips through both forms byte for byte, and the container holds exactly
`container.json` and `drawing.json`; `file_kind` on both forms of both kinds; each reader refuses
the other's file; unknown content, a stray `document.json`, a missing drawing, an unknown
container version and a damaged drawing refused; both forms from disk, and a directory refused;
mutation fuzz of both forms (an error or a drawing, never a panic).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Paper scene: SVG of a sheet, its lines, arcs, cubics and fills (#4890 A3a) (#7032)

The first export of a paper scene, as SVG first (jyviko and 0xlillith on #4890), from the research
in the plan's §16. A3b adds ellipses, hyperbolas and Béziers above degree 3, which this refuses as
"not supported yet".

- `geom::paper::Prepared`: writers read a scene through `Scene::prepare`, which checks it first, so
  none reads one unchecked. Clipping goes there too, once (A8).
- `geom::paper::arc_pieces`: an arc's ends in pieces of at most a quarter turn, the last its own end.
  A half turn drawn from its ends is ill-conditioned: rounded to the nanometre (six decimals of a millimetre) its
  centre moves up to 1e-2 mm, where a quarter turn's stays under 1e-6 mm.
- `io::paper::paper_svg`: one user unit a millimetre, the size in mm on the root; y flipped by
  writing the height less y, so the file's numbers are page coordinates and an anticlockwise arc
  has sweep flag 0; lines `L`, arcs `A` (never large-arc), cubics `C`, parabolas `Q`; fills
  black and even-odd; one path per item; groups as `data-object` and `data-kind` (object ids can't
  be XML ids). No background, so cutters see only the marks.
- Numbers are whole nanometres (six decimals of a millimetre): no exponent, no "-0", the same bytes every time. They are the
  writer's own state too, so a joint is written once and a closed path ends exactly on its first
  point. Numbers are written straight onto the document, with no text made per number. A width
  under half a nanometre is written as one, and a piece too short for the grid is merged into the
  next, both counted in the report; a path's last piece has no next, so it is kept however short
  (unless it has no length). Output over 64 MiB is refused.

Tests: the A3 drawing border's whole document; quarter, 270°, clockwise and whole-turn arcs, and a
circle of two halves giving the same four quarters; the quarter-turn boundary both sides; cubics and
parabolas, and the conics refused; the number format; a closed path whose start and end round
apart; a sliver merged; an open path's short last line and arc kept, and one of no length dropped; read back through our own SVG reader (lines exactly, the arc as an arc);
the scene's JSON mutated into the writer (never a panic).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Drawing W17a: view definitions as data (#4816) (#4817)

* claim: #4816

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

* Drawing views as data: named views, scales and the map onto paper (#4816, W17a P1)

What a drawing view is, before anything draws it (geom::drawing_view):
- NamedView: the six principal views and the isometric views from the four upper corners, each
  an exact basis (a Plane whose normal points back at the viewer), never taken from a camera,
  whose pitch clamp would tilt a top view, with no -0 to write out; `iso` reads as the
  front-right one.
- basis_looking: any other view from its direction, an up and an origin (paper x = direction x up,
  paper y = x x direction), refusing a zero direction, an up along it, or an origin that isn't
  finite or within 1e12 mm. W07's
  {direction, up, origin} view reads through it.
- Scale: paper : model as two positive numbers, read and written as "1:2", from 1:100000 to
  1000:1; Scale::fit picks the largest ISO 5455 preset (50:1 to 1:10000) at which a view fits a
  space (W07's "fits the page" default), compared as size x paper <= room x model so an exact
  fit at a reduction isn't lost to rounding.
- ViewFrame: a view's basis, scale and place on paper, the point of its plane at `centre`
  landing at `position_mm`. Its paper map moves the view's curves and their accuracy onto
  paper (paper error = scale x model error); `boundary` maps a box of its plane onto paper from
  its four corners.

In geom, where Seg2 lives:
- Similarity2 and Seg2::similar (new): scale, turn and shift a segment exactly, an arc staying an
  arc. Similarity2::new refuses a scale that isn't above 0 and works the turn out once, not per
  point;
- Seg2::map_controls: a segment's control points mapped by any function. It was doc's
  sheet::map_controls; its four callers in doc use the method now, unchanged.

CurveAccuracy::scaled gives a curve's accuracy at another scale.

Tests: each named view's basis and viewer side, and the same bases from a direction and up; a 50 mm cube in every principal view at 1:1 and
1:2, and in isometric (edges 50 x sqrt(2/3), outline 50 x sqrt(2)); the view's centre on its
position (W07's 100 x 20 front view on A3 at 2:1); scales read, written and refused; the fitted
scale, an exact fit at 1:5 and ISO's smallest; no -0 in any named view's axes; an origin refused by
its own message; hostile bases, centres and positions; every segment kind moved by a similarity,
and the similarities refused; scaled accuracy.

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

* Drawing views: the bottom view pinned, square near parallel, bounded scales and boxes (#4816)

From DevonHope's review of #4817:
- The tests pin all six principal views exactly. The direction-and-up test uses literal ups,
  so a bottom view turned half round now fails it.
- basis_looking squares x to the direction before normalising. Near the 1e-9 threshold the
  normal was up to 8.8e-9 rad off; now it is about 1e-16.
- Scale::fit fits nothing for a size or room that is negative or not finite. A size of 0 still
  sets no limit that way.
- Each side of a scale is 1e-6 to 1e6, so a scale writes at most 49 characters.
- ViewFrame::boundary returns a Result. It refuses a box that is inside out or not finite on
  paper, instead of dropping a corner.
- paper_map, boundary and seg_to_paper say which coordinates they take.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Drawing W18 S2: title blocks as data (#3875) (#3877)

* Title blocks: what a cell shows and where its text goes (#3875)

geom::cell holds a title-block cell's content: parts of text and fields
(component, drawing, sheet, revision or prompt, by key, with an optional
unit and decimals kept as written for the resolver to check), at most 64
parts and 4096 bytes of text, with no control character but a line break.
Beside it are a resolved value (live, overridden and drifted, or missing,
with "-" for each part that couldn't be read and why), an override with
the value it replaced, and the projection a drawing's views use (first or
third angle), which the title block's symbol will read.

A fit warns and never cuts: a count of characters, not bytes, with its own
count for text in Chinese, Japanese, Korean or Yi characters.

The cell text rule places a label and a value: the label's baseline 1 mm
plus its cap height below the cell's top, the value box under it inset by
1 mm, one line centred in the box, and wrapped or several lines down from
its top at the font's pitch, with a warning when the last falls below the
box. Wrapping by width, width warnings and glyphs a font lacks need the
text measured, so they wait for the paper scene's text.

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

* Title blocks: definitions and their place on a sheet (#3875)

geom::title_block holds a title block's definition: its size (1 to 1000
mm a side), its outline and cell line widths, and up to 64 cells. Each cell
has a snake-case name of its own, a rectangle measured from the block's
lower-right corner, an optional one-line label with its cap height, and a
value: text from cell content, with its cap height, alignment and fit, or
the projection symbol, which reads the drawing's projection and is never
fixed in the block.

validate() checks a definition on its own, as when one is read: cells
inside the block and not overlapping (touching is fine), label and value
cap heights that fit, and the content's own limits. layout() puts the
block on the drawing border's lower-right corner of a sheet, refusing one
that doesn't fit, and gives each cell's rectangle, its label's place and
its value box by the cell text rule. On A3 landscape a 180 x 54 mm block
runs from (230, 10) to (410, 64); it fits A5 landscape and every larger
sheet, but not A5 upright.

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

* Cell text: a value box lies within the paper's coordinates (#3875)

place_lines took any finite, ordered value box, but centring one far out (min x 1e308, max x
1.1e308) overflows: the anchor came back infinite, with no warning. 0xlillith found it in review
of #3877.

The paper scene already bounds every coordinate it holds by MAX_COORDINATE_MM (twenty times
the longest sheet side). That limit is now public, and place_lines keeps its box inside it, so
its anchor is always finite: one rule for what a paper coordinate may be.

Tests: the hostile-box test refuses both overflow cases from the review and a box just past the
limit on each side, and places lines in a box the size of the whole paper limit. The fuzz test's
numbers now include values past the limit (1.1e5, ±1e308).

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

* Title blocks: padding from the lines' inner edges, one label rule, nothing unread (#3875)

A cell's text is padded from the inner edge of its lines: each side of a cell moves in by half
the line drawn on it (the outline's on the block's edges, a cell line's inside), so a wide
outline never covers the text. Before, the padding ran from the line's centre.

The label's one-line rule lives with the cell text rules (`cell::check_label`), where parts
lists will find it. The cell types nothing read yet (`Override`, `Resolved`, `State`,
`Problem`, `Reason` and `MISSING`) wait for the resolver that will read them.

The tests check the exact thresholds inside the lines (a label that leaves no room, a value
that just fits), pick each of the lines, alignment and wrapping on their own so every
combination comes up, and lay fuzzed blocks out on every standard sheet, either way, with and
without the filing margin.

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

* Title blocks: the drawing border's line, descenders, room where the block sits (#3875)

From DevonHope's review of #3877:
- Each cell's lines are clamped to the inner edge of the drawing border's line. That line runs
  along the block's right and bottom edges, and along any other edge it reaches, as when the
  block fills the border. Aabb2::intersection, the 2D twin of Aabb3's, does the clamping.
- layout checks each cell's room again where the block sits (place_cells, which validate runs
  at the origin), so rounding at the real corner can't turn a cell inside out.
- The cell text rule counts descenders, at 3/10 of the cap height (ISO 3098's lettering B).
  They may reach into the padding under a value box, never past it to a line. place_lines
  warns Height when they would, and validate asks it whether one line fits.
- The module doc no longer links to a private function.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Drawing W06a: exact orthographic hidden lines for the analytic subset (#2455) (#2457)

* Drawing W06a: exact orthographic hidden lines for the analytic subset (#2455)

crates/geom/src/hlr (L0): prepare once per body, then hidden_lines for a
scene of placed bodies. Edges and face silhouettes are projected from their
exact curves, split where projections cross, and each piece is classified by
rays toward the viewer against the trimmed faces: visible, hidden or
explicitly uncertain, never guessed. Planes, cylinders, cones, spheres and
tori are supported whether stored as analytic records, revolutions or spline
patches; freeform faces are reported and make what they could hide
uncertain. Screen geometry is Seg2 with the W01a accuracy vocabulary: exact
lines and conic arcs where the projection is exact, bounded chords
otherwise. docs/drawing-hlr.md holds the contract and its limits.

* geom/hlr: handle the brep records main gained in #3093

Main's exact model storage (#3093) added Curve::SurfaceIntersection,
Surface::PlaneThrough and Surface::PlacedRevolution, and the HLR matches on
the records no longer compiled. A plane through three points is a plane: it
is trimmed and intersected as one, and a box stored that way draws the same
bits as one with axis-stored planes. The procedural intersection curve and
the placed revolution (whose affine placement may scale it unevenly) are
refused by prepare as not supported yet, until the kernel adapter handles
them. The records' positions move with the body's centre like the others.

* geom/hlr: pad plane faces' boxes, so a plane record off by rounding still hides

Plane faces took the box of their boundary chords unpadded, flat along the
normal. When the plane record and its edges disagree by rounding (a unit cube's
top plane one ulp above its edges, a 100 mm cube's 1e-11), the ray toward the
viewer reached the plane just outside that box, the slab test culled the hit,
and the back edges came out Visible with no doubt. Every face's box is now
padded by 1e-6 of its size, as curved faces' boxes already were. Reported by
DevonHope on #2457.

* geom/hlr: limits from review: trim samples, scene extent, crossing pairs, surface groups

From DevonHope's review of #2457:
- Options::trim_samples must be 1 to MAX_TRIM_SAMPLES (4096).
- Placed bodies must stay within MAX_COORDINATE (1e12 mm, as section and brep
  bound stored numbers). Placements 1e200 apart made the scene's size infinite
  and every tolerance taken from it with it, so everything came out Visible.
- The crossing search counts its candidate pairs before it starts and meets
  each pair only in the first grid cell the two segments share, instead of
  keeping a set of the pairs met, which could outgrow wasm's 4 GB before the
  500 million pair budget tripped. The same pairs in the same order.
- prepare groups faces on one quadric or torus by comparing each face with
  the faces of its kind whose size agrees to the tolerance (sorted), not with
  every face; at most 50 million comparisons. prepare_cancellable polls a
  cancel callback between edges, faces and comparisons.

* geom/hlr: boundary chords on a grid, and no fine pass over coincident curved chords

The cost items of DevonHope's review of #2457:
- Every ray hit asked how far it lies from its face's boundary by scanning all
  of the face's chords (4096 per circle; a plate's face with 256 holes has
  about a million). The chords are now bucketed on a uniform grid over the
  boundary's box, cells at least as long as the longest chord, and the test
  looks only at the cells around the point. It finds exactly the chords the
  scan finds.
- Chords of two curved curves that lie on each other end to end (aligned holes
  seen along their axes) were cut into 16 by 16 pieces and intersected, to
  find crossings that are only noise within the curves' tolerance. They are
  now skipped; visibility changes where a coincident run starts or ends, and
  chords that only partly overlap still find those.
- Fixtures: a plate with holes, coaxial cylinders seen along their axis, and
  300 placements.

* geom/hlr: pad boxes by the body's coordinates too; coincident chords only to rounding

Review of the previous commits found two holes:
- The padding grew with the face's size, but rounding grows with where the body
  was modelled: a 0.001 mm cube 1e7 mm out, its top plane one step (1.9e-9 mm)
  above its edges, still had its hit culled. The pad now adds 1e-12 of the
  body's offset.
- Two chords were taken for coincident when their ends lay within each other's
  sag, which in a coarse scene can hide real crossings: an arc can bulge across
  a line 1 mm off its chord twice. Only chords that coincide to rounding are
  skipped now; identical projections, the costly case, still are.

* geom/hlr: coincident curves meeting at shared sample points do not cross

Timing the review's scenes showed that skipping coincident chords was not
enough: chords of two coincident curves also touch end to end at every sample
point they share, and each touch was a crossing to refine and split at. 64
coaxial cylinders seen along their axis gave 8.3 million crossings (19 s), 64
cylinders one behind another along the line of sight 4.2 million (214 s;
before these commits the crossing budget refused it). A touch at a shared
sample point is now dropped when the two curves also coincide, to rounding, on
both sides of it. Where a coincident run starts or ends one side differs, and
the touch is kept.

* geom/hlr: the own-face margin allows for rounding where a body was modelled

The far 0.001 mm cube of the previous commit showed a second effect of records
rounded at large coordinates: its top plane, one ulp (1.9e-9 mm) above its
edges, hid the top edges themselves, because a ray leaving an edge met its own
face 3.2e-9 mm along, past the own-face margin of 1e-6 of the face (1.4e-9).
The margin now also covers 1e-14 of the body's offset (about 45 ulps of its
coordinates), which only matters for parts far smaller than their distance
from the origin.

* geom/hlr: no parity test for a hit on the boundary, where it is uncertain anyway

* docs/drawing-hlr: the fixtures of the review's second round

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>

---------

Co-authored-by: CamWight <91156642+CamWight@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Benny1496 <163034806+Benny1496@users.noreply.github.com>
Co-authored-by: Mateusz Sych <151755914+SychPL@users.noreply.github.com>
Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-10-28 13:31:16 +00:00
Tarik Sijerčić f060702676 i18n: add Bosnian, Croatian and Serbian interface languages (#7813)
* i18n: add Bosnian (bs), Croatian (hr) and Serbian (sr) catalogs

Full translations of all English messages, the pending ones included,
kept in step with one shared terminology:

- Bosnian (Bosanski): ijekavian, Latin script.
- Croatian (Hrvatski): ijekavian, Latin script, Croatian standard terms
  (točka, kut, brid, ploha, značajka, spremi, izbornik, sučelje).
- Serbian (Српски): ekavian, Cyrillic script; brand and file names,
  acronyms, units, key names and placeholders stay in Latin.

The Browser panel is "Stablo modela" so it is never confused with a web
browser, and the History panel is the timeline ("Vremenska traka"), not
the undo history, per the glossary.

Registers the three catalogs, adds them to the new-language test, a
pending attribution file, and a terminology note in docs/localization.md.

* i18n(bs, hr, sr): translate the 195 messages added on main since the catalogs were made

Covers the navigation bar customisation, the render stage, the new
command help texts and the Assistant start-up preferences, so the three
catalogs again carry every English message.
2026-10-28 12:38:27 +00:00
Mateusz SychandMateuszSychXpl e181015fd1 MCP save: a folder the agent opened or saved a design in stays in scope (#7181) (#7216)
* claim: #7181

* MCP save: a folder the agent opened or saved a design in stays in scope (#7181)

The agent's default folders held the open design's folder only while that
design stayed open there, so after a new design, an opened STEP file or a
save-as elsewhere a save beside the earlier design was refused, while the
same save through the control port worked.

The MCP server now keeps, for the rest of its run, the folder of every
design file a call opened or saved (open, save, and doc.open, file.save,
file.save_as through execute or batch, path given in full) and, headless,
of every design open in its session. Such a call only succeeded because the
folder was already in scope, so naming a path adds nothing. The folders get
the open design folder's terms (inside home or temp, not hidden, hidden
entries unreachable), at most 16. Bridged, they go to the app as
design_folders, which it judges the same way.

The save tool description and docs/mcp.md say how to add a folder with
--allow-path.

* MCP scope: a design's folder is the folder its file resolves to (#7181)

A link in another folder to a design inside the agent's folders passes the
scope check, which follows it, but its own folder was what got remembered
(and, for the open design, what counted while it was open). Take the folder
of the file as the check resolves it.

* Control test: a hidden file the export format accepts (#7181)

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-10-28 12:22:20 +00:00
1dba7f7f04 Agent chat: OpenCode Go preset with per-model endpoints (#6111)
* agent chat: OpenCode Go preset with per-model endpoints

One preset (https://opencode.ai/zen/go/v1) covers every Go model: each
model is routed to its endpoint family (chat completions, responses,
messages, Gemini), always with the x-opencode-session header Go
requires. Unknown models fall through protocols when Go names the
mismatch; the conversation and tools translate per protocol.

* agent chat: keep Go reasoning metadata, route all listed models (#5656)

* fix agent chat Go endpoints to documented inference routes

* style: collapse long assert in Go routing test (cargo fmt)

* refactor: share Go tool/args helpers, table-driven protocol routing (no behavior change)

* refactor: share GoCallAccum across Go reply parsers (no behavior change)

* refactor: share part/call extractors across Go history converters (no behavior change)

* refactor: share post_to envelope between post and go_post (no behavior change)

* refactor: pass system note into go_responses_input, drop history clone (no behavior change)

* refactor: lazy Gemini name map, shared go_tools, named tool-call cap (no behavior change)

* refactor: share shaped-tool wrapper across Go tool converters (no behavior change)

* fix: set request_timeout_s in Go test setups for rebased main (no behavior change)

* fix: expect ToolsAvailable first in tool-loop tests (matches new event order)

* simplify: reuse tool_arguments, drop restater comments (no behavior change)

---------

Co-authored-by: Julius <julius@localhost>
Co-authored-by: Jom_g <jom_g@users.noreply.github.com>
Co-authored-by: HoloPort <holoport@localhost>
2026-10-28 12:20:19 +00:00
austin62jakeandClaude Opus 5.5 1e3cb3e36e Motion interference: a check cut short is not clear, and budget_s gives it time (#7855) (#7934)
* claim: #7855

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

* Motion interference: a check cut short by its work budget is not clear (#7855)

motion.interference stops when its work budget runs out (truncated) but
answered clear: collisions.is_empty(), so a check that reached only the first
steps read as a pass: a gear pair checked for 14 of 61 samples was "clear".
clear is now true only when every sample was checked and none collide.

The budget is an argument of interference_within, so a test can cut a check
short: B slides up to touching A, a full check is clear, and the least work
that checks a first step stops after 1 of 16 samples with clear: false (it
was clear: true before). The guide and the mechanisms skill say the same.

The issue's second request, a budget_s on motion.interference, is not here.

Refs #7855.

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

* Motion interference: budget_s gives a check the time it needs (#7855)

The work budget is fixed, so a gear pair drawn in detail was cut short
after a few samples, or refused before its first one. motion.interference
now takes budget_s (seconds, above 0, at most 86400, as inspect.interference
allows): the check then runs until that time instead of the work budget,
looked at before each sample, and echoes budget_s in its answer. Without
budget_s nothing changes. The refusal before a first step now names the
budget that ran out and suggests budget_s.

Tests: given a time, the smallest work budget (which checks no step) no
longer stops the check, through motion.interference too; budget_s 0, -1,
1e9 and "soon" are refused.

Fixes #7855.

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-10-28 12:19:10 +00:00
semmelrindeandClaude Opus 5.5 8f8df03358 Navigation: Maya and HFSS mouse presets (#6474, #2742) (#8184)
Maya moves the camera with Alt and any button (left orbits, middle pans,
right zooms), middle drag also pans. HFSS pans with Alt+left drag and zooms
with Shift+Alt+left; its Ctrl+left orbit is the drag from empty space every
preset already has, so a Ctrl+click on geometry still adds to the selection.
The help texts are in the catalog for every locale that has the other
presets' help.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:12:05 +00:00
github-actions[bot] 5bf5de49a5 Regenerate generated files and fold pending files [skip ci] 2026-10-28 12:35:35 +00:00
Nathan Dane b86c75544e Appearance: read the look chain and replace a look everywhere in one undo step (#6428) 2026-10-28 12:10:31 +00:00
raykholo db999b2a98 Control protocol: the ui.prefs settings as a sorted list, one per line (#7747) (#7988) 2026-10-28 12:07:43 +00:00
github-actions[bot] aca80f2054 Regenerate generated files and fold pending files [skip ci] 2026-10-28 10:25:10 +00:00
Nathan Dane 0b2efd60b9 Render browser: scene tree with Environment, Lights, Shots and Objects rows, resolved appearances and a Browser selection (#6427) 2026-10-28 09:58:57 +00:00
github-actions[bot] ae07e36a7d Regenerate generated files and fold pending files [skip ci] 2026-10-28 07:57:59 +00:00
Nathan Dane 18fe82ab26 Render workspace: focus view, compact docks, a Browser chip at narrow widths and a localisation pass (#7376) 2026-10-28 07:29:53 +00:00
github-actions[bot] 372badfc1b Regenerate generated files and fold pending files [skip ci] 2026-10-28 06:43:04 +00:00
Nathan Dane b0c8c466b0 Navigation bar: icons for visual styles, selection modes and display (#6585) (#6898)
* Navigation bar: slot model and one-row renderer (#6585)

* Navigation bar: customisation section in Preferences (#6585)

* Navigation bar: icons for visual styles, selection modes and display (#6585)

* Navigation bar: redraw the Bodies, Parts and Components selection icons (#6585)

* Navigation bar: refine the Ground shadow, Ambient occlusion, Parts and Components icons (#6585)

* Navigation bar: solid cube and a longer arc for the Ground shadow icon (#6585)

* Navigation bar: the owner accepts the new icons as final (#6585)
2026-10-28 04:16:44 +00:00
github-actions[bot] 85aaed2ccb Regenerate generated files and fold pending files [skip ci] 2026-10-28 03:48:50 +00:00
Nathan Dane 110efa3598 Render: allow picking in the product view with a Body, Face or Component filter and outline the selection in the product pass only (#6426) 2026-10-28 03:20:43 +00:00
P'tite TêteandP'tite Tête 9fb41f8378 feat: Start a new selection's preview without waiting for the obsolete one (#7167) (#7261)
- Leave the running preview behind when the selection moves to another setup, asked to stop
- Keep at most two obsolete previews running; streaming, tools and other inputs wait as before
- Keep no result of a cancelled watched evaluation in the shared evaluation cache
- Test the detaching, the cancel reaching the spawned job, and the cache guard

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-10-28 01:32:17 +00:00
CamWightandClaude Opus 5.5 303fddb20e Browser: Bodies folders only where they help: Always, Several bodies only (the default) or Never (#4217) (#4219)
* claim: #4217

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

* Browser: Bodies folders only where they help: Always, Several bodies only (the default) or Never (#4217)

A new preference on the Interface page, in the Layout section under Browser, sets when a
component's bodies sit in a Bodies folder of their own. Several bodies only lists a component's
lone body, when it has no groups, straight under its placement. Never lists every component's
bodies and groups there. Always keeps the folder as before. The design's own Bodies folder and a
folder hidden with its eye always stay.

One rule, browser::bodies_folded, decides it for the rows and for the menu. A folded folder
draws no row and stays open; its items move up one level, and their own eyes, hover and drops do
its jobs. While a folder is folded, its placement's menu takes the folder's Create Components from
Bodies and Group Selected; New Group and Show All were already there.

ui.prefs takes browser_bodies_folders (always, several, never) and refuses other values; saved
preferences read an unknown value as the default. The s37 scenario sets Always so that it still
checks the Bodies folder's chevron.

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

* Browser: Bodies folders in Arabic, Hebrew and Vietnamese too (#4217)

Arabic, Hebrew, Japanese, Vietnamese and Chinese are complete catalogues, so
every_complete_catalog_carries_every_english_message fails a new message any of them lacks. The
preference's four messages get their Arabic, Hebrew and Vietnamese text, in the words those
catalogues already use for a body and a folder.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:24:17 +00:00
taylorhayis c4fb42f665 Preferences: search settings (#2218) (#6002)
* claim: #2218

* Preferences: search settings across every page (#2218)

A field at the top of Preferences filters the live controls with the same scorer as command search, including a dropdown's current value and the English source of a translated label.

* Preferences search: keep the plain page list for its tests (#2218)

The production dialog always draws the search strip, so the no-header entry is test-only and no longer warns as unused.

* Preferences search: show only the settings that match (#2218)

A page name or a loose string of letters was keeping the rest of that page on screen, including notes and the mouse legend.

* Preferences search: drop stray chrome and clear from the field (#2218)

The colour swatches and folded Advanced heading were painted on every query, and the field had no way to empty itself.

* Preferences search: fold shortcut matches and drop the blank gaps (#2218)

* Preferences search: stop unmatched pages painting over the hit (#2218)

* Preferences search: a page tab clears the search and shows that page (#2218)

* Preferences search: keep OK and the graphics checkboxes in view (#2218)

A dropdown that skipped its real height on a dialog's first frame left OK outside the window, and the search strip used up the rows the graphics page needs. The shortcut fold is in the UI kit gallery.

* Preferences search: type a query and open the match from the keyboard (#2218)

A printable key or Cmd/Ctrl+F focuses Search settings, arrows move through the matches, and Enter opens that row. Escape clears the query before it can close the dialog, and clearing returns to the page that was up when the search started.

* Preferences search: translate the search strings for the complete catalogs (#2218)

Arabic, Hebrew and Vietnamese are delivered complete, so Search settings and the empty result need a translation there. German and Korean get the same two strings.
2026-10-27 23:27:33 +00:00
bherbruckandClaude Opus 5.5 03b8f4138b New designs save as .sc; .solidcraft files still open (#7325)
* claim: new designs save as .sc

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

* New designs save as .sc; .solidcraft files still open and keep their name on Save

DESIGN_EXT is now "sc"; DESIGN_EXTS lists sc, solidcraft and solvecraft and
every open path (dialogs, drag-drop, recent files, CLI, MCP, links, the web
picker) accepts all three. Save of an opened .solidcraft file writes that
file; Save As, autosave, recovery and crash reports use .sc. Windows, macOS
and Linux register .sc alongside the older extensions.

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

* Update UI tests for the .sc default extension

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:41:40 +00:00
yhamil04andClaude Opus 5.5 c0a0da225a Ribbon tooltips: help for the Solid > Modify tools, and new toolbar commands must have it (#2758) (#7134)
* Ribbon tooltips: help for the Solid > Modify tools; new toolbar commands need it (#2758)

Writes the two-sentence help for the 16 Modify tools that had none (en, fr,
ja, zh), and adds a test that fails when a toolbar command has no help; the
190 older ones are on a backlog that only shrinks. AGENTS.md, CONTRIBUTING.md
and the Adding a command guide say so.

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

* Ribbon tooltips: the help test reads the ribbon as the toolbar lays it out (#2758)

Review: commands placed by a workspace section without .at(...) were not
checked; 79 of them join the backlog. Physical Material's help no longer
promises the centre of mass.

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

* Ribbon tooltips: French help uses the glossary terms (#2758)

Matériau, Apparence and Scinder, as rlartot asked in review; Delete Face
says the hole stays open.

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

* Ribbon tooltips: the help rule fits the commands skill's size limit (#2758)

AGENTS.md became a router to skills on main, so the rule lives in
skills/solidcraft-commands/SKILL.md, kept under its 3600-character limit.

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

* Ribbon tooltips: Arabic, Hebrew and Vietnamese help; the backlog follows main (#2758)

Arabic, Hebrew and Vietnamese are complete catalogs now, so the 16 texts
come in those too. Twelve toolbar commands that landed before the rule
join the backlog; the Render commands, which left the ribbon, leave it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:30:15 +00:00
Lars Jansen 83ac98ac76 Origin plane names: Top / Front / Right (#6491) (#6496)
* claim: #6491

* feat(ui): origin plane names Top / Front / Right (#6491)
2026-10-27 21:06:14 +00:00
42ec27cfe4 Browser with History under it: drag the split between the two cards (#7371) (#7803)
* claim: #7371

* Browser with History under it: drag the split between the two cards (#7371)

Under the browser, History took exactly half the column. The seam
between the two expanded cards is now a grip with the vertical resize
cursor; dragging it moves the split, as the column's right edge moves
its width. The split is kept as the browser's share of the column in
a new preference, browser_share, whose default is the equal split, so
nothing changes until someone drags.

Either card keeps at least its header and two rows. Folding either card
works as before and gives the other the rest; while one is folded or
folding there is no grip. A non-finite share falls back to one half.

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

* Prefs: keep the browser share with the History-under-browser settings

Moves the entry so it no longer sits next to the render and dialog
preferences that main added after browser_width.

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

---------

Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:59:38 +00:00
bherbruckandClaude Opus 5.5 7a6c884418 Change the app id to io.tangencylabs.solidcraft, with aliases for existing installs (#7681)
The app id moves from ai.storyteller.solidcraft to io.tangencylabs.solidcraft: macOS bundle id
(the CLI signs as io.tangencylabs.solidcraft.cli), Linux desktop/metainfo/MIME file names, icon
name, Wayland app_id / X11 class, Flatpak, Snap common-id, nfpm/FreeBSD/Nix/AppImage staging,
the web app's icon paths and the docs.

Existing installs keep working: the Flatpak repository marks both old ids
(ai.storyteller.solidcraft and ai.storyteller.solvecraft) end-of-life with a rebase straight to
the new id, re-marking the solvecraft ref that pointed at the 0.8 id; the manifest grants
read-only access to both old ~/.var/app folders and the first launch copies settings and
autosaves from the newest one; AppStream <replaces>/<provides> and X-Flatpak-RenamedFrom list
both; the AppImage removes menu entries it wrote under either old id. The data and settings
folders (solidcraft / SolidCraft) are unchanged. The MSI UpgradeCode and ProgIDs are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:16:40 -04:00
raykholo d2f0627421 Follow a theme file and reload it when it changes (#7573) (#7574)
* claim: #7573

* Follow a theme file and reload it when it changes (#7573)

* Translate the follow-a-theme-file strings
2026-10-27 20:08:12 +00:00
98abb249c4 Cylinder: place by distances from reference edges, like Hole (#6380) (#6381)
* claim: #6380

* Cylinder: place by distances from reference edges, like Hole (#6380)

A Cylinder standing on a flat face can take up to two straight edges of
that face as references, each with the distance of its base centre from
the edge's line, measured in the face's plane, as a Single hole does
(#37). `solid.cylinder` takes them as `references: [{edge, distance}]`
with a numeric `base`; the edges are found again by their persistent
names, else their points, so the cylinder follows them when the part
changes, and each distance is a parameter (an input of Inspect Feature).
A cylinder without references stays at its base, as before.

In the Cylinder window, started on a picked face, Reference 1 and 2 take
the edges, each with its Distance measured from the pick, so it works
with the mouse alone; the canvas shows each distance with a box to type
it in, and Edit Feature picks the edges again.

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

* Cylinder: Edit Feature shows each reference edge input once (#6380)

A face selected when Edit Feature opened the window had already added
the reference inputs; the edit added them a second time.

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

* Cylinder references: the measured-once cost test's cylinder has none (#6380)

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

* Cylinder: pick the face it stands on in its window, then the edges (#6380)

The Cylinder window took its place only from a face selected before it
opened, so started first it stood at the origin and offered no reference
edges. It now has a Face input before Reference 1 and 2, as the Hole
window does: start Cylinder, click the face, click the edges, OK.

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

* Glossary: reference edge, and "Reference" among the words with more than one meaning (#6380)

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

* Cylinder edge references: the Cone dialog (#6250) gets the references field too

The Cone primitive landed on main after this branch began; its dialog initialisers lacked
the `refs` field the Primitive kind gained here. The dense form check for Cylinder fills only
its fields: its face and reference edges place it, which the reference tests cover.

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

* Cylinder: declare references in its contract; reference-placed cylinders are not placement-independent

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-27 19:28:22 +00:00
7b03439dda Navigation: Free orbit can turn around the vertical axis (#6964) (#6965)
* Navigation: Free orbit can turn around the vertical axis (#6964)

A new option under Free orbit, Turn around the vertical axis, makes mouse
drags a turntable without the pole limit: sideways turns about the world's
up axis (reversed upside down so the model follows the cursor), up/down
tilts over the top. Off by default; free orbit without it is unchanged.

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

* i18n: Japanese and Chinese for the vertical-axis orbit option (#6964)

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

* i18n: Arabic, Hebrew, Vietnamese, German and Korean for the vertical-axis orbit option (#6964)

Main now treats ar, he and vi as complete catalogs, so the merge queue's
every_complete_catalog_carries_every_english_message failed on the two new ids.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: danieldimitrov86 <danieldimitrovdev@gmail.com>
2026-10-27 19:28:14 +00:00
github-actions[bot] 882737ff94 Regenerate generated files and fold pending files [skip ci] 2026-10-27 19:52:35 +00:00
Nathan Dane 295d04932f Render workspace: stage bar, aligned floating docks and the Shots strip host (#6281) (#6296)
* claim: #6281

* Render workspace: stage bar, aligned floating docks and the Shots strip host
2026-10-27 19:21:09 +00:00
Erlis VidalandClaude Opus 5.5 c8c5c882de Sketch: name = value in a drawing tool's value box defines the parameter (#7567) (#7660)
* claim: #7567

* Sketch: a drawing tool's value box takes `name = value` (#7567)

While drawing, a value box (circle diameter, line length, rectangle width and
height, slot, arc, polygon) now accepts `name = expression` and
`#name = expression`, as the Dimension tool already does: the shape is drawn at
the expression's size and its dimension defines and uses the user parameter
`name`, in one undo step.

- sketch.draw.dimensioned checks a definition's right-hand side, read as the
  new parameter will hold it (a bare number in the design's unit), and passes
  the text on to sketch.dimension; the reply lists `defined`.
- The value boxes size the preview and validate by the right-hand side, refuse
  a definition in a box that is not a dimension (polygon sides, rounded
  rectangle corner radius, conic rho), and the status line names the
  parameters defined.
- Guides: parameters and sketching.

Fixes #7567

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:02:09 +00:00
Lars Jansen 5a477b7774 Dialogs: open in the left column in place of the browser and History (#6707) (#6712)
* claim: #6707

* feat(dialogs): open in the left column in place of the browser and History (#6707)

* fix(i18n): Vietnamese messages for the dialog placement option (#6707)
2026-10-27 17:59:00 +00:00