* 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>
Collapse All from a component or placement row folds everything under that
row and leaves the row itself open, matching the whole-tree Collapse All,
which already keeps the design row open.
* Standard Parts and Contact Set: label the rows in words, not with raw keys
The Insert Part dialog listed a size's dimensions under the library's
keys (d, head height, socket depth), and the Contact Set dialog's
checkbox read "enabled". The dimensions now take messages (Nominal
diameter, Head height, Socket depth, ...) and the checkbox reads
Enabled, translated like the rest of the dialogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contact Set: translate the Enabled checkbox's label
ui_kit::checkbox paints the text it is given; the callers translate it,
as Configuration Rules' Enabled does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Interface page's strip of tab accent colours looked up its own
word-cased names ("Sheet Metal", "Surface"), which the catalogs
mostly don't carry or carry with another meaning: in German it read
"Sheet Metal" and "Flächenkörper" where the ribbon says Blech and
Fläche. It now names each tab the way the ribbon does, through the
tab's heading message, and the Form tab gets one.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Preferences: the snap-suppress choice and Direct interaction in the user's language
The Hold to stop snapping choice joined its keys with an English "or"
("Cmd or Option", the default) in every language, and Navigation's
Direct interaction setting and its note had no message. The choice now
reads through a message with the keys as placeholders, and the setting
gets its two messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the new test away from where another open PR adds one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: 54 labels that read English in every language get messages
The construction dialogs' selection fields (First edge, Second point,
Through point, ...), the joint types (Rigid, Revolute, Slider, ...), the
Sheet Metal and Plastic rules' rows (K Factor, Flange Gap, Min Draft,
...) and a few options and prompts are passed to the label translator as
English source text that no catalog carried, so they showed English in
every language. They are added in one pending file, with the eleven
usual translations, and a test makes sure every dialog's selection
fields keep a message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialog labels: German Flange Gap is Flanschspalt, as the catalog's Flansch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Italian catalog lacked 1350 of the English messages. This batch
translates the 719 outside the Render workspace: Preferences, the
Assistant, the help texts, command labels, materials, navigation and
the rest. It uses the catalog's own terms (Schizzo, Lavorazione, Corpo,
Giunto, Cronologia, Struttura, Risalto, Appoggio, Aggancio a scatto).
The Render workspace's 600 messages are left for a second batch while
that workspace is still being built.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Part: name the part families in the user's language
The Type list of Insert Part and the Change Part Size dialog showed the
library's English family names (Socket head cap screw, Hex nut, ...) in
every language. Each family now has a message under its library id,
and a family the catalogs don't know keeps the library's name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Part: the Name field's automatic hint in the user's language
The hint was the English word "automatic"; it now takes the catalogs'
parts.automatic_name, which every language already translates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the family-name helper and its test away from where #8005 adds its own
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Turkish: translate the 113 messages the catalog was missing
The Turkish catalog lacked 113 of the English messages: the newer
Preferences settings, the hole presets, the slicer messages, the
Assistant's start-up options and tool summaries, and the Render
workspace's stage, shots and inspector labels. They are translated in
one pending file, with the catalog's own terms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Translate the 87 messages main added since (the navigation bar's)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: translate the texts a few dialogs draw themselves
The joints' Play/Stop buttons, the Motion Study's Loop and the BOM's
sub-parts checkboxes, the Stitch tolerance hint, the Flange type and the
Emboss wrap note were drawn as English literals; the joint lists named
each joint's type by its Rust name (PinSlot, PointOnPlane). They now take
messages, and the joint lists use the Type box's label, translated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the new test away from where another open PR adds one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Surface, Sheet Metal, Plastic, Frame, Move, Align, Assembly, Cam,
Coupling, Sweep, Part Size and sketch editing dialogs refuse their
inputs with English sentences ("select bodies first", "pick faces of
one body"). The dialog translates a refusal through the catalogs as it
shows it, but no catalog carried these 96, so they showed English in
every language. They are added in one pending file with the eleven
usual translations.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Everything outside the Render workspace, which is still being built:
Preferences and the new navigation bar, the Assistant, the help texts,
command labels, materials, hole presets, the slicer messages and the
remaining dialog labels, with the catalog's own terms.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Everything outside the Render workspace, which is still being built:
Preferences and the new navigation bar, the Assistant, the help texts,
command labels, materials, hole presets, the slicer messages and the
remaining dialog labels, with the catalog's own terms.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Everything outside the Render workspace, which is still being built:
Preferences and the new navigation bar, the Assistant, the help texts,
command labels, materials, hole presets, the slicer messages and the
remaining dialog labels, with the catalog's own terms.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: the status in the view and the browser's editing mark in the user's language
While a sketch is edited, the view's corner said "Sketch1 — 2 degrees of
freedom" (or "fully constrained", "constraints conflict") and the
browser marked the sketch "(editing)" or "(attachment lost)", in
English whatever the language; one degree of freedom read "1 degrees of
freedom". They now take messages: the catalogs' existing degrees-of-
freedom ones and four new ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch status and editing mark: the sketch's default name as shown elsewhere
The browser shows a default name in the user's language ("Skizze 1")
except on the edited sketch's row, whose mark made it a different text;
the view's status kept the stored name too. Both now use the name as the
browser shows it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Motion Study, Motion Trace, the motion plot, Explode and the contact
check drew their notes and refusals as they got them ("add a key for a
joint first", "select a point on a component", "none"), so they read
English in every language. They now go through the label translator, and
the four that no catalog carried get messages.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Double-clicking a sketch row inside a component (an inserted fastener's sketch)
activates that component so the sketch is edited in its placement. Finish and
Cancel only closed the sketch, so the component stayed active and every other
body stayed faded.
Edit Sketch now remembers the component and placement active before it
activated the sketch's own (also when sketch.edit activates the component of a
face-attached sketch), and leaving the sketch makes that one active again, as
Finish already puts the history marker back. A component activated while the
sketch is open stays active.
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
* claim: #5592
* Canvas and Decal: send the picked face, so a large design takes the image (#5592)
A click with Canvas or Decal sent only the point, so the engine searched every face of every
body for it and gave up on a large design ("face capture work limit exceeded"). Send the
picked body, face index and path as Create Sketch does; canvas.decal takes that face pick too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Canvas and Decal test: drop the check that a point alone gives up
On upstream main a sketch on a face by its point alone no longer gives up on this design, so
the test keeps only what the picked face has to do: both images land on it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6039
* Sketch: offer an arc length dimension for a selected arc (#6039)
The arc length dimension already existed in the engine (sketch.dimension with
type arc_length), but nothing in the UI led to it: the Dimension tool and the
context menu always gave an arc its radius.
With exactly one arc selected, the sketch context menu now offers Arc Length
Dimension next to Sketch Dimension, which adds the dimension through the
existing command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
* test: move the arc length menu test up to avoid a merge conflict
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>
* claim: #5517
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Right-click opens the menu despite tiny pointer movement (#5517)
A Magic Mouse moves the pointer a fraction of a point as it is clicked.
The right button orbited on any movement, and the menu needed exactly
zero movement in the release frame, so most right-clicks only nudged
the view. The right button now navigates once the press can no longer
be a click (egui's click distance and time), catching up with the
travel since the press, and a right-click is egui's click.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7947
* Browser: reveal a placed body's row when it is picked or found (#7947)
The off-screen skip in `entry_row` keeps the reveal target's row alive so it
still has a rect to scroll to, but it matched `app.tree.reveal` (a world body
name) against `Entry::key` (the placement-local name). For a placed body those
never match, so the row returned early and the `scroll_to_rect` below it, which
does use `row_key`, was never reached. `app.tree.reveal` is then cleared at the
end of the frame, so there was no second chance.
Match the skip on `row_key` minus its `b:` prefix, as the `scroll_to_rect`
already does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The Box, Cylinder, Cone, Sphere, Torus and Coil dialogs labelled their
size rows with the command's parameter names (length, top_diameter,
section_size), in every language. Each row now takes a message: Length,
Top diameter, Major radius, Section size, translated as the rest of the
dialog is.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix linked component materials and appearances (#7609)
* Linked components: the cached link read carries the source properties
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Update Links: a link without recorded inherited properties is read again
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Preserve authored linked-face overrides and apply cached source properties
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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)
- 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>
#6471 joined the body a To Object extent runs to into the tool, whatever its component.
A connector's boss extruded up to an enclosure's face (each in its own component) merged the
enclosure into the connector, and features referencing the swallowed body failed after it.
The body it runs to now joins only when it is in the feature's own component, or when the
feature names it as a target.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* 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.
* claim: #7107
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: Delete on a picked profile deletes the curves around it (#7107)
While a sketch is being edited, a profile picked by clicking inside a
shape stands for the curves of its outer loop, so Delete removes the
shape instead of failing with "a profile goes with its sketch". Holes
inside it are shapes of their own and stay. Outside the sketch a
profile still goes only with its sketch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Delete a picked letter's whole text, not just its outline (#7107)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* 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>
* 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>
* claim: #7253
* Sketch: deleting a dimension in a rolled-back history no longer rebuilds the features after the marker (#7253)
The check before a delete evaluated the whole history twice when the
timeline was rolled back. Removing a sketch constraint changes the later
features' cache keys, so on a long history Delete on a dimension took
minutes. When the design differs only in sketch constraints (and model
parameters nothing uses any more) and each changed sketch solves to the
same geometry, profiles and ends, the features after the marker see the
same inputs: compare the live models only. Anything else still gets the
full check, so the report is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
* claim: #7203
* Report a bug: Ctrl/Cmd+Enter continues on GitHub (#7203)
The report's fields are multi-line, so Enter adds a line break and the
only way on was the mouse. Ctrl+Enter (Cmd+Enter on a Mac) in any field
now does what Continue on GitHub does: it is read before the fields see
it, so it adds no line break, and an empty description is still asked
for. A note under the description says so, with the keys of this
computer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Report a bug: keep the shortcut hint apart from the other report changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Report a bug: holding Ctrl/Cmd+Enter continues on GitHub once
egui's consume_key also matches the key's repeats, so every repeat frame
opened another GitHub tab. Only the press counts now; repeats are dropped.
The test sends the modifiers each platform really sends (mac_cmd or ctrl
together with command) and holds the key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Arabic and Hebrew for the new strings
ar and he are complete catalogs on main now (#3458): every new message
needs them. The pending-messages test compares right-to-left messages in
display order once #7532 lands (#7527).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Vietnamese for the new strings
Vietnamese became a complete catalog on main (#5823), so every new English
message needs it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Report a bug: the Save screenshot test scrolls its button into view
The shortcut hint adds a line above the screenshot, which moves its
buttons below the fold of the review's scroll area at the test's size.
The test scrolls them into view first, as the other report tests do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Save as Mesh: Add all bodies and Add visible bodies buttons
Two buttons under the Bodies input fill it with every body of the design,
or with only the bodies the viewport shows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Save as Mesh: translate the two body buttons for every complete catalog
ar, he, ja, vi and zh are complete catalogs, so every English message needs
an entry there; ko as the ui-text skill asks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* claim: #6828
* Viewport: clear new resolve targets so AMD Direct3D 12 shows the scene (#6828)
With anti-aliasing above 1x the scene is drawn multisampled and resolved
into textures that nothing but the resolve writes. wgpu counts the resolve
as their first write and never clears them. Direct3D 12 requires a render
target placed in reused memory to be cleared, discarded or copied over
first, and some AMD Radeon drivers then show the uncleared texture as
transparent, a flat colour or speckles: no grid, origin planes or bodies
(#6828, #7157, #6613, #7052). Each new resolve texture (the frame's colour
and the see-through sums) is now cleared once when it is made.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7502
* Assembly: name placed bodies from a per-design index, reveal the Browser from the body's path, evict cache entries singly (#7502)
* Release the placement index before in-place document edits so pinning body names stays in place
---------
Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
A sketch on a face of a placed component is evaluated in that component's frame, so the
evaluator moves (and meshes) the bodies placed differently into it. FrameBodies keeps those
moved bodies, but every evaluate started a new generation and dropped what neither it nor the
evaluation before it used. Evaluations that reuse every result never touch the cache, so two
of them in a row emptied it: Delete refreshes the model three times (selection.delete's nested
refresh, its own, then execute's), and the confirmation's dry run more, and an undo answered
from the cache is another. The next edit of the sketch then moved and meshed every placed body
again: in a design of ~3,500 imported bodies each sketch edit after a Delete took 46 s, 34-43 s
of it tessellation.
The generation now starts on the evaluation's first use of the cache, so an evaluation that
moves nothing leaves it alone. It still holds at most two generations of moved bodies.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* 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>
* claim: #7756
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* File dialogs open in the folder used last (#7756)
The desktop dialogs never said where to open, and the Linux ones then
start in the home folder every time. Open, Insert, attach, Save As and
Export now open in the folder of the file last chosen in a dialog, or of
the open design when another design becomes the open one, whichever came
last. Also fixes#7735.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5769
* Let body appearances replace imported STEP face colours (#5769)
A body appearance assigned in the design now replaces the face colours the
body was imported with; face appearances set in the design still win over it.
* 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>
* claim: #7296
* Viewport: a frame the GPU has no memory for is drawn smaller, not a crash (#7296)
The viewport's offscreen frame, its see-through targets and the cached
still picture were created with no error scope, so a texture the card had
no video memory for went to wgpu's default handler and closed the app
("wgpu error: Out of Memory" on a GT 740). They are now created under error
scopes; a refused frame lowers the renderer's frame budget and is asked for
again smaller (less supersampling, or the view's own frame drawn smaller and
filtered up), refused OIT targets fall back to sorted see-through surfaces,
and the status line says once that the view is drawn at a lower quality.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Arabic and Hebrew for the new strings
ar and he are complete catalogs on main now (#3458): every new message
needs them. The pending-messages test compares right-to-left messages in
display order once #7532 lands (#7527).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* GPU memory test: targets take the OIT flag main added (#7296)
The test's two GpuTarget initializers predate the oit field and no longer
compiled once the branch met main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Vietnamese for the new strings
Vietnamese became a complete catalog on main (#5823), so every new English
message needs it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6003
* Save: a design changed while it is written stays unsaved, and scripts wait for it (#6003)
Two of the gaps @raykholo found in #6003 were still open on main:
- `engine.script` ran while the window's Save was being written: it calls
the session directly, past `SolveApp::run`'s file-operation guard, and
`drain_control` did not hold design-changing requests for a save. They
now wait for the save like they wait for a file being loaded or picked.
- `adopt_save` marked whatever the session held when the save finished as
saved. It now takes up the state the save wrote (`done.saved`, and its
visibility), so a change made meanwhile stays unsaved instead of being
lost on close without a prompt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6570
* test(text): cover nonpositive pitch and format-12 glyph zero (#6570)
Original synthetic font regressions. Focused native font tests, strict scoped Clippy and wasm pass. Full repository gate status is recorded in PR #6574; no CI infrastructure changes.
* Sketch: switch a circle or arc dimension between radius and diameter (#7499)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: Toggle Radius / Diameter for a circle's or an arc's dimension (#7499)
New command sketch.toggle_diameter, with input and output schemas, and a
context-menu item on a single selected radius or diameter dimension. The
circle keeps its size: the dimension's parameter is halved or doubled (a
plain value is rewritten, an expression is wrapped). Refused, with nothing
changed, when anything else uses that parameter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7741
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move: a copying Move can be edited from the History (#7741)
Editing a feature runs its command on a scratch session with the marker
right after it. For the last feature that marker sits at the end of the
timeline, and place_feature pushed the new feature after it, rolled back,
so a copying Move found no copies and refused the edit. A marker right
after the last feature now moves on past a feature added there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7749
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Delete: deleting one instance of a component takes its joints with it (#7749)
Deleting an occurrence while its component had other instances dropped
only the occurrence: the joints on it, their motion links, joint origins,
contact set places and exploded moves stayed, so a joint stayed in the
Browser naming an occurrence that was gone. The clean-up that
remove_component_tree did is now Document::remove_occurrences, used by
both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5638
* Box dialog: place the box by its corner or centre (#5638)
The Box dialog gets a Placement choice (Corner or Center) with X, Y and Z
fields that take expressions and units, and a Point input: a vertex, a
point on an edge or face, or the origin picked in the view fills them.
The Cylinder dialog gets X, Y and Z for the centre of its base. Left
alone, a new dialog sends no placement, so the box stays at the origin;
Edit Feature shows the stored point and OK without a change keeps it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Box dialog test: pass the active feature to Input::accepts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Box dialog: Base Center in the complete Arabic, Hebrew and Vietnamese catalogs (#5638)
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>
The native codec gave one body back with other topology: a pattern copy
cut by an automatic extrude, 10 faces, 18 edges and 10 vertices, came
back with 12 vertices, so the design opened with another body than it
was saved with. Encoded::of now decodes each body it encodes and keeps
the model only when every body comes back with the same face, edge,
vertex and shell counts; otherwise the design is saved without its
model, as when the encoder refuses a body, and opening it evaluates it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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)
* claim: #6525
* perf(eval): reuse local geometry across placement changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(engine): preserve recovery journal through shared refresh
The shared evaluator retains the document Arc, so refresh and body-name
pinning can copy it after command_done recorded its address. The next read
then mistakes that copy for an outside edit and discards the replay.
Record completion after refresh, name pinning or failed-command restoration.
Add a minimal journaled box/inspect regression in the existing crash suite.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7009
* Assistant: connect the last-used agent and open the panel at start (#7009)
Two Settings > AI options, off by default: when SolidCraft starts, connect
the agent started last (once the installed agents are known; a missing one
is not swapped for another) and open the Assistant panel. Not for runs
started with --control.
A connection that never gets ready (no agent, start error, the agent ends
while starting) is a new Failed state: the header reads Connection failed
and Connect reads Retry. The control channel reports it as "failed" and
takes connect_at_start / open_at_start.
* Assistant: keep the start-up agent when a design replaces the blank one (#7009)
Opening a design over the blank design SolidCraft starts with reset its
chat and stopped the agent connected at start. A chat not asked anything
yet now starts over in place and keeps its agent. Turning AI features
off also drops a start-up connection still waiting for discovery.
* i18n: Arabic, Hebrew and Vietnamese for the start-up options (#7009)
ar, he and vi are complete catalogs on main now, so every new English
message needs them.
---------
Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
Hovering or picking a face in the view lights the feature that made that face (its
creator, as Edit Feature on a face finds it: the hole, not the plate it was cut into)
in the bottom timeline, the History on the left and the Browser's Features folder;
edges and vertices light the feature that made their body, as before. A new face or
edge pick scrolls the history shown to that feature once, through the Find in
Timeline reveal.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Update Links reads again only the linked designs that changed (#5606)
Update Links read, evaluated and STEP round-tripped every linked design on every run. Remember a digest of each linked file, of the STEP it gave and of its face names, and skip a link whose file, bodies and face names are all unchanged. Read a linked design through its saved model when the file has one, as opening it does.
* Drop an import the rebase left unused
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>
* claim: #7350
* Report a bug: Retake rebuilds the details instead of refusing, and keeps the screenshot (#7350)
Once anything changed with a report open, every Retake refused with "Settings or model
changed. Close this draft…", and each refused, waiting, timed-out or stale capture deleted the
screenshot the draft had. Retake now rebuilds the technical details for the current view
(keeping what was typed, edited details and a crash draft's body) and re-baselines before it
captures; the screenshot is replaced only when a new frame is accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Arabic and Hebrew for the new strings
ar and he are complete catalogs on main now (#3458): every new message
needs them. The pending-messages test compares right-to-left messages in
display order once #7532 lands (#7527).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Vietnamese for the new strings
Vietnamese became a complete catalog on main (#5823), so every new English
message needs it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The default view sends the single pixel's ray through a box edge, which the
raytracer rejects as an ambiguous interface normal. The job then ended Failed
whenever the render thread got ahead of render.cancel (about 7% of runs under
load), and the test only accepted Cancelled or Completed. Render off-axis so
the outcome no longer depends on that race, and print the phase and error if
it ever fails again.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7148
* Lost GPU: a dropped frame no longer crashes the window's close (#7148)
On a lost device wgpu's present and discard return early and leave the
acquired frame's image in the surface, holding a swapchain acquire
semaphore. Dropping the surface later (the window closing for the
restart after the loss) then panicked in wgpu-hal. The painter now holds
the frame as AcquiredFrame, which destroys a second handle to the
frame's texture after the frame is presented or dropped; wgpu-core's
destroy of a surface texture takes the image back without checking the
device, and does nothing once a present or discard took it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7077
* Repeat Last Command in command search, with a key you can change (#7077)
Shift+R already started the last tool or dialog again (#3045), but it was a
key the app kept for itself: command search could not find it and Keyboard
Shortcuts could not change it. It is now the command edit.repeat, with
Shift+R as its default key. A call through the engine is refused, because
only the app has a tool to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Arabic and Hebrew for the new strings
ar and he are complete catalogs on main now (#3458): every new message
needs them. The pending-messages test compares right-to-left messages in
display order once #7532 lands (#7527).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Vietnamese for the new strings
Vietnamese became a complete catalog on main (#5823), so every new English
message needs it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6533
* Sketch: add points to an existing spline (#6533)
A spline could not take more points after it was drawn. The new command
sketch.spline.insert_point inserts a fit point on the curve where it
passes nearest a spot, between its neighbours, or adds a new first or
last point. Existing points keep their indices, so constraints stay. A
right-click on a fit point spline offers Add Spline Point at the clicked
spot. Control point splines are refused for now: with uniform knots one
more control point would reshape the whole curve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Arabic and Hebrew for Add Spline Point
ar and he are complete catalogs on main now (#3458). The pending-messages
test compares right-to-left messages in display order once #7532 lands
(#7527).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Locales: Vietnamese for the new strings
Vietnamese became a complete catalog on main (#5823), so every new English
message needs it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7219
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* History: delete a folder with its features, and suppress a whole folder (#7219)
`timeline.delete` and `timeline.suppress` take `groups: [timeline folder ids]`:
every feature in those folders and their nested folders, in one undo step.
A delete takes the folders themselves too; `timeline.ungroup` still keeps the
features. The History on the left offers Suppress/Unsuppress Selected Folders
and Delete Folders and Features… (asking first, like Delete Selected
Features…, when something outside needs them); a folder whose features are
all suppressed is drawn faint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* History folder actions: Arabic, Hebrew and Vietnamese strings (complete catalogs)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6981
* Drive Joints: say why a rigid joint can't be driven and offer Edit Joint (#6981)
Drive Joints opened on joint 0 when no joint moved, and its preview ran
joint.drive on that rigid joint, so the dialog showed a red "a rigid joint
does not move" with a Create Issue button.
The dialog now says a rigid joint holds its parts still and how to let them
move (Revolute to rotate, Slider to slide, Cylindrical for both), with an
Edit Joint button that turns it into Edit Joint on that joint. Its commands
refuse a rigid joint, so nothing is previewed and OK stays off. A rigid
joint picked in the browser opens Drive Joints on it to say the same; the
joint box lists only joints that move. joint.drive's own refusal names the
types that move and joint.edit.
* Drive Joints: the rigid joint message in Arabic, Hebrew and Vietnamese (#6981)
Main now delivers ar, he and vi complete, so every new message needs them.
---------
Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
* claim: #7395
* Sketch: a circumscribed polygon stays regular when dragged (#7395)
Circumscribed Polygon held its sides equal and tangent to the support
circle only. Equal sides round one circle leave every other corner free
(a rhombus round a circle), so a hexagon had 9 degrees of freedom instead
of 4, and dragging its centre or a corner pulled it out of shape (radius
10 became 8.02, angles 174/66 degrees).
Its corners now also lie on a construction circle concentric with the
support circle, which makes it regular as an inscribed polygon is, and
one side is tangent to the support circle, which sets its size: dof 4
(centre, size, turn). One tangent rather than all of them, so no
constraint is redundant.
Test in sketch_ux_tests: 4, 5 and 6 sides, dragged by the centre and by
a corner, stay regular with their sides on the support circle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A point a distance from another point on the same line has two places on it, the same distance
either way. A solve after a value edit or a link move could land on the other one and report the
sketch solved, with no warning: the point jumped to the other side of its anchor, twice the
distance away. Such a distance is now an orientation the solves keep, like an unsigned offset's
side: which way along the line the one point lies from the other. One point must be on the line
by a constraint (an end, a point held on it, or coincident with either); the other may lie on it
by what else holds it (the mirror image of a point on it across a line square to it), read where
it is in the reference state.
Test: a point 3 mm along a linked rail from a point held on it, or mirrored onto it: the image
back past that point counts as a flip, and the rail walked far keeps the side.
Fixes#7506.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6804
* Sheet metal: a base or contour flange hides its sketch (#6804)
Only an Extrude or a Revolve counted as using its sketch, so the sketch of a
base flange stayed shown. Its line on the plate's bottom edge lies exactly on
that edge and took the Flange dialog's click: a sketch line makes a contour
flange, a separate flat wall with no bend whose Flip moves it into the plate.
A base or contour flange now uses its sketch as an Extrude does: hidden unless
shown on its own, and shown while its feature is edited.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
* Sheet metal: test that a click on the bottom edge makes an edge flange (#6804)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The merge queue ran the four-shard GPU path tracer tests (about 56 runner-minutes)
on every group. Now they run when the diff touches render code (crates/render,
crates/gpu/src/path, the render workspace UI, assets/render), or a pull request
carries the new render-tests label (a pull request or a group holding one). The
nightly and manual runs still always run them; a push to main does not.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Typed contracts for the cylinder, sphere, torus, pipe, bounding solid and scale
Contracts for solid.cylinder, solid.sphere, solid.torus, solid.pipe, solid.bounding_solid and
solid.scale, with `FeatureResult` output, and the shared types `AxisDir` (calling
`cmd::axis_dir_form`, split from `axis_dir_param`), `NumberExpr` and `ScaleFactors`. The
descriptions say what the evaluation does: the limits it enforces, the real default scope of
`targets`, and where `participants` overrides a key or has no effect. Scale makes no body, so it
declares `name` and `participants` itself. Box's descriptions get the same corrections. The
reply test runs every form in Warn and checks for no `param_warnings`; the guide lists the new
types and adds lessons from the conversion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Typed contract for the cone
`solid.cone`, added on main beside the primitives this PR converts, gets its contract and
`FeatureResult` output: the cylinder's keys plus `top_radius` / `top_diameter`, with the
evaluation's limits in the descriptions (a radius above 0, a top radius of 0 or more, a height
that is not 0 and may be negative, sizes within 1e7 mm). A `null` top radius stays the handler's
to refuse, since the handler takes it beside a `top_diameter`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4860
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Graphics: the window no longer waits for Direct3D 12 shader compiles (#4860)
On Direct3D 12, wgpu compiles every pipeline's shaders with FXC when the
pipeline is made, and nothing is kept between launches, so the window
waited seconds for the viewport.
- The GPU tessellation shader, most of that time, compiles on a thread of
its own. Bodies are built on the CPU until it is ready, then drawn from
their plans; where it never compiles they stay on the CPU, as before.
- The viewport's pipelines build in parallel, one job per fragment shader
(solidcraft_gpu::concurrent). A job that errs, panics or cannot start
runs again on the calling thread, so errors reach the caller's scopes.
Threads only on Vulkan, Metal and D3D12 with FXC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7327
* Trackpad: inertia stops when Shift changes mid-orbit (#7327)
macOS sends the inertia after the fingers lift as its own scroll bracket,
with the modifiers held at the time. Each move was routed by its own
modifiers, so releasing Shift while an orbit coasted turned the rest of
the inertia into a pan (and pressing Shift turned a coasting pan into an
orbit). A bracket that starts as the fingers' one ends now keeps the
fingers' mode and ends when the modifiers change. Switching between pan
and orbit while the fingers are down works as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7225
* fix: headless pictures no longer keep the previous picture's highlight
Capturer::rgba_highlighted left the highlight slot empty when a picture had
no highlight. The renderer keeps its last highlight scene while the slot is
empty, so a selected edge's band drawn for one picture showed in the next
one on the same capturer. The depth tests share their capturers across test
threads, so they failed at random: an edge "192 of 612 px drawn", a washer
showing through its plate, a cut showing what lies behind it. A picture now
always gets a highlight scene of its own, empty without one.
Clearing the leak showed that a_selected_edge_on_a_grazing_face_draws_whole
passed at 88 degrees in a design of radius 30 only by counting the leaked
band: its reference line lies 143 mm behind the sheet's middle, past that
design's far plane, and drew nothing. The small design now holds the edge.
New test: a_picture_without_a_highlight_shows_none_of_the_one_before.
Full `--test depth` runs on Apple silicon: 10 of 10 failed before, 0 of 20
after.
* claim: #7274
* Renaming an imported body with numbered siblings evaluates nothing again
Since #2467, Model::rename_body evaluates the design whenever another body's
name is worked out from the renamed one's (`X (2)`). Every STEP import whose
solids share a label has such names (`Part`, `Part (2)`, ...), so renaming
one of its bodies re-read the import and meshed every body again.
body.rename keeps the names of a body's siblings and of later bodies named
after it, so for a body no later feature changes (the same mesh from the
feature that made it to the end), renaming it in the model gives the names
evaluating the renamed design gives. That case is followed again, as before
#2467; bodies later features change still take #2467's checks.
* claim: #7192
* Web app: Report a bug saves its screenshot as a download (#7192)
Save screenshot… wrote the PNG with image's save, that is std::fs, which
the browser doesn't have. The screenshot is now encoded in memory and
written through vfs, as every other save is, so the web app hands it out
as a download. Natively the same file is written and ui.screenshot
replies as before. A failed save says why in plain text instead of a
Rust debug value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Web app: a screenshot without a path no longer panics (#7192)
`ui.screenshot` without a `path` saved to `std::env::temp_dir()`, which panics on
wasm32-unknown-unknown (std has no file system there), and the web app's control
requests come through `window.solidcraft.request`. `vfs::temp_dir()` now returns
`vfs::BROWSER_TEMP`, a folder of the in-memory files, in the browser, and
`save_screenshot` and `vfs::design_folder_refusal` (reached when a request names
`allow_paths` while a design is open) use it. Natively nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Web app: keymap and theme exports download (#7192)
Both exports wrote with `std::fs::write`, which always fails in the browser. They
now write through `vfs::write` like the other saves, so the web app hands the file
out as a download. Natively the file is the same; an agent's folder scope now
applies to them as to every other write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: say what the folder-scope checks of the saves show (#7192)
The screenshot test wraps the write in a folder scope by hand: that proves the write goes through vfs, not that ui.screenshot's write runs inside the request's scope. ui.screenshot checks the agent's folders when the request arrives; the PNG is written in a later frame, outside that scope, as on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The seat of a subscribed connection on the DesignWatch lived in the
connection's serve thread. It left only when that thread saw the end of
the stream, so one unbounded blocking read stood between a closed client
and the count. Under load (GPU backend probing and crash-dump children
on Windows) that read can sit for seconds; the test's 10 s deadline then
expired and `!WATCH.subscribed()` failed (#7226).
Move the seat onto the events thread. The thread ends on `stop`, or on
the first write a closed client refuses, so the next publish retires the
seat. The test now publishes while it waits, and its wait no longer
depends on the serve thread.
Neither theme set the File view's rail colours, so SolidCraft derived the rail from the accent and
the start page's whole sidebar was the primary orange, against the design system's one-or-two
ember budget (#4868 derives an unset rail from the accent). The rail is now n0 (void, beside the
app bar), its hovered and open row n3 (raised), and its text n11.
Session::refresh evaluated the model and then solved the joints, which
writes the solved placements into the design. A feature that reads where
a part is placed kept the result made for the placement before the
solve: a mirror of a jointed part's body, made from the root, ended up
4 mm below the part after a move the slider took back.
When the solve moves parts, the model is now evaluated again for the
placements the design holds, and the joints solved for that model, at
most three rounds. Features that don't read placements come from the
cache.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A tangency at a point the two curves share (or a line's end held on the round) is solved as a
block of its own, and the orientations a solve keeps left it out. When a point placed by
parameters moved far, the line could land on the other tangent from its far end (its mirror
image) with every constraint met and no warning. It now keeps the round's side of the line, as a
tangency away from the ends already does: kept where the move can be walked there, reported as
a flip where it can't.
Fixes#7428.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7206
* Browser: a body's name hides the body, not a sketch of the same name (#7206)
`browser.visibility` looked a key up as a feature first, so when a sketch
had exactly the name of a body (a sketch named after the body it makes),
the body's eye in the browser hid and showed the sketch instead, and the
body could not be hidden at all. The browser names a sketch by its id and
a body only by its name, so a name that is exactly both now names the
body. The sketch still hides and shows by its id; matches that differ
only in case are left as they are (#6967, #7004).
Fixes#7569 too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7131
* feat: Complete the French UI catalog (#7131)
- Add the 1,153 messages missing from fr.json as a pending fr block
- Keep the terminology already used in fr.json and French typography
---------
Co-authored-by: P'tite Tête <asso@ptitetete.com>
* claim: #7171
* Report a bug: take the screenshot once the preview is done instead of giving up (#7171)
A report opened while a preview was being computed took no screenshot
and asked to be reopened, which loses what was typed. It now waits for
the preview with the report open and takes the screenshot as soon as it
is done; after 10 s it shows the preview unfinished and says so in the
report (a hanging preview is often what is being reported).
The preview the report waited for belongs to its context unless the
design, the dialog or the selection changed meanwhile, so the retake
check compares those alone. What is typed while the capture hides the
report waits for it, and its focused field gets the focus back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Report a bug: keep the new helpers apart from the other report changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Report a bug: keep what is typed as the waited-for screenshot is taken
The screenshot a report waits for starts in the frame the preview
finishes, before the form is drawn, so what was typed in that frame went
nowhere. It is now held with what comes while the form is hidden. The
report's context also leaves the dialog's own state out: a preview error
the dialog shows meanwhile (a sketch fillet's) no longer counts as a
change that refuses the screenshot, since it is often what is reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7188
* macOS menu: disable design commands behind an open Report a bug draft (#7188)
The native menu bar kept running its actions while a Report a bug draft was
open, although the app's own toolbar and shortcuts wait behind it: Edit > Undo
or Cmd+Z removed the last feature, so the report described a design that no
longer existed. FileAction::enabled now refuses every action but Quit while a
draft is open. Settings, Keyboard Shortcuts and Agent Chat are refused too:
their windows open over the report but take no input until it closes. The
menu's Undo and Redo still go to a focused report field
(FileAction::menu_enabled), which no longer leans on Settings being enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Menus: Quit and Help's windows wait behind an open Report a bug draft
Quit switched to the first design with unsaved changes under the draft and
asked about it in a prompt that can't be answered while the report blocks
the app; with nothing unsaved it quit and lost the typed report. Like any
modal window, the draft now turns Quit off. Help's items that open a
window (About, Keyboard Shortcuts, Messages, Crash Reports, Connect AI
assistants) wait too, in the native menu and in the app's Help menu;
Documentation and Report an Issue… stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7190
* Report a bug: focus the empty description once, not every frame (#7190)
After Continue on GitHub found What happened? empty, the form moved the
focus back to it on every frame, so typing in Summary went into the
description. Continue now re-arms the one-time focus (and scroll into
view) instead; the other fields stay free to type in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #1156
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Startup tests: start the backends the app would, no OpenGL driver on Windows (#1156)
The software-adapter test enumerated every wgpu backend and the probe test
asked OpenGL on its own even where the app stops at D3D12, so both loaded the
GPU vendor's OpenGL driver (atio6axx.dll on AMD) into the test process, where
it once aborted the process at exit with c0000409. The adapter test now starts
what the software renderer starts (D3D12 alone on Windows) and the probe test
probes in the app's order, stopping at the first backend with a GPU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Startup tests: read the software renderer's backends from wgpu_options
Review: the adapter test now takes the backends from the app's own
wgpu_options(true, None) instead of a copy of its choice, and the probe test
no longer re-checks the order choose_backends' own test covers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7281
* cargo xtask skills: check the files as Git stores them, on a CRLF checkout too (#7281)
A Windows checkout with core.autocrlf gives AGENTS.md and the SKILL.md files CRLF line endings,
so `front_matter` found no `---\n` and every skill was reported, and each line's `\r` counted
towards the size caps. The check now reads the files with CRLF taken as LF, as #4960 did for
icon_audit. The new test checks a CRLF skill filled to the cap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7299
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a circle centred on an existing point shows its Coincident glyph (#7299)
The centre circle tool sent a centre snapped to an existing point as that
point's id, so the circle shared it: attached, but with no constraint and so
no glyph to see, pick or delete. A plain point snap now goes as its place,
where `connect` ties the new centre to the point with a Coincident the sketch
draws. Midpoint, on-curve, crossing and body-vertex snaps still go as refs,
so the centre stays held there (#3099 keeps passing).
Fixes#7299
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Claim: centre arc on an existing point shows its Coincident glyph (Refs #7299)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: an arc centred on an existing point shows its Coincident glyph (Refs #7299)
The Centre arc tool sent a centre snapped to an existing point as that point's id, so
the arc shared it with no constraint and the sketch drew no glyph. A plain point snap
now goes as its place, where draw's connect ties the new centre with a Coincident;
midpoint, on-curve, crossing and body-vertex snaps still go as refs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7156
* Highlight the selected sketch in the viewport (#7156)
Picking a sketch in the Browser or the timeline selected it, but the
viewport lit nothing: the highlight only drew the bodies and faces a
feature made. It now draws all of a picked sketch's curves in the
selection colour, over the model and also while the sketch is hidden,
so a sketch is easy to find without hiding and showing it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7348
* Report a bug: include the Messages log and other failing features (#7348)
About half of the reports say "No command error was recorded" and often
can't be reproduced. The error was often in the Messages log, or on a
feature that was neither selected nor the latest. The technical details
now list every failing feature (id, name, kind, error; at most 10) and the
last 8 messages with level and age. Each entry is one quoted line of at
most 300 characters, with the home folder shown as ~.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6977
* Open from computer: let iOS and iPadOS pick .solidcraft files (#6977)
The iOS file picker matches an input's accept list by system file type,
not by extension. No system type has the .solidcraft extension, so the
designs were greyed out. On an iPhone or iPad the file inputs now offer
every file; other devices keep the extension filter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7007
* A surface left without a configuration is configured again, not a crash (#7007)
wgpu reports device-loss errors to no error scope. On D3D12 a driver reset
found by Surface::configure drops the old configuration and reports nothing,
so the surface was taken as configured and the next acquire panicked with
"Surface is not configured for presentation". A configure that meets a lost
device or an unsupported surface is now a refused configure, the acquire runs
under an error scope, and its Validation status reconfigures the surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
model_render_follows_the_grid_and_origin_switches_unless_asked compares nine
renders byte for byte and has failed once in a full run without coming back
alone. A mismatch now names the step, counts the differing pixels, lists the
renderer that drew each picture and keeps both PNGs.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The value box beside a command's geometry and the placement fields kept a number's width, so a
formula or parameter names scrolled out of sight while typing. They keep that width for a number
and grow with the text, up to three times it.
* claim: #7166
* Toolbar: a panel with no buttons keeps one that opens its menu (#7166)
Removing New Sketch from the Model tab (Remove from Toolbar, or a drag off
the toolbar) left the Sketch panel empty above its label, so Sketch seemed
to be missing from the toolbar. A panel whose buttons are all gone is now
drawn like a collapsed panel: its first command's icon over a caret, which
opens the panel's drop-down, in the same room.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7643
* Web app: Import… reads shortcuts and theme files through vfs (#7643)
Import… on the Keyboard Shortcuts page and the theme strip's Import… tile
read the picked file with std::fs::read_to_string. In the browser a picked
file is kept in vfs's in-memory files under /upload/, and std::fs on
wasm32 is unsupported, so every import failed. Both now read through
vfs::read_to_string, capped (a theme file at theme_files::MAX_BYTES, a
shortcuts file at the new toolbar_custom::MAX_IMPORT_BYTES), like the
other imports. Natively it is the same file on the disk.
The new tests import a file outside a vfs folder scope (refused, nothing
imported) and inside it (imported). With std::fs both fail: the file
outside the scope is imported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
timeline.redefine evaluated every later feature in its trial session, then again in the session:
the evaluation cache keeps only the last few states of a design with large bodies, so the trial's
results were gone by then. The trial keeps them in the session's kept results (or its own), and
the session's evaluation takes them from there. EvalCache::with_budget gives tests a small cache.
* claim: #7152
* Preferences: the Colour vision preview shows the chosen mode again (#7152)
Since the preferences redesign the Colour vision section previewed a mode
with four status chips (Selected, Warning, Error, Done). No colour vision
mode changes those colours, so the chips looked the same in every mode.
The section shows the sample of what the modes recolour again (sketch
lines, axes, bends, Draft Analysis, a failed feature), drawn in the mode
being chosen (Tokens::get_in). The status chips stay as they are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7091
* Report a bug: scale a large window's screenshot down instead of refusing it (#7091)
A frame over 16 million pixels (a 6K display, a window across two 4K
screens) was refused with "Screenshot dimensions are unsupported", so the
report went out without its picture. Such a frame is now averaged down by
the smallest whole factor that fits.
The frame is also fitted to the longest side a texture can have: a frame
under the pixel limit but wider than the GPU's limit (three ultrawide
screens, 10320 x 1440, on a device limited to 8192) made the preview's
upload fail wgpu validation, and the window's device panics on that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Inspect: center of mass measures the world state, as Measure does (#5246)
inspect.center_of_mass read the model state, so in an assembly a moved
occurrence was measured where its bodies were made, a second placement
was left out, and a placed copy's world name was "no such bodies". It
now reads the world state, as inspect.measure does, and agrees with
component.properties' mass centre at uniform density.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Center of mass test: unground the placed part before moving it
Upstream now grounds a design's first part when it is made.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6281
* assets: add Render appearance, library and paint icon drafts for owner review
* assets: add Render lighting, environment and light-type icon drafts for owner review
* assets: add Render camera, shot, frame and strip icon drafts for owner review
* assets: add Render output, result and compare icon drafts for owner review
* assets: list the Render icon drafts in the inventory and ATTRIBUTION
* assets: remake the Render light icons as one family with a shared dotted stem
* assets: remake the Render look icons around the lit look sphere
* assets: redraw the Render look icons so each action reads at a glance
* assets: record the owner's acceptance of the Render light icons
* assets: simplify the Render look icons and make Remove Override an alias of From Material
* assets: record the owner's acceptance of Render look icons
* assets: mark the removed look on From Material with a cross instead of an arrow
* assets: record the owner's acceptance of further Render icons
* assets: redraw the Render library, presets, environment, ground and backdrop icons
* assets: restore the first Render library and backdrop drawings and equalise the cube in the studio icons
* assets: record the owner's acceptance of the Render studio icons
* assets: redraw the Render camera and shot icons as one family
* assets: redraw the Render All Shots and Lens icons so they read at a glance
* assets: put the play badge back on the Render All Shots icon
* assets: redraw the Render output and result icons around one picture base
* assets: give Render Image its own play badge and enlarge the Compare handle
* assets: record the owner's acceptance of the Render output icons
* assets: describe the final Render icon drafts in RENDER_DRAFTS.md and ATTRIBUTION.md
* claim: #6281
* ui_kit: lit sphere thumbnail, cached and bounded
* ui_kit: look-chain widget (four steps, one active, empty steps dashed)
* ui_kit: add both to the gallery and the UI kit notes
* claim: #6281
* docs: record the Render workspace interaction design and implementation roadmap
* docs: plan the Render panels so each has one purpose
* docs: replace the tabs-and-ribbon plan with the stage-bar design
* docs: drop the note about the superseded draft
* docs: plan every Render panel and where each old control goes
* docs: defer the shared lighting rig, UI steps go first
* Patterns: a body of a moved component is copied where it is shown (#4763)
A pattern of bodies made outside the bodies' component (a screw's body patterned from the root)
moved each body's stored geometry, which is in its component's frame, by transforms in the
pattern's frame. The copies landed where the body sits before its component is placed: near the
origin, for every pattern kind. As Mirror does, each copy is now moved as the pattern moves the
body in the world and kept beside its source, in its component.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pattern test: unground the placed component before moving it
Upstream now grounds a design's first part when it is made.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Hovering many bodies re-emits every edge in the highlight's colours. Each edge
interned its three colours into the palette again, each segment was written in
eight small appends, and segments without their faces' normals encoded the
same zero normals again. Lines are now one record, the edge colours are
interned once per highlight, the buffers are reserved per edge, and the shared
width word is worked out once.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The no-OIT glass cache compared the blocks' vertex and run buffers by address
without holding them, so a freed buffer's address could come back for other
contents. It now holds them and compares with Arc::ptr_eq.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Instanced placements were culled per view, but a block drew every body in it
whatever the camera saw. Blocks now keep each body's vertex span and box; when
the view changes, each class's runs are drawn without the bodies whose boxes
are out of the view. A block whose bodies are all in view draws its runs as
they are.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Arabic and Hebrew are complete catalogs since #3768, so
every_complete_catalog_carries_every_english_message fails a pull request
that adds an English message without them. The localization guide's list of
languages a new label needs now says so.
Co-authored-by: rootarchitect <7781866+rootarchitect@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4771
* Move/Copy takes a browser body the same way Combine does (#4771)
A body row feeds the dialog's active input through the same pick as a
click in the view, and does not replace the selection (#4452). A group,
the Bodies folder, or a component is that pick for every body in the
set. M and the Move/Copy button also start from a selected body or from
a component picked in the tree.
* claim: #3457
* Hebrew interface language with right-to-left text (#3457)
Adds a complete Hebrew catalog (assets/locales/he.json, original
translations) and registers `he`.
egui 0.36 shapes each font run with harfrust, so a run of Hebrew letters
is drawn right to left, but it places the runs of a line left to right in
stored order. The new `bidi` module runs the Unicode Bidirectional
Algorithm (unicode-bidi, MIT OR Apache-2.0) over each message of a
right-to-left catalog once, giving egui the runs in display order with
brackets mirrored and placeholders kept whole, while leaving each Hebrew
run's letters in reading order for the shaper. A test lays messages out
with a real Hebrew font and checks where the glyphs land.
Desktop builds append a system Hebrew font (Arial Hebrew / SF Hebrew,
Segoe UI / Arial, Noto Sans Hebrew / DejaVu Sans). Layout mirroring,
wrapped multi-line RTL paragraphs and a web Hebrew font are follow-ups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hebrew: translate the new Describe Command label; list he in the control protocol
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hebrew: catch up with main's catalog and the new ribbon (#3457)
- Translate the 258 messages main added since the branch was cut, drop
the 38 it removed (10 renamed ones keep their translation) and
retranslate the two whose English changed (toolbar → application bar).
- The timeline title is ציר הזמן, matching the glossary.
- Ribbon buttons split their labels into two lines from the start of
the string; Hebrew labels come in display order, so the first line
showed the end of the phrase ("חדשה / סקיצה"). For right-to-left
labels the first line now takes the words at the end of the string,
and a shortened line loses characters, and shows its ellipsis, on the
left. The word-boundary sweep checks Hebrew from the lines the galley
is built from, since epaint's right-to-left rows carry zero-width
glyphs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hebrew: after rebasing onto main, load the Hebrew face with the cached system fonts
main now searches the system fallback faces once (SystemFonts, on a thread);
the Hebrew face joins that search with its coverage check. Drop the three
camera-animation messages main removed (#4296) from the Hebrew catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hebrew: translate the messages main added after the catch-up (#3457)
Translates the messages main added since the last catch-up: the history
bar's buttons and name toggles, the 3D mouse driver and axis mapping, the
thread size columns, the File view (home page), the redesigned Preferences, the new issue report
dialog, free orbit and the assistant's setup card. Drops the one message
main removed. The catalog has every English message again, so the
complete-catalog checks pass for he.
* Arabic interface language (right-to-left, on the Hebrew bidi path)
Adds an original Arabic translation of the whole English catalog
(assets/locales/ar.json, every message) and registers it. `ar` joins the
complete catalogs, so a message added to en.json later fails the build
until it is translated. It goes through the right-to-left path of #3458
unchanged: crate::bidi puts each message in display order and leaves
every Arabic word's letters in reading order for harfrust, which joins
them with the font's own tables. Ribbon labels use the right-to-left line
split from the same stack.
fonts.rs adds a system Arabic fallback after the Latin interface font,
which keeps spaces, digits and Latin text so each Arabic word stays one
shaped run: Segoe UI/Tahoma/Arial on Windows, SF Arabic/Geeza Pro/
Damascus/Tahoma on macOS, Noto Sans or Naskh Arabic/KACST One/DejaVu Sans
on Linux, or NotoSansArabic-Regular.ttf in CRAFT_FONTS_DIR. It is the first
fallback for an Arabic UI (Arial Unicode, a CJK candidate, has Arabic
letters without joining data) and the last otherwise; like Thai and
Hebrew it is never promoted for a CJK interface.
Tests: the catalog comes back in display order with no directional
controls, and Arabic laid out with a real font lands on screen word by word
in reading order.
* Hebrew: review the catch-up translations; restore the camera-animation messages (#3457)
- Review of the 533 messages translated in the previous commit: 21
corrected. Keys in the motion dialog are keyframes (נקודות מפתח, as
in Add Key), the Design workspace is תכן as in its label, a dropped
"(Direct Driver)" is restored, terms aligned with the rest of the
catalog (פורמט, סוכן קוד, רצועת הכלים), and a few calques reworded.
- Translate ui.animate_view_changes, ui.animation_speed and
ui.look_at_anim_desc, which main has again, so he stays complete.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arabic and Hebrew: translate the messages main added since (#3457, #3762)
Both catalogs again carry every English message. The 760 messages main added
since the last rebase (render workspace, material editor, standard views,
sheet metal Path Flange, AI app connections, document-on-disk notices and
more) come from A-AL-ANAZI's translations on #3768; the five Perforate dialog
messages are new here. The earlier Hebrew review is kept on top of them, and
33 of the new Hebrew messages are corrected: "completed" was "perfect"
(מושלם → גמור), Regrade read as ranking (now תיקון צבע מחדש), shaders,
guarantee, the Mirror reflection mode, simulations and displacement.
Co-authored-by: Abdullah <alksa2025@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arabic and Hebrew: attribution rows as a pending file
main now takes new ATTRIBUTION.md rows as one file per change
(attribution/pending/), so the he.json and ar.json rows move there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arabic and Hebrew: translate the 23 messages main added since (#3457, #3762)
Both catalogs again carry every English message; the two theme-page messages
main removed are dropped. Twelve of the new messages (link updates while
loading, the agent chat's model choice, plugin search, render quality) come
from A-AL-ANAZI's translations on #3768; the sketch plane descriptions, the
rigid group and empty-boolean messages, free sketch points and the web update
note are new here.
Co-authored-by: Abdullah <alksa2025@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arabic and Hebrew: translate the 12 messages main added since (#3457, #3762)
Eleven come from A-AL-ANAZI's #3768; "Turn Up Axis" in Hebrew follows the
catalog's existing up-axis wording, and the agent chat's no-tool-call message
is new in both.
Co-authored-by: Abdullah <alksa2025@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arabic and Hebrew: translate the 22 messages main added since (#3457, #3762)
* Arabic and Hebrew: a label found by its English text comes back in display order too (#3457, #3762)
* Arabic and Hebrew: translate the 32 messages main added since (#3457, #3762)
* Arabic and Hebrew: translate the 34 messages main added since (#3457, #3762)
* Arabic and Hebrew: translate the 30 messages main added since (#3457, #3762)
* Arabic and Hebrew: translate the 7 messages main added since (#3457, #3762)
---------
Co-authored-by: rootarchitect <7781866+rootarchitect@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Abdullah alanazi <x1700@MacBook-Air-Abdullah-2.local>
* claim: #6943 free orbit Shift snapping
* fix(ui): snap free orbit direction and roll with Shift
* test(ui): align free orbit coverage and complete snap help translations
Evaluation and placement never read appearances, yet an appearance edit
re-evaluated the model and placed every occurrence again. A whole-document
proof that only appearances changed now takes the existing render-only refresh,
which keeps the model and retargets the world-placement cache.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Sketch Transform: scale, turn and move sketch entities in one command and one undo
sketch.transform scales about a centre, turns about it, then moves; the window and its
handles for existing sketch geometry build on it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Sketch Transform window: one box with handles to scale, move and turn what is picked
Select curves and points in a sketch and press G (or Transform in the Modify panel or the
context menu): the picks get a box with a handle at each corner to scale (the opposite corner
stays), the box to move and a knob above it to turn (Shift snaps to 15 degrees). Position,
Width, Height, Scale and Angle in the window are the same values, to type exactly; OK applies
it as one step.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Move the new message to a pending locale file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Transform: follow the sketch field of Sel::SketchCurve
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Nathalie Schönwetter <nathalie@cfw-ag.ch>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #5542
* Reopen the designs that were open at the last quit (#5542)
A launch with no file to open reopens the saved designs that were open
when SolidCraft was last closed, in tab order. Designs never saved and
files that are gone are skipped. Preferences > General > Reopen designs
turns it off; a launch driven over --control or with --sample starts fresh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the new messages to a pending locale file
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>
Each command that names a body (appearance, material, move, ...) mapped it to
its component's own name by working out every placed body's world name and
frame, so a command on a large assembly cost a walk of the whole design. The
list is now kept with the world-placement cache while it is current.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Naming a placed body scanned every occurrence and body of the design, so the
world state and every caller naming all placed bodies grew with bodies times
design size. WorldNames indexes the design once and names each body in time
proportional to its occurrence path; one naming routine serves both it and the
single-body scan. Names are unchanged (a test compares them with the scanning
implementation).
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Viewport: palette-indexed scene and incremental appearance updates
- Move triangle, line and instance colours and opacity into a per-scene
palette (an R32Uint texture, WebGL2-safe); appearance edits update
palette entries instead of vertex bytes.
- Build CPU body triangles as one canonical vertex stream per block with
opaque/glass/faded index lists; an opacity class change rewrites only
the index list.
- Cache resolved body appearances by a per-body key, so an edit resolves
only the bodies whose look changed.
- Split the model scene key from the session revision: selection and
view-only commands no longer rebuild or upload the model scene.
- Replace the O(n^2) section-index lookup and per-placement instance scan
with hash lookups.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Viewport: skip unchanged palette work when rebuilding
- Reuse each body's appearance key while the document is unchanged.
- Leave a palette slot alone when its value did not change, and allocate
every body's slots once before the build loop.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Viewport: measure scene rebuild cost by edit kind
An ignored scale test times the scene build after selection, appearance,
theme and one-body geometry edits at 200, 1,000 and 5,000 bodies.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Viewport: read faded grid alphas through the scene palette
The grid-fade tests now resolve line palette indices instead of reading the
line record's old colour byte.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Viewport: measure rebuild cost on few large meshes
Adds the 20/100-torus case where a block rebuild used to re-emit a large
part of the model.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Viewport: measure rebuild cost on an assembly
Unique detailed parts plus one component placed many times, timed for
selection, part and component colour, opacity, theme, hide and move.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Viewport: draw block classes as vertex runs, not index lists
A body's vertices are three per triangle in mesh order, so each draw class
is a few runs of the block's vertex buffer: all-opaque blocks need no index
buffer, and an opacity class change uploads nothing. A fragmented class
(more than 64 runs) falls back to one index buffer.
Each triangle is written as one record into a block reserved up front,
which takes the cold scene build of a 7.1M-triangle assembly from 97 ms
to 55 ms (main: 83 ms).
Also fix reindexing a block whose bodies are drawn elsewhere: it walked
the mesh's triangles from that body's start, past its own vertices.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Viewport: draw the highlight's own block, and light faded bodies without copies
The highlight scene keeps its feedback (a hovered face, a body copied on
the CPU) in a block of its own; since blocks became vertex runs, only the
model's blocks were drawn, so that feedback was uploaded and never shown.
Draw the highlight's blocks with its triangles.
Whole faded and see-through bodies (another component active, or an
appearance below full opacity) were lit by copying every triangle on the
CPU at each hover change: 85 ms for a design row of 812 bodies. They are
now drawn again from the model's own block vertices, in the highlight
palette with their slots set to the highlight colour, or as placements of
the model's shared triangles. The picture is the same as the copy's.
Adds a GPU frame benchmark for document-tree hover (ignored test).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Viewport: count scene uploads, and keep the sorted glass stream across colour edits
Renderer::uploads reports the bytes the model scene's last change uploaded
(geometry, placements, palette rows, planned colours, re-evaluated vertices).
Without order-independent transparency the global glass stream is kept while
the blocks' see-through triangles are the same, so a colour edit on a glass
body uploads palette rows only. Tests assert both on a GPU.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Viewport: keep palette slots and looks without rehashing every body
A rebuild found every body's palette slots by name and wrote them back,
allocating and hashing a new key per body, and copied every shown name into a
set to check the arena still belongs to the design. Slots are written back only
when a body's faces grew, and the looks cache is pruned only when it holds more
bodies than are shown. Adds the STEP-file benchmark (SOLIDCRAFT_SCALE_STEP) and
the colour command's own time to the assembly benchmark.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Viewport: round-pick construction lines through the scene palette
The axis and centre feedback for round picks (#4610) drew with a raw colour;
on the palette scene it is interned once like the other construction geometry.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Viewport: draw faded and see-through bodies from their plans with OIT
A body faded outside the active component, or see-through, left the GPU
tessellation path and was built triangle by triangle on the CPU, so working in
a component of a large assembly rebuilt every other body. Where the renderer
blends see-through surfaces order-independently (GpuTarget::oit, set by the
renderer), a body whose faces share one class is drawn from its plan: chunks
are split by class, opaque passes, AO and the section draw only opaque chunks,
and the OIT pass draws the rest. The sorted fallback keeps them on the CPU.
The model and its highlights decide through one rule (model_plan_class).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
* Move the design-link path helpers into one module
`component_link.rs`'s path rules for links to other design files (base_dir,
absolute, relative, stored, resolve, rebased) move, unchanged, into
`cmd/design_link.rs` with their test, so other kinds of link (#3891's drawing
files, linked parameters #5354, #5113's extract) share them instead of copying
or re-exporting them. No change in behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* design_link: keep base_dir and relative private
Only design_link.rs uses them; the module's surface is absolute, stored, resolve and rebased (review on #5420).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* component_link: import Document after the rebase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Carel Theron <careltheron@Carels-MacBook-Pro.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #6861 eval scripts on input designs
* fix(cli): evaluate scripts on the input design (#6861)
* fix(cli): validate eval options before opening inputs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6608
* Canvas: keep value boxes clear of the command window (#6608)
* Canvas: describe how value boxes clear the command window (#6608)
* Canvas: keep the value boxes clear of the gizmo handles (#6608)
* ui-egui: format the value box regression test (#6608)
* gpu: the startup MSAA check skips when there is no GPU adapter
The check needs a real adapter to draw the multisampled view and to
resolve it, so on a machine with no software Vulkan adapter it is
skipped, with the reason printed, instead of failing the build.
SOLIDCRAFT_REQUIRE_GPU=1 still makes a missing adapter a failure, as in
the CI job that has a software adapter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A cut (or intersect) that names no bodies went through every body it met,
hidden ones too: a body hidden to keep it out of the cut was cut anyway. When
some of the active component's bodies are hidden, the cut now stores the shown
ones as its targets, so the hidden bodies stay as they are, and stay so once
shown again (the model never depends on what is shown). With nothing hidden it
goes through every body it meets, as before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Until a connection had shown the token, its first line (up to 16 MiB)
was parsed whole into a JSON value. A line of [0,0,...] that size is
hundreds of MB once parsed, so 16 connections without a token could
make the app run out of memory and lose unsaved work. The token is now
read from the request object without building the rest of it: other
fields are only skipped, and anything but an object with a string
token is refused as before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Canvas to Sketch: trace a canvas image into a sketch on it (#3931)
New command canvas.to_sketch, offered as Canvas to Sketch in the canvas
context menu. It traces the canvas image (automatic Otsu threshold, light
drawings on dark backgrounds inverted) with interpolated marching squares
and Douglas-Peucker, and adds lines and cubic Bezier splines through Insert
SVG's add_geometry to a new sketch on the canvas plane, with the canvas'
width, centre, rotation and flip. Holes stay holes; one undo step.
The image's declared width x height is checked before decoding (at most
40 megapixels) and the decoder runs under image::Limits, since canvases
are capped by file size, not pixel count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the new message to a pending locale file
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>
* claim: #2605
* Insert SVG / DXF: place and scale the drawing before it is added (#2605, #3443)
After the file is picked, an Insert SVG (or DXF) window sets the
drawing's X, Y offset and scale, with a live preview; OK adds it in one
step and Cancel adds nothing. The insert commands already took `at`
and `scale`; the window is what was missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert scenario: the picked drawing goes through the placement window (#2605)
`sketch_toolbar_imports_use_file_picker` expected the SVG added as soon as
it was picked; it now checks the placement window first and adds it with
OK.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert SVG / DXF: a size in millimetres, an angle, and placement by the drawing's centre
sketch.insert_svg and sketch.insert_dxf take width or height (mm, aspect kept, winning over
scale), angle (degrees) and align (origin or center). The new sketch.drawing_bounds reads a
file's own size without inserting it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Insert SVG / DXF window: Width, Height and Angle, placed by the drawing's middle
Width and Height in millimetres follow the scale (aspect kept); Angle turns the drawing
about its middle, where X and Y now put it. Uses sketch.drawing_bounds for its own size.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Insert SVG / DXF: handles in the viewport to scale, move and turn the drawing
The preview gets its box with a handle at each corner (scales about the opposite corner,
aspect kept), the box itself (moves) and a knob above it (turns, Shift snaps to 15 degrees).
They edit the same X, Y, Scale and Angle the window shows.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Insert handles: the move that comes with the release counts
A quick drag could stop short of the pointer's last position because the final move arrives
in the same frame as the release.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Insert SVG / DXF: the Drawing Size command's label in the catalogs
`sketch.drawing_bounds` came without a catalog entry, which the i18n
test requires (and the complete ja and zh catalogs with it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the new messages to a pending locale file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Nathalie Schönwetter <nathalie@cfw-ag.ch>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #2113
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Crash reports: capture native crashes on macOS (#2113)
A signal handler for SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP and SIGABRT
writes the same <journal>.native.json as the Windows exception filter: the
signal, the faulting module and offset, the thread and a frame-pointer walk.
The next start reports it instead of "an unclean exit". The signal then goes
on to the previous handler or the default action, so macOS still writes its
own crash report.
native_crash_macos.rs joins the crash-safety skill's list of files that may
use unsafe (no safe API can catch these signals).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5641
* Cone primitive: solid.cone with base and top radius (#5641)
A cone or truncated cone placed like the cylinder: base centre, axis,
base radius (or diameter), top radius (0, the default, is a full cone)
and height, with the new body / join / cut / intersect choice. It is
built from what the kernel already exposes, a revolve of its half
section about +Z through the origin, moved into place. Toolbar entry
after Torus, a Cone dialog that also places it on a picked face, Edit
Feature, a timeline icon, pending UI strings and attribution, and one
pending design-format change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #1932
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Press Pull on a fillet's round face edits the fillet (#1932)
Q on a fillet's round face opened Offset Face, which refuses a face
between two others it is tangent to ("a corner whose faces don't meet
the same way after the move"), so OK stayed disabled. The engine's
solid.press_pull already changes the fillet's size on such a face; the
window now does the same and reopens the fillet (or chamfer) to edit.
Other curved faces still open Offset Face, planar faces Extrude.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6718
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Claim bot: let an unassigned claimant /unclaim (#6718)
GitHub doesn't assign people who aren't collaborators, so many claims
have the `in progress` label and the bot's "Claimed by @user" reply but
no assignee, and `/unclaim` refused the claimant. When nobody is
assigned, `unclaim()` now also accepts the user named in the bot's
latest "Claimed by" reply on the timeline, unless the label was removed
after it. Maintainers and the owner rule are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add Open in Slicer for the selected bodies (#2595)
One command saves the selected bodies as a 3MF in millimetres (an STL if the 3MF can not be written) and opens it in the last slicer used. It is on the File menu, the Mesh tab and the body right-click menu. The first time, or when that slicer is gone, it asks which one with the same list Save as mesh uses. The Save as mesh dialog is unchanged.
* Send slicer files to a private per-user folder (#2595)
Files sent to a slicer used to go to a shared folder in the temp directory. They now go to the user cache folder (mode 0700 on Unix), under a name that stays the same for the same design and bodies, so sending again replaces the file and the slicer can reload it. The file is written to a temporary name and renamed into place. If the name can not be replaced, as on Windows while the slicer holds the file open, the copy gets a number and the status line says so. Files older than seven days are deleted at startup. The first send tells the user that sending again replaces the open file, and so does the tooltip of Open in Slicer. Send to slicer in Save as mesh uses the same path.
* Open files in a running slicer (#2595)
PrusaSlicer, SuperSlicer, Bambu Studio and Cura are started with --single-instance, so a slicer that is already open loads the file instead of opening a second window. OrcaSlicer has no such flag and is left alone, and macOS bundles are single-instance already. PrusaSlicer 3.0 ships a launcher entry that already carries --single-instance-on-url; it keeps that one and never gets a second flag. It is a preference, on by default, and scripts can pass single_instance. Preferences also gets a button to forget the slicer Open in Slicer remembers.
* List slicers the desktop entries say open 3MF or STL (#2595)
On Linux the scan of desktop entries now also lists a program that claims 3MF or STL in MimeType, even when it is not in our table of known slicers. The table still supplies names, ids, icons and flags for the ones it knows. Entries marked NoDisplay or Hidden are skipped, and so are terminal programs and SolidCraft itself. The existing size and count limits stay. The Windows scan now looks inside a Prusa3D folder, where PrusaSlicer installs itself.
* Only start slicers the user can see or chose (#2595)
file.save_mesh took a slicer parameter that could be any executable path, so a script or an MCP client could run any program. The slicer now has to be one of the detected slicers (by id or name) or a program the user already added under Custom, which the app hands to the engine from its settings. The same rule holds for file.open_in_slicer. The agent chat already asked before any file command; its question now also names the slicer.
* Review fixes for Open in Slicer (#2595)
The file name hash keeps all 64 bits instead of 32. When Open in Slicer has to fall back to an STL, the status line now says so. The engine test that sets the chosen programs clears them again. The unused "could not make the folder" message is gone from the catalogs, and the docs say plainly that finding slicers by file type is Linux only.
* Keep unsaved designs apart and hide Open in Slicer in the browser (#2595)
Two unsaved designs with the same name and body names got the same file in the slicer folder. Every document now has a uid that is new for each New, Open and Recover and stays the same for the life of the document, and the file name uses it (with the process id) until the design has a path. At the first Save As the name changes to the path-based one, so the slicer gets one new file; the old one goes with the seven-day sweep.
The browser can not start a program, so Open in Slicer is now disabled there: the engine command reports why, the File menu item and the body and component right-click items are greyed, and starting it anyway says so on the status line instead of opening a dialog. The Save as mesh dialog keeps its browser guard for the open flow too.
* Put the Session doc comment back on Session (#2595)
The new NEXT_UID static had landed between the doc comment and the struct it describes.
* List every install of a slicer, and test the first Save As (#2595)
Detection kept one entry per known slicer, so a system PrusaSlicer 2.9 and a Flatpak 3.0 beta showed up as one. Every distinct install is listed now. The same program found twice, through a desktop entry and through PATH, is still one. When a slicer has several installs, each gets an id made from where it was found (prusa#comprusa3dprusaslicer) that stays the same on every run, and a name worked out from its command and location without running anything: PrusaSlicer (system), PrusaSlicer (Flatpak, beta), PrusaSlicer (AppImage). A lone install keeps its plain id and name. The plain id or name still picks the first install, so a remembered "prusa" keeps working. Single-instance flags and the other per-slicer behaviour go by the id without the install part.
Also a test that the first Save As changes the file name once and later sends keep the name that comes from the saved path.
* Start PrusaSlicer 3.0 first and send the file once it is ready (#2595)
PrusaSlicer 3.0 starts through slic3r-app-launcher, which ignores a file given at a cold start whatever the flag, and loads it over D-Bus when an instance is already running. A small table in slicers.rs names such slicers by their command; today it holds that launcher, so PrusaSlicer 2.9 and OrcaSlicer are untouched. Ready means a name starting with com.prusa3d.prusaslicer.BroadcastListener. is on the session bus, read from busctl --user list with no new dependency. If busctl is missing or fails, nobody waits and the file is sent as before.
When the slicer is not ready, Open in Slicer starts it without the file and looks every 200 ms for up to 10 seconds. The wait runs on another thread, so the window stays live, and the status line says Starting PrusaSlicer. Once the name appears the file is sent with the same arguments as before. If the limit passes, the status line says the slicer did not respond, that the file is already saved, and to send again. A second click during the wait only repeats the Starting line, so there is one launch and one send. Scripts calling file.open_in_slicer wait inside the command by default, or pass wait: return or no.
* Always show the slicer picker for Open in Slicer (#2595)
File > Open in Slicer, the Mesh tab entry and the right-click entry now always open the picker, with the slicer used last time pre-selected, so the usual case is one click or Enter. Scripts and file.open_in_slicer with a slicer still send without a dialog. The preference button that forgot the remembered slicer is gone: it could no longer make anything ask again, and it did not clear the pre-selection either. The guide says how it works now.
* Leave Bambu Studio out of the single-instance list (#2595)
Open files in a running slicer now adds --single-instance for PrusaSlicer, SuperSlicer and Cura only. Bambu Studio is started as it is, so it may open a second window. A test pins that, for the detected slicer and for one the user picked as a custom program. The guide and the preference tooltip no longer name Bambu Studio as getting the flag.
* Sort the English catalog and leave features.md to the generator (#2595)
cargo xtask lists wanted one message moved in en.json after the rebase. docs/features.md is written by the Generated files workflow on main, so the row I had added by hand is gone from this branch; the workflow adds Open in Slicer when it lands.
* Drop the unused folder message from the Vietnamese catalog (#2595)
Vietnamese arrived on main after I removed ui.save_mesh_temp_failed from the other catalogs, so the check that every translated id exists in English failed on it. Nothing uses the message any more.
* Say so when a started slicer can not be found again (#2595)
After Open in Slicer had started a slicer on its own, a failure to look it up again to send the file just returned, so the status line stayed on Starting and then nothing. It now says the slicer was started, that it can not be found to send the file, and where the file is saved. A test covers it.
* Say why a failed busctl ends the wait (#2595)
If busctl fails partway through a wait, the wait stops and the file is sent, the same as when busctl can not be run at all. That is on purpose: a file sent too early is dropped quietly and costs one more send. A comment in wait_for_ready says so.
* Keep the readiness wait inside its limit (#2595)
wait_until asked the probe before checking the deadline, including right after a sleep, so a yes could be accepted after the limit, and busctl ran with no timeout, so one look could outlast the whole wait. The deadline is checked first now, the probe is told how much time is left, and busctl is given at most two seconds (less near the end of a wait) and is killed after that. A hung busctl counts as not telling, which sends the file as before. This covers the first look on the UI thread too. Tests cover a slow probe, the shrinking time left, and a fake busctl that hangs, answers and fails.
* Send the file that was already written once the slicer is ready (#2595)
When the wait for a started slicer ended, the UI ran file.open_in_slicer a second time. That exported the design again on the UI thread, from whatever the session looked like by then, and could write a numbered copy while the timeout message still named the first file. The wait now keeps the slicer it found and the answer of the first call, and opens the file that was written. A test changes the design during the wait and shows the same file is sent, is not written again, no command runs, and no second file appears.
* Send with the single-instance flag after starting a slicer (#2595)
After Open in Slicer has started a launcher that needs a start first, the file has to reach the instance that is now running, so the send that follows a start carries --single-instance whatever the preference says. A normal send, to a slicer that was already up, still follows the preference. A comment in both places says why. The test uses a plain launcher command with the preference off: the normal send has no flag and the send after a start has it.
* Find the remembered slicer again after an uninstall (#2595)
With two installs the remembered key is prusa#prusaslicer. Remove the other install and the lone one is plain prusa, which matched neither the exact key nor the plain-key fallback, so the picker fell back to the first slicer in the list, maybe a different one. The key is now also matched by the part before the #, so the same slicer is pre-selected. A test covers the lone install, a plain key with two installs, an exact key, and that another slicer is never picked.
* Say a second click did not send anything while a slicer starts (#2595)
While a started slicer is being waited for, another click showed Starting <the first slicer> even when it had picked another slicer or other bodies. It now says it is still waiting for that slicer and that nothing else was sent. The test checks the wording and that it names the slicer being waited for.
* Label macOS installs by their application (#2595)
An open -a command resolved its program to /usr/bin/open, so every application on a Mac was labelled system. Labels for open commands now come from the .app path: /Applications is system and a folder under home is shown as ~/Applications. A test with two PrusaSlicer.app bundles and a made-up home checks both labels and that the ids differ.
* Pick the same place for a program found twice every time (#2595)
When two desktop files, or a desktop file and PATH, pointed at one program, the first one read was kept and its file name went into the id, so the ids could change with the order the folders were read in even though nothing was installed or removed. The place that sorts first now speaks for the program. A test with two aliases and a second install lists them with the folders in both orders and gets the same ids and commands.
* Treat flatpak run spelled two ways as one install (#2595)
flatpak run ... and /path/to/flatpak run ... with the same arguments had different install keys, so one invocation could be listed twice. The launcher is now compared by its file name and the arguments are kept, so both give the same key, while a different branch is still its own install. The test uses a real launcher file so the full-path entry is not skipped as stale.
* Say plainly that a late yes is accepted on purpose (#2595)
The comment on wait_until said a slow probe could not accept a yes that came too late. That was not what the code does: no probe is started after the limit, but one already running may finish and its yes is taken, because the file should still go. The comment now says so, with the bound: the limit plus that last probe, which in production is given the time left and so ends by the limit. A test has a probe that runs past the limit and says yes, and checks it is accepted and the wait stays within limit plus the probe time, and that a probe that says no is not followed by another.
* Give reading busctl the same deadline as running it (#2595)
After busctl exited, the output was read with a join that could wait forever if a process it left behind kept the pipe open. The reader now hands the text back over a channel and the wait uses the time that is left of the same limit, and gives up with nothing otherwise. The polling sleep no longer overshoots the time left either. A test uses a fake busctl that backgrounds a sleep with its output inherited and exits at once: the call returns within its limit.
* Tell two different flatpak launchers apart (#2595)
Comparing only the file name of the launcher made /opt/tool-a/flatpak run ... and /usr/bin/flatpak run ... with the same arguments one install even when they are different programs. The launcher is now found the way any program is, through PATH and with links followed, and its name stands in only where it can not be found. The test has flatpak on PATH and the same file by its path as one install, a different executable of the same name as a second, a different branch as a third, and the fallback when there is no PATH.
* Keep waiting when busctl is slow, send only when it is missing or fails (#2595)
Since the busctl timeout, a slow busctl came back as "can not tell", and that was read as send now. One slow look, likely just when a cold PrusaSlicer 3.0 is starting under load, ended the wait and sent the file early, which gets dropped. The look now says why it has nothing: slow means not up yet, so the wait goes on to its limit and then reports that the slicer did not respond; a missing or failing busctl still means it can not be told and the file is sent. The comments name only those two cases. Tests use fake busctl programs: one that hangs keeps the wait going to the limit and times out, a missing one sends at once, one that says not up and then fails sends, and one that names the slicer is ready.
* Do not open an empty path when the answer had no file (#2595)
If the answer kept for a started slicer had no path, the send ran open with an empty string. The tick now says there is no file to send and starts nothing. The test gives a wait with no path, checks the error line, and that the fake slicer was never run.
* Drop the unused folder message from the Danish catalog (#2595)
Danish arrived on main with ui.save_mesh_temp_failed, which this branch removed from English because nothing uses it any more, so the check that every translated id exists in English failed. The entry is gone from the Danish catalog too.
* Always start the 3.0 launcher with the single-instance flag (#2595)
A hand run showed that starting the PrusaSlicer 3.0 launcher with no file while a 3.0 is already running forwards to it and exits when the flag is there, but opens a second full window when it is not. A slow first look at the bus can start the launcher twice, and with the preference off the flag was stripped. A slicer that has to be started first now keeps the flag on that start whatever the preference says, the same reasoning as the send after a start; when nothing runs the flag does no harm. A normal send with the preference off still has none. The test covers the launcher entry, a bare launcher command, a normal send and a slicer that is not on the table.
* Swap --single-instance-on-url where it does not hand the file over (#2595)
A hand run on the PrusaSlicer 2.9.6 Flatpak showed that its entry's --single-instance-on-url does not forward a file to the running instance: every send opened a new one, three after a cold start and two sends. With plain --single-instance the second process finds the first, forwards, and exits. So when a command carries --single-instance-on-url and the slicer is not on the wait table, that is, not the 3.0 launcher, it is replaced by --single-instance, with the file markers handled as before, when the preference is on, and stripped when it is off. On the 3.0 launcher the flag does forward and stays. The comment on the rule says why. A test uses the real 2.9.6 and 3.0 Flatpak command lines.
* Cut long slicer names to fit the picker (#2595)
With installs listed on their own, a name like PrusaSlicer (Flatpak, master) is wider than its row. The list painted it with no limit, so it ran out of the popup, and the closed selector wrapped it onto a second line. The list now lays the name out on one line that ends in an ellipsis when it does not fit, the selector truncates, and the full name is in the tooltip when it was cut (for a program the user added, ahead of the delete hint). A test checks that a long name is cut to one line within the width and a short one is left alone.
* Keep the 3.0 launcher's flag on a send whatever the preference says (#2595)
A hand run with the single-instance preference off showed the 3.0 launcher needs its flag on a send too, not only on a start: a cold start with a file and no flag loaded nothing, and a send to a running 3.0 left a launcher process that never forwarded. A slicer on the wait table now gets the flag on every launch, as it already did when started alone. The doc comment says why. The preference text, in English, Japanese and Chinese, and the guide now say that PrusaSlicer 3.0 is the exception to turning this off. The tests that took the flag away with the preference off now expect it, for the launcher entry, a bare launcher command and the 3.0 Flatpak line; the test of the send in the UI checks the first send carries it.
* Keep the install-flag swap to PrusaSlicer, found by what it is (#2595)
The swap of --single-instance-on-url for --single-instance ran for any program whose command had the flag, so another program with that flag would have been rewritten. It is now a PrusaSlicer rule: it applies to a slicer recognised as PrusaSlicer by its identity, its id or, for a program the user picked, the name it goes by, never by a version number. The single-instance table uses the same identity check. A test keeps the flag of a program that is not PrusaSlicer, strips it when the preference is off, and swaps it for a picked program named PrusaSlicer.
* Do not promise a hard bound in the wait comment (#2595)
The comment on wait_until said the production probe ends by the limit. Its clock starts after the process is spawned, and killing and reaping the process and scheduling are not bounded by it, so it is a polling and read budget with process overhead on top. The comment says that. Comment only.
* Tell PrusaSlicer 2 from 3 by the installed version (#2595)
PrusaSlicer 2 and 3 need different handling, and the rules had to guess from the command line. Each install of PrusaSlicer now carries its major version, read when it is detected from the release at the top of its appstream metadata and never by running the program: a Flatpak's own files (the branch the command names, else the current one), or the metainfo file beside a desktop entry. PrusaSlicer older than 3 is sent the file with --single-instance in place of --single-instance-on-url, which does not hand a file over there, and is not waited for. PrusaSlicer 3 and newer keeps the launcher's flag, is started on its own, waited for, and sent the file. When the version can not be read, the command decides as before: the slic3r-app-launcher launcher counts as 3, and any other entry is left as it is. A program that is not PrusaSlicer is untouched whatever its flags. Tests use metadata fixtures, no Flatpak: 2.9.6, 3.0.0-alpha10, a branch that is not installed, a system package, an unreadable version with and without the launcher, and a non-PrusaSlicer program with the same flag.
* Make the busctl wait tests steady under load (#2595)
One of them failed once in a full run at its first assertion and passed alone. A script that has only just been written can fail to start (text file busy) while another test forks, and that reads as a busctl that is missing. The fake programs are now started once, patiently, with a flag they answer at once, before a test relies on them; a hanging one uses exec so it is killed cleanly. The time bounds that could trip on a busy machine are several seconds now, and the checks are on outcomes: not ready and keeps waiting, send now, and the number of looks. Thirty runs alone, thirty of the whole slicers filter and thirty of the busctl tests under eight busy loops all pass.
* Say what the busctl reader thread leaves behind (#2595)
The thread that reads busctl output is left to end when the pipe closes. A busctl that answered but left a process holding its stdout reads as slow, and every poll of such a bus can leave one blocked reader behind until that process lets go. A comment next to the thread says so. Comment only.
* Make Save as mesh a plain export (#2595)
Save as mesh asked for the bodies and the format, and could also send the file to a slicer with a checkbox and a slicer list. Sending now has its own command, so the checkbox, the list and the Send label are gone from this dialog: bodies, format, a file dialog, Save. The two settings that only the checkbox used, save_mesh_send and save_mesh_slicer, are no longer kept; a settings file that still has them loads, and they are ignored. The strings only they used are gone from the catalogs. The slicer list stays in the dialog of Open in Slicer. file.save_mesh keeps its slicer parameter for scripts. The guide describes the two as separate.
* Rename Open in Slicer to Send to Slicer (#2595)
The command sends a file to a slicer rather than opening one, and a later Send to App can share the name. File menu, Mesh tab, right-click menus, the dialog button, status lines, the guide and the English, Japanese and Chinese strings now say Send to Slicer, and the command id file.open_in_slicer is file.send_to_slicer. It is new in this change, so there is no alias. The remembered slicer is stored as send_slicer, and the question the agent chat asks says send to. Module and type names stay as they are.
* Put the send targets in one table and one send pipeline (#2595)
The known slicers, the list of slicers with a single-instance flag and the rule for slicers that must be started and waited for were three separate things. They are one table of targets now: id, label, kind, names and program files, accepted formats and the default, how the file is handed over, the single-instance flag, the version from which PrusaSlicer's on-url flag hands a file over, and the wait-for-ready rule. Programs that are not in the table are slicers with the same defaults. The kind is slicer, the only one so far; its comment says it is where a later Send to App would plug in. The send itself is one function: write the bodies, find the target, start it and wait if its rule says so, hand the file over, report. Detection and resolution take a kind. Nothing changes for what already worked.
file.save_mesh keeps its slicer parameter, now through the same function, with its export arguments and result unchanged: a cold PrusaSlicer 3 is started and waited for before the file goes, and it accepts the program path of a detected slicer as file.slicers lists it (file.slicers gives it as program). A path is accepted only when it is the program of exactly one detected target, and that target's own command and arguments run; a launcher several entries share, such as /usr/bin/flatpak, is refused as ambiguous, and any path nobody listed or chose is refused. The parameter and the code say it may be deprecated later in favour of file.send_to_slicer or a Send to App workflow, which is a possibility and not a plan.
Tests go through file.save_mesh itself for the two reported failures, a listed AppImage path and a cold PrusaSlicer 3 that is waited for, with the slicers and the busctl made up; and they check that the result and the file are what an export without a slicer gives, and that a path that is not listed, or is shared by two entries, writes nothing. The pipeline has its own tests: a cold launcher waited for, one already up, wait return, and one that never comes up. A test of save_mesh that shared a file name with another is given its own.
* Keep an AppImage's id through an upgrade (#2595)
When a slicer has several installs, each gets an id made from where it was found. For an AppImage that was its file name, which carries the version, so upgrading it changed the id and the remembered choice missed. The id is now made of the slicer's id, the fact that it is an AppImage and a short hash of its folder, so a new version that replaces the old one in the same folder keeps the id, and the same AppImage in another folder is its own install. Two versions kept side by side in one folder are two installs: while both are there, their file names are added to their ids to tell them apart.
A remembered id is found again only when it names exactly one install. An id that was one AppImage and is now two versions, or a plain id that is now several installs, is treated as not remembered, so the picker starts from the first entry as on a first run and does not quietly pick another install; a single install left of the same slicer is still found, as before. Installs that are not AppImages are told apart as before. An id from before this change no longer matches. Tests cover an upgrade, two versions side by side, a second folder, and the picker's side of each case.
* Add Open with EasyPrint to Send to Slicer
EasyPrint is Printables' slicer in the browser. It's a plain text row in
the Send to Slicer dialog, backed by a new file.open_in_easyprint
command. The command exports the bodies as a 3MF (or STL), hands them to
the host to upload to slicer.printables.com/import, and the app opens the
link that comes back. The desktop app uploads with ureq and the browser
build with fetch, so this is the one send target that works on the web.
The model leaves the machine, so the command refuses without
consent: true, the dialog asks once and remembers, and an AI agent's
request asks first. A model over 100 MB is refused before anything is
sent, and a failed upload shows as an error with the reason.
The chat's privacy note said SolidCraft sends nothing anywhere, which
isn't true any more, so it now says that about the chat. New strings are
in every catalog.
* Place Send to Slicer on the Insert Files panel
* claim: #6170
* Move: after picking a body, go on to From, Point or Axis (#6170)
In Point to Point, Point to Position and Rotate, once a body is picked
and the next input is empty, the dialog goes on to it, so the next click
picks the point or axis instead of toggling another body. Free Move
stays on Bodies: its Pivot is optional.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6070
* Sketch: a changed length dimension keeps its start point, so a side grows from its first corner (#6070)
Changing a dimension's value re-solved its sketch as the smallest move from
the stored positions, which splits the change between both ends: a
rectangle side set from 40 to 60 mm grew 10 mm each way. parameters.change
now first solves a sketch whose length dimension (distance, horizontal or
vertical distance, line length) the parameter drives with that
dimension's first point held, and stores the result, so evaluation has
nothing left to move. Where holding it can't solve (a symmetry moves both
ends), evaluation solves as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5099
* Browser: a feature's eye shows or hides the bodies it made (#5099)
A row in the Browser's Features folder has an eye when the feature made
bodies that are in the design now (a patch, a box, a pattern's copies;
not a fillet, which changes bodies). It runs browser.visibility with the
new features: [id or name] (and path: for one placement of a component
placed more than once), which hides or shows those bodies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A path a design names (a linked design, a sketch text's font file) now
goes through one rule before any file call: a network location is
reached only once its server is allowed, a device path never. Update
Links and Open Linked Design ask first (Allow once / Always allow this
server / Cancel); Preferences > Design lists the allowed servers and
can allow every network location. component.links reports such a
link's found as unknown with a reason, without looking for it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A homemade 3D input device's driver app (MAKMOUSE) listens on a loopback
port; with Preferences > Navigation > "Navigate with a device app on this
computer" on (off by default), the desktop app connects, says hello and
reads JSON lines: axis motion (orbit, pan or zoom by mode), zoom steps and
standard views. The motion goes through navigation::device like a 3D
mouse's, only in the focused window; a HID device the app names is left
to it. Documented in docs/3d-mouse.md.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat: add personal Hole parameter presets
* feat(hole): prompt for preset names and recall changed selections
Ask for a name only on Save new or Rename in the kit confirmation. Keep Enter and Escape inside the prompt, preserve cancelled drafts, recall only changed selections, and retain independent recessed-hole bottom choices.
* feat(hole): preserve thread direction and partial depth in presets
Carry Left/Partial, explicit source units and Custom draft guards through personal preset capture and recall. Preserve legacy Right/Full/flat defaults and reject unknown options before changing the form. This production bridge depends on the handedness and graphical-depth changes inherited below it.
* docs(hole): describe supported preset thread options
* feat(ui): save modified hole presets in place
* fix(presets): retain reference placement and current locale contracts
* test(ui): borrow restored preset library during serialization
* test(presets): preserve modal focus with viewport focus restoration
* Use UI kit controls for hole preset selection
* Themes: Clay (light) and Clay Dark, warm paper and charcoal with a terracotta accent
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Clay: on-accent text a touch lighter for 4.5:1 on the accent
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6724
* Extrude: the picked region is the one extruded again (#6724)
Since #5530 (#5302) every index or point pick grew to the whole drawn
profile that reference geometry cuts, so a lens or crescent clicked in
Extrude, where the face's projected outline cuts a circle, extruded the
whole circle while the dialog highlighted and counted one region.
Dialogs now send exact_regions: true with their profile picks, and the
command keeps each pick to the region it names. Picks without the flag
(scripts, agents, the CLI) still take the whole drawn profile (#5302).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5345
* Select Other: list what is under the pointer front to back, one body at a time (#5345)
The list was sorted by kind first, so on stacked bodies it showed every
body's face, then every body, mixed across depth. Now each body's rows
stay together (vertices, edges, face, then the body), bodies ordered by
their nearest hit along the pick ray; an edge counts at its point under
the pointer. Sketch overlays stay first, profiles, planes and axes last.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4784
* Part properties: one table of keys, each with its scope, check and label (#4784)
SolidCraft's part-property keys (project, part number, revision, author,
organization, description, licence) were declared once but listed again
by hand in each command, error, dialog and window. They are now one
table, metadata::KEYS: each row has the key, where it may be set
(Scope: the design, a component or both), how its value is checked and
the message id of its label. A new key is one row.
Metadata::set is told whose metadata it changes (Owner::Design or
Owner::Component), and a key is taken where its scope covers that
owner. is_well_known, both set commands, document.metadata,
component.metadata, the refusals, the Design Properties dialog and the
Bill of Materials window read the table; the hand-written lists are
gone (WELL_KNOWN, the label match whose fallback named any unknown key
"License", the window's three labels). metadata::BOM_KEYS names, in
column order, the keys a bill of materials lists for each part:
component.metadata returns them, component.metadata.set takes each as a
parameter of its own and the window edits them; a test keeps each one a
row of the table that a component takes. Free properties use the keys'
text rule (metadata::check_text) instead of a copy of it. The params
docs point to document.metadata's properties instead of repeating the
keys.
Every key accepted before still is, on the design and on a component.
Two things change:
- a refused key or a full metadata on a component says "part property"
("`bogus` is not a part property: …", "part properties are limited
to …"), not "design property";
- changing the design's own properties no longer evaluates the model.
The fields that change no geometry are named once,
Document::with_part_properties_of: the design's metadata, and each
component's metadata and free properties. It compares them first and
copies the design only when one differs, as before.
The shortcut is for a session's own edits only. A command that gives
the session another design (doc.open, file.new, doc.recover: through
Session::replace_design, which now counts them) is refreshed in full,
however little the new design differs from the one before, so its
joints are solved and its occurrence state pruned. Before, opening a
design that differed from the open one only in render settings or
component properties skipped that; with the design's properties in the
list it would have for two revisions of one part too.
Tests: the table (one row per key, the dialog's order); the bill of
materials' keys (rows a component takes, in column order); each scope
on each owner, and refusals that name the owner (the empty key, an
unknown key, a 256-byte key shown cut); the text check at 4096 bytes,
line breaks, tabs and control characters; hostile keys and values
refused without a change; only-part-property changes in each scope,
and changes beside them (units, a component's name, an added, removed
or swapped component); document.metadata.set, remove, undo and redo
evaluate nothing; opening a revision that differs only in its author
solves its joint; the component wording, the shortcuts and the limits;
the dialog's and the window's labels from the table.
What the tests can't reach: every row is Scope::Both, so a known key
refused for its owner (the wrong-owner branch of check) can't happen
yet. It becomes live with the first single-scope row, bom_type, a
component-only key.
Left for later (not this topic):
- Metadata::others() ignores scope, so a component-only key stored on
the design would be neither a dialog field nor listed under other
tools' data. It matters from the first single-scope key (bom_type).
- assembly.bom's rows, its CSV columns and the window's grid headers
still name the three keys by hand; the bill of materials module's
rewrite will read BOM_KEYS.
- The bill of materials types (P1, #5107, into drawings) still use
WELL_KNOWN and the two-argument set: once this is on main, bom_type
is rebuilt on it as one KEYS row with Scope::Component, set through
Owner::Component.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5497
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Sketch pattern: a spacing arrow dragged past zero gives a negative spacing (#5497)
The drag arrow floored an unsigned value at a small positive number, and the
rectangular sketch pattern's two spacing arrows were unsigned, although the
command takes a negative spacing and typing one works. Both arrows are signed
now, like the Extrude, Move and offset ones.
The test drags each arrow back past its start: without the change the first
one stops at 0.001.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Drag arrows: the solid pattern spacing and one-sided sketch offset go negative too (#5497)
The solid rectangular pattern's Spacing arrow and the sketch Offset distance
arrow were unsigned, so a drag back past the start stopped at a small positive
value, although both commands take a negative value and typing one works. Both
are signed now. A symmetric offset uses only the distance's size, so its arrow
still stops at the curve.
Tests: the solid pattern arrow dragged 10 mm back gives Spacing -10 and the
copies on the negative side (without the change: 0.001); the offset arrow
dragged across its circle gives -1 mm and the same circle as typing -1 mm
(without the change: 0.001), and a symmetric offset stays positive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Release: sign the Windows binaries and MSI through SignPath Foundation
When the SIGNPATH_API_TOKEN secret exists, each Windows job sends the
three .exe files to SignPath before packaging, then the MSI built from
them, using the GitHub artifact flow from SignPath's docs. The policy
is test-signing unless the signpath_signing_policy dispatch input or
the SIGNPATH_SIGNING_POLICY repository variable says otherwise. Without
the secret the jobs build unsigned exactly as before.
package.ps1 gains -Phase stage|package so signing can sit between the
build and the MSI; the artifact configuration to paste into SignPath is
packaging/windows/signpath-artifact-configuration.xml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* SignPath: drop the description attributes the Foundation plan doesn't allow
SignPath Foundation projects get an automatic signature description (its watermark);
the artifact configuration may not override it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Double-click a shown sketch dimension in the model view to edit its value (#7079)
A finished sketch's dimensions shown in the model view (Show Dimension) now take a
double-click: the same inline value box as sketch editing opens at the text, Enter
changes the dimension's own expression through parameters.change (one undo step), Esc
cancels. The sketch is not edited and the view does not move. Driven dimensions stay
read-only with a status hint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control channel: ui.selection lists the shown dimensions of finished sketches
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test: a shown dimension that breaks a later fillet reports it, and Undo restores both
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #1457
* Agent chat: tool calls read as summaries, raw JSON behind Details (#1457)
Each tool-call card now says what the call did, built from the command's
translated label, its main parameters (lengths in the design's units) and the
bodies the result names: "Extrude Sketch1 by 10 mm", "Box 40 x 30 x 20 mm,
then Fillet 4 edges (r 4 mm)", "Measured Block: ...", "Saved block.solidcraft".
Calls it can't put in words keep the agent's title. The raw arguments (now
kept from the in-app client and ACP's rawInput) and the result sit under a
folded "Details".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Agent chat cards: show file names, never paths; temp files not named
ui.screenshot reads "Screenshot"; save/export/open and other tools' titles
show only the file name. The full text stays under Details and in the tooltip.
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>
* claim: #5681
* sketch: exact circular arcs on drawn 3D wires (#5681)
Add WireArc (centre, unit normal, radius, start/end) as an optional analytic
form on drawn 3D sketch wires, with arc_from_3_points, arc_sweep and
sagitta-bounded arc_polyline; add_drawn_arc_in stores the arc, its three
defining fit points and its samples; move_drawn_point re-derives the arc
atomically and refuses collinear points; validate_wires checks saved arcs.
* engine,ui: sketch.arc3d exact 3D arc command (#5681)
sketch.arc3d draws an exact circular arc through three world points in a 3D
sketch, stored analytically beside its fit points and samples; sketch.move3d
rebuilds it, sketch.inspect reports centre/radius/sweep; placement mapping
(map_wire, now shared with the engine's world state) transforms the arc.
One pending design-format file records the new field. The viewport's 3-Point
Arc tool draws a 3D arc when clicked off the sketch plane, with a live arc
preview; the SKETCH panel offers sketch.arc3d.
* ui,engine: 3D Arc button starts an interactive tool (#5681)
sketch.arc3d now maps to a Draw(3) tool handled by sketch3d: three world clicks (start, end, through - same order as the 2D arc tool) run the command, with the live arc preview and snapping. The command is only enabled while a 3D sketch is open, so the toolbar never offers a tool that could only fail. Locale labels for the command.
* ui: cargo fmt
---------
Co-authored-by: RayGalt <RayGalt@pm.me>
* claim: #1383
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport tests: the hover pixel test keeps its pictures when it fails (#1383)
The Metal hover pixel test failed once in a loaded full-suite run and has
not been reproduced since. On failure it now saves the plain and hovered
GPU and CPU frames and a mask of the pixels that do not light, and names
the folder in its message. The assertion is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4384
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: shared module, with Rest as the first tool
The Rest handles spike becomes a shared module, place_gizmo, so other features placed on a face can
use the same marker and knob. The mechanics (crosshair with its three states, the grab offset, the
footprint while dragging, the turn knob) live in place_gizmo.rs. The handles own the pointer through
canvas::owns_pointer, as the move triad does, so navigation and Select Other's press-and-hold leave
them alone. Each tool is one file under place_gizmo/ that gives a Spec: which dialogs it serves,
which commands' Position input also takes sketch points, where the place is, how the pointer
re-places it, its footprint and its turn, if any. The module lists the tools one per line, so tools
added on separate branches each add one `mod` line and one list line. Rest is the first.
The handles are always on; the preference the Rest spike had is gone. The POINTS accept flag on a
Position input is granted from the tools' Specs instead of a Rest-only arm in the plastic dialog's
start. Handles are published as `rest:position` and `rest:rotate` as before.
Seen straight down, the dialog's height arrow collapses onto the place and its value box sits just
right of it, where it took the press meant for a knob pointing that way. The knob now slides out
along its ray until it is clear of the box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Placement handles: Boss
The marker moves a boss across the model, also from Edit Feature, and its Position input takes
sketch points while the handles are on, so a screw boss can be dragged onto the centre of a
circle drawn for it. A boss is round, so it gets no knob; the footprint while dragging is its
outer circle. Published as `boss:position`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Placement handles: Snap Fit
The marker moves a snap fit across the model and its Position input takes sketch points while
the handles are on, so the arm can be put where a sketch says (#3423). The knob points the
catch: it turns between the four directions the Catch Direction combo offers for the face, so
the knob and the combo always agree; the command keeps only those four, so there is no free
turn. The footprint while dragging is the arm's foot, its thickness behind the place and its
width across. Published as `snap_fit:position` and `snap_fit:rotate`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Placement handles: the look in one file
Everything that draws the handles (the crosshair and its three eased states, the knob's ball, the
cased lines, the angle label, the colours) moves from place_gizmo.rs into place_gizmo/look.rs, so a
restyle touches one file and the mechanics (hit radius, drags, knob geometry) stay where they are.
No change in behaviour.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: behind a Design preference, off by default
The handles sit behind Preferences > Design > Placement > Placement handles (`placement_handles`,
also through `ui.prefs`), off by default as the hole placement work in #2912 is: a Rest, Boss or
Snap Fit is then placed by its click alone, its Position input takes faces only, and no handle is
drawn or published. The tests switch the preference on; one checks the default.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the tests hide the origin through set_origin_shown, as main does since #1849
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the move marker as designed in the prototype, drawn with theme tokens
The marker is a dot with four arms to the screen cardinals: short and dotted at rest, longer with open arrowheads when pointed at, solid while dragging, each state growing out of the previous one (360 ms). Sizes are the prototype's. Its core is the icon highlight and its casing the marker halo, the tokens main's hole handle uses, so the literal allowance goes. The tip shows while pointing, not during a drag.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the dialog's drag arrow stands above the move marker
With the handles on, the Height arrow started at the marker's centre and ran through its upper arm, head on arm tip. The arrow now keeps the marker's full reach and a gap free at the base, its shortest length grows by as much, and the base dot is left to the marker. Other dialogs are unchanged (clearance zero).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: a turn ring on the face replaces the knob
The ring lies on the face about the move marker's centre, drawn in perspective, a fixed 65 px on screen like the move triad's arrows (it is a control of something on the screen, not a measure of the model). At rest it is a short dotted arc with an open chevron grip just outside; pointed at, the dotted circle opens from the grip both ways round and the grip slides in and fills; while turning, a solid arc around the grip. Dragging the grip turns the feature as the knob did, through the same handle name, so the harness tests stand.
The floating value box yields: with a handle's centre under it, it moves to the other side of the arrow's tip or above it, since seen straight down the box sat exactly where the grip is. A handle the arrow's stem crosses keeps the pointer.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: a compass while turning, and the turn pulled onto the face's compass points
Four ticks just outside the ring at the face's in-face world directions (the ones the Snap Fit combo names, +X, −Y, …) appear while turning and stay put, so you can see how far you have gone. A Rest's turn is pulled onto one of them within 8°, otherwise it steps by 5° as before; Alt or Ctrl frees it. The readout beside the grip names the point it sits on ("90° · +X") and keeps clear of the ring.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the footprint in the preview's blue, the idle handle steps back
The footprint drawn while the marker drags is a filled shape in the preview's own blue with a thin darker edge, not a white outline, so white stays reserved for what can be grabbed. While one handle is dragged the other fades to a quarter and comes back when the drag ends.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the ring in the preference text, the docs and the tests' wording
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the look as tuned in the app
Marker: drawn to the redrawn design (dot r 3.9 pt, two dashes per arm, a filled head 8.7 × 7.1 pt whose base is where the outer dash began); pointed at, the outer dash becomes the head (its far end narrows to a point, its near end widens); dragging runs the inner dash on into the head as a shaft. Every casing is 0.4 pt outside the edge, for lines, heads and the dot alike.
Ring: the grip and the compass lie in the ring's plane like the circle, so the grip is never skewed against it; the grip is a thick chevron with a hole that flattens into the base as the grip slides onto the ring, and fills; the dotted circle stays while turning, with the solid arc laid over it; opening eases in and takes 0.3 s, leaving is a 0.15 s fade over the resting ring, not the sweep backwards; a drag keeps the angular gap of the press, so the grip does not jump onto the pointer; the compass ticks and the readout's point name are in their axis colours, and the grip takes the axis colour (eased) while aligned with one, with that point's tick left out; free turning keeps a hair of pull (0.5°) so an axis can still be hit; the readout wraps to a half turn.
Value box: with a ring it keeps out of the circle the ring, grip, compass and readout sweep, one place per view, nothing moves when a turn starts; also when the value does not evaluate and there is no arrow.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the readout shows a half turn as 180°, not −180°
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the motions in one place
The animations the handles grew (a factor eased in and out each way on its own time and easing, a handle stepping back while the other works, a colour that comes and goes from the last one it was on, the gap a press keeps for its drag, an offset that slides instead of jumping) move out of place_gizmo into motion.rs, so the next manipulator reuses them. The look's timings become Transitions in look.rs. No change in behaviour: the contact sheet before and after is identical, the harness tests stand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Placement handles: the axis colours as main names them since #4430
The compass and the aligned grip take the one set of X, Y and Z colours (axis_x, axis_y, axis_z); the Move gizmo's own array is gone.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* claim: #3133
* Apply complete feature transactions off the window thread
Evaluate dependent history and prepare reference and GPU display caches before accepting a guarded result. Preserve the evaluated original model for safe editor cancellation and journal only accepted commands.
* fix(ui): preserve original marker across sketch-to-feature edits
* test(ui): verify edit-sketch cleanup and guard Unix link fixture
* test(ui): fix texture disposal and unreachable assertion in feature tests
* fix(build): compile CLI link inspection only on Unix
While an agent's batch computes off the window thread (#1989), the chip reads
N/M · <command>. It is centred on the navigation bar, just above it, and moves up
past any toast or panel it would cover.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Axes and points from circular edges and round faces
In 3D, a round edge or face stands for its circle wherever a command
takes an axis or a point.
Revolve, Surface Revolve, both Circular Pattern dialogs, Plane at Angle
and Move -> Rotate take a circular edge (through its centre, along its
normal) or a cylinder, cone or torus face (its axis). The features keep
the pick as a model reference, so they follow the model when a parameter
moves it. Straight edges work as before; flat faces are refused.
Every 3D point input takes a circle's centre: construction points (which
follow the circle), To Object, patch points, hole placement, and Move's
pivot and points. Hovering shows the axis as a dashed construction line
or the centre as a star, and the dialog's chip names what the pick
stands for ("centre of edge 10", "axis of face 3").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Format the round-pick hover check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Selection chips: name a round pick as a circle centre or axis
The chip has room for little more than the body and a word or two:
"Shaft · circle centre", "Shaft · circle axis", "Shaft · face 2 axis",
and "Shaft · point" for a point of the model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move: a circle's centre in a placed component is where the component shows it
Move's pivot and points read a picked circle from the model in its
component's own frame, while the pick is in the world. They now read it
from the world state (and, editing a Move, from the state before it,
placed the same way), as the viewport and the other round picks do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Plane at Angle on a planar face about a model edge: test that it follows both
Upstream #1956 made a planar face a base for Plane at Angle; this branch makes a model edge an
axis. The rebase keeps both in one PlaneRef::AtAngle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #1397
* External STEP corpus: nightly import checks on real-world files (#1397)
tests/external-corpus.toml pins 20 real-world NIST PMI STEP files (AP203
geometry-only and AP242 editions from several vendor exporters, public
domain) by archive and per-file SHA-256 with the licence recorded.
cargo xtask external-corpus downloads each source once into the gitignored
corpus/external/, verifies every hash, imports each file headless
(file.insert_step + inspect.measure) and checks body count, total volume
(0.1 %) and area (0.5 %) within tolerances and time and memory budgets.
A file whose upstream hash changed is reported as 'source changed', never
as our failure. --bless "why" re-records expectations in the manifest with
a dated reason. The External corpus workflow runs it nightly.
assets: skip corpus/ when scanning for attribution (downloaded case data
is never committed).
---------
Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
The blue, green, red scale's lightness rose and then fell (L* 44 up to
72 and back to 50), so a flat face and a tight radius looked alike with
hue lost, and its upper part merged for a red-green loss. The stops are
now dark blue, a middle grey-gold and light yellow in one straight
blend, as measured in the issue: L* rises the whole way and every
quarter step stays clear for normal, protan, deutan and tritan vision.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Frames: a Weldments ribbon tab (#4003)
- WELDMENTS joins the ribbon after SHEET METAL and is built the same way:
Sketch (New Sketch), Create (Frame Member) and Configure (Parameters,
Configurations). Frame Member's home moves there from MODEL > Create.
- Always shown, as a frame starts from a sketch of its lines. It selects
itself and takes the contextual style when the selected body (or the last
body of the active component) is a frame member; sheet metal comes first.
- It uses the default accent until the palette has a colour for it.
- The tab label is in every catalog; docs/ribbon-layout.md, docs/frames.md
and the tab list in docs/control-protocol.md follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Frames: Weldments keeps the ordinary tab look (#4003)
Weldments still selects itself when a frame member is the active body, but
no longer takes the solid contextual fill or the 2 px accent line under the
tab row: selected, it looks like a selected Model tab. Only Sketch and Sheet
Metal (on a sheet body) are contextual.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: a number and its unit are one value (10 mm / 2 mm is 5)
A unit written after a number was multiplied in at the precedence of `*` and
`/`, so `10 mm / 2 mm` was (10 mm / 2) mm = 5 mm² and `90 deg / 1 rad` a value
in angle², reported as a unit error. The number now takes the unit after it
before `*` and `/` apply; a fraction of numbers before a unit stays one value
(`1/2 in` is half an inch), and an exponent stays a plain number (`2^3 mm`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: after a division, a fraction before a unit only when closed up
`width / 10 / 2 mm` read the `10 / 2 mm` as the fraction 5 mm and gave
width / (5 mm) = 8 instead of (width / 10) / 2 mm = 2. After a `/` the
fraction rule now needs the fraction written without spaces (`1 in / 1/2 in`
is still 2); elsewhere `1/2 in` and `10 / 2 mm` are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
?open= now also accepts an absolute path on the page's own origin, fetched
with the page's scheme, so a self-hosted setup that serves SolidCraft and
other tools under one origin can link a design on a LAN without a public
HTTPS address. Absolute URLs keep the HTTPS-only rule (a URL on the page's
own origin is also accepted), redirects must end on HTTPS or the same
origin, and protocol-relative, dot-segment, backslash and control-character
values are refused.
* Patterns: one before the feature it copies follows that feature's parameters (#6726)
A pattern or mirror of features made with the timeline rolled back comes
before its sources and replays them from their definitions. Its
fingerprint chained only the features before it and its own inputs, so a
change of a source's parameter left its result in place: the box grew,
its copy kept the old length, also after rolling forward.
The chain fingerprint of a pattern or mirror of features now also takes
the fingerprint of each feature it replays (and of their sources, for a
pattern of a pattern), wherever they are in the timeline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Patterns: a test for a later feature taking a deleted source's name (#6726)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Patterns: fingerprint the replayed sources with the document, as fingerprint now takes it (#6726)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #3538
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Updated AppImage takes the release's file name (#3538)
A versioned AppImage name (solidcraft-<version>-linux-<arch>.AppImage) is
replaced by the new release's name, so zsync-based tools keep finding the
update data. Other names are kept.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
A sketch dimension read Ø40.00, with nothing to tell millimetres from
inches, while the value inputs and the pointer's coordinates show the
unit. Length labels now end with the design's unit (Ø40.00 mm, 1.000 in,
(25.00 mm) for a driven one); angles keep their degree sign.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md drops from 16.8k to 3.7k characters: the hard rules, each naming the
check that enforces it, and a table of what to read for each kind of work.
The rest moves into eleven new skills; the five existing ones stay.
cargo xtask skills fails when AGENTS.md or a SKILL.md cites a path, an xtask
command or a skill that does not exist, when the router table and skills/
disagree, or when the router or a new skill grows past its size cap. It runs
in cargo xtask ci and the repository checks job.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
With a section shown, the marking pass decided where the cap shows (the stencil) and wrote the
depth everything after it tests against. #1768 gave it the faces' FACE_BIAS for the second job,
so edges draw over their own faces (#1434). For the first job its slope part is unfair: next to an
edge where a back face seen through the cut meets a front face, the front face won a band about a
pixel wide although the back face is nearer. The cap skipped that band, and the edge line showed
through the cut.
#6363 marked those samples again from the back faces, pulled toward the eye as far as FACE_BIAS
pushes. That closes the gaps beside the edges, but the pull also lets back faces win wherever they
lie within the push of a front face, along every body's far silhouettes, and the cap hatches that
band. On bherbruck's block, the band away from the cut went from 58 px orthographic and 534 px
perspective before #6363 to 618 and 780 with it. In the app, his filleted hub showed it in every
frame of a 36-step orbit in both projections.
This replaces that pass. While a section is shown the opaque model now draws in four steps (`Cut`):
1. mark: the faces by their true depth (no push): colour, the stencil and depth;
2. a triangle over the viewport sets the depth back to the far plane, colour and stencil kept;
3. push: the faces again with FACE_BIAS, depth only, so the depth is what unsectioned frames have
and edges draw over their faces as before;
4. cap: depth only, as #6728 made it; the mark pass has drawn the cap's colour and hatch, as fs_solid
draws a back face seen through the cut.
The window pass's depth reset and this one are one helper, `depth_reset_pipeline`, for either
depth direction, and its passes are built by #6728's `solid_writes`. Unsectioned frames are unchanged. The CPU rasterizer already chose the cap by
true depth.
Tests (crates/gpu/tests/depth.rs), with the device's float depth and the 24-bit fallback:
- a_cut_hides_the_edges_behind_it (new): bherbruck's block cut at y = -16, home view, orthographic
and perspective. No edge shows through the cap, and away from the cut the picture is a box of
just the kept part's: 0 px orthographic and 1 perspective (main: 618 and 780).
- bherbruck's pocket and hub tests from #6363 are kept and pass (pocket 0 px; hub 1 and 2 px over
its twelve views, as with #6363's pass).
- an_edge_on_a_face_draws_whole_at_any_angle and
an_edge_just_behind_a_face_stays_hidden_on_a_large_design also run with a section shown.
In the app (safe mode, WARP), with 625 patterned cylinders cut by a section, the frame time is
that of main (#6363): 33.5 against 34.3 ms at the home view, 34.1 against 35.2 ms with the model
filling the view.
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6720
* Face pick by point: linear work budget, so bodies with many faces take a sketch (#6720)
A face picked by its point alone charged the whole mesh once per face, and
face_contains scanned every triangle for each candidate. A block with a
modelled thread (about 5 000 faces) went past the 2 000 000 limit. The
triangles are grouped by face once, each face's test reads only its own,
and the budget charges triangles + faces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #3812
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27
* Sketch Dimension: value box in the design's units (#3812)
A dimension added without a value restated what it measured as
"<value> mm", whatever the design's units. In an inch design the
Dimension tool's value box therefore opened at "25.4 mm" next to a
dimension whose text read 1.000 (inches).
Document::design_length writes a measured length in the design's units
("1 in"), with enough decimals to keep it to the micrometre, so a
dimension that restates the geometry still moves nothing. The Dimension
tool, AutoConstrain's dimensions and Toggle Driven (making a driven
dimension driving) use it. Millimetre designs are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27
* Move the design-units dimension tests into sketch_ux_tests (review)
The #3812 tests go in the existing sketch dimension test file rather than
a new file of their own (AGENTS.md, #3226).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The evaluation cache's 64 MB budget charged every cached feature result the whole model, though a
state shares every body it did not change with the state before it. A design whose model is a few
MB kept only its last few dozen results, so setting a parameter back recomputed what had dropped
out. Each result is now charged for the bodies and sketches it does not share with the state it
started from.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6550
* Crash reports: keep journaling through file.new and doc.open (#6550)
`Session::replace_design` swapped in the fresh session without its
`crash_journal` flag, and the app turns it back on only at its next frame.
So `file.new`/`doc.open` never recorded how they ended, the commands sent
after them in the same frame (a control-channel or MCP batch) were not
journaled, and a crash report then named `file.new` as the command in
flight. `replace_design` now keeps the flag, like the other session-wide
fields it keeps; only the design on screen has it set.
Test: `commands_right_after_a_new_design_are_journaled` (crash_tests.rs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6917
* Tests: the six timing assertions do not depend on the wall clock (#6917)
Six tests asserted on the wall clock. Their result depended on how loaded
the machine was and not only on the code under test: they passed on an idle
machine and failed when the test binaries ran beside each other, which is how
the suite runs (`cargo xtask test` starts every test binary at once).
CONTRIBUTING.md:196 says it: "Tests run beside each other on a busy machine,
so a test must not depend on wall-clock timing."
The change is confined to the six test files the issue names:
- geom: the tangent chain of a helix is a ratio (750 pieces against 3 000)
instead of a limit of 200 ms.
- io: the placement budget is spent inside the reader, so the clock is gone.
- ui-egui: the classification of many faces is a ratio (900 against 3 600)
instead of a limit of 10 s.
- engine: the clearance measurement drops its 30 s limit; the boolean that
gives up reports itself.
- engine: the fillet beside a modelled thread is a ratio (a 1 mm thread
against an 8 mm one) instead of a limit of 30 s.
- ui-egui: the window frame budget goes from 250 ms to 20 s, the limit #6072
gave its own probe.
The helper is the one #649 wrote, copied into the crates that need it, with
the noise of a run as a parameter.
* claim: #6558
* Shortcuts: Ctrl+Shift+S opens Save As in the workspace (#6558)
The Home page already opened Save As with Ctrl+Shift+S, but the design workspace matched Ctrl+S, so the chord saved over the current file instead of offering another one. The workspace now routes the chord to Save As, and the File menu shows that shortcut next to Save As, taken from the command registry.
* claim: #6600
Co-authored-by: Cursor <cursoragent@cursor.com>
* Save an imported mesh that only fits after compression (#6600)
The design file limit was applied to the mesh before Deflate, so a mesh
that had already loaded could not be saved or autosaved.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: validate and version large mesh saves (#6600)
---------
Co-authored-by: Colin Faulkingham <colinfaulkingham@Colins-Mac-mini.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
* claim: #5679
* Top-level cut and join reach bodies in sub-components (#5679)
An extrude, revolve or press-pull from a sketch on a face of a body in a
component inside the active one now cuts or joins that body, moved or
turned, in its own component, so every placement shows the change. The
automatic operation probes that body where it is shown. A linked part, or
a face on a further placement of a part placed more than once, is refused
with the reason instead of a silent no-op (#5678).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6423
* test: a copied body must keep the look of the body it came from
solid.move with copy: true leaves the copy without the appearance, the
material or the face looks of the source body, so it renders with the
default look. The tests fail for that reason: the copy has no entry in
appearances.bodies, in materials or in appearances.faces.
* fix: a copied body keeps the look of the body it came from (#6423)
A move with `copy: true` pushed a new body but left it with the default look, so the copy came out a different colour than the part it came from. The copy now carries the body's appearance and its material, as the component copy already did.
* test: keep the copy regression to the body look and the material (#6423)
* test: each copied body keeps the look and the material of its own source (#6423)
Two bodies with different appearances and different materials, copied in one solid.move: each copy must keep the look and the material of the body it came from.
Scope of the fix: the look of the BODY and its material are carried over to the copy. The per-face looks are NOT carried over: a face look is keyed by the point on the face, so the copy's faces would have to be matched and their points translated, which is a separate feature.
A drag that isn't live walks to the cursor in steps, each a held solve
on its own budget seeded on the last solved state. Past a failed step
the walk keeps trying every step (a stretch the geometry can't follow
can be followed by one it can), and each failing solve runs the
numerical solver to its limit: one drag in a 65-command sketch took
7.7-9 s, most of it in failing solves.
A failed step leaves the sketch as it was, so the steps after it all
start from the same state and none changes what the next one sees.
They are now solved eight at once on all cores; those that fail in a
row are taken as tried and the walk goes on at the first that doesn't,
as it would one by one. The drag above takes 3.7-4.3 s and every
command's reply is unchanged. Live frames, whose steps share one
frame's budget, are left as they are.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6569
* Sketch: an offset slot can still be moved by the dimension that places it (#6569)
An offset arc is drawn round its source's own centre, and its ends are
held while the sketch solves (the offset is rebuilt from its source
afterwards). Its arc condition, both ends as far from the centre, then
only tied that shared centre, and the source arc with it, to the middle
of the held ends: a slot offset by 1 mm lost a degree of freedom, and
the length that places it was refused as conflicting.
The solver and the constructive reading now leave out the arc condition
of an offset arc round its source's centre (Sketch::offset_arc_follows_source),
as #4466 did for the Parallel between an offset line and its source. The
source's own arc keeps the centre; nothing changes without an offset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a test for a rounded rectangle with an offset round it (#6569)
kaievns's case from the review: the offset's corner arcs tied each corner
centre to a 45 degree line, so the height followed the width and every
width change was refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6841
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsiqyXWBFGTCX9J51bhysc
* Sheet metal: a pattern of a cut made in the flat is kept like the cut (#6841)
A rectangular, circular or path pattern of an extrude cut through sheet metal was refused
("this sheet machining cannot be preserved in its flat material"), though the cut itself is
kept as a cut-out of the flat pattern. The pattern replayed its copies through the generic
boolean, whose sheet check refuses any change the sheet model can't keep; the cut goes through
the sheet's flat machining first.
Each copy of an untapered extrude cut is now machined into the sheets the same way, in its
moved sketch plane (a moved copy keeps the extrude's prism certificate), and then cut from the
other targets. A copy that meets a folded bend is refused as the cut is there. Mirrored copies
(the sketch plane turns over) and patterns of an intersect (which cut with the union of their
copies) keep the generic path.
Fixes#6841.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsiqyXWBFGTCX9J51bhysc
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5098
* Sketch: deleting part of a link breaks off the rest instead of rebuilding it (#5098)
Deleting some of a link's curves left the rest linked. The next refresh saw a
different layout and rebuilt the whole link as normal geometry: the deleted
curve came back and the others lost their construction flag (a projected arc
returning as a solid arc, a projected face's lines turning solid).
sketch.delete now breaks the link for what remains, as Break Link does: those
curves stay where they are and keep their flags. Deleting a whole link, and
text links, are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
* Move the #5098 tests up in sketch_project_tests.rs
They sat at the end of the file, where main has since added other
tests; moving them next to the break-link test avoids the merge conflict.
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>
* claim: #5853
* Batch: refuse a step that would fail at its turn before the first step runs (#5853)
engine.batch and the MCP batch tool checked only a batch's shape up
front. A misspelled command id in a late step was found at its turn,
after every earlier step had run, and then all of it was rolled back: a
160-step batch with a typo as its last step ran for a minute and
changed nothing.
solidcraft_engine::batch_preflight::check finds, before anything runs,
the first step the batch would certainly refuse at its turn: a step
without a command, an unknown command id (the error names the closest
ids, as at the step's turn) or, with strict parameter checking, a call
that breaks its command's typed contract. Anything that depends on the
design is left to the step's turn.
- engine.batch (control channel) checks against the app's registry and
its session's level; view.pick, which the app answers itself, passes.
- The MCP batch tool checks when the commands run in its own process
(headless, with --param-check from the backend). A running app may be
another version with other commands and checks the batch itself; its
reply's not_run is passed on.
- The refusal is the failing step's reply with completed 0, undone 0,
not_run listing every step and a note.
Tests that used an unknown command only to make a late step fail now
use a step that fails at its turn (a bad extrude distance), so they
still test rollback, timings and file.new as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6211
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Joints: a loop whose axes are a hair out of parallel still moves (#6211)
Holes picked on parts are rarely exactly in line. A closed loop through
axes a few hundredths of a microradian apart (a lever, link and a rod on
a cylindrical axle) can't close exactly: it closes to nanometres, and the
gap grows as it turns. The loop solver wanted 1e-7 mm, so it threw such a
closure away (the loop stayed open, its parts at 0 dof) or stopped a drive
or drag after a fraction of a degree.
The conflict check is now 1e-4 mm / 1e-6 rad (it was below what its
message can show), and joint.drive / motion and joint.reach close the
loop at each step as at the start: to the tight tolerance where they get
there, otherwise within the conflict check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #2175
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Body rename: an imported assembly's other bodies keep their names and components (#2175)
An Import keeps its body names by the file's body index, and builds no body for the
file's later instances of a repeated part (`skip`). Renaming a body stored the names by
the body's place in the model instead, so every name after the first skipped instance
landed one slot early (and the new name could land on a skipped slot). The rename looked
right until the design was rebuilt; then those bodies took other names, dropped out of
their components and showed up loose in the import's top component.
`Feature::body_name_slots` maps the bodies a feature builds to their stored slots, and
`Feature::pin_body_names` stores current names through it. The rename, the pinning of
later features' names (rename and suppress) and `Document::body_rename_from` use them.
Fixes#2175
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6348
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: constrain to an edge of any body, a mirrored copy too (#6348)
A constraint tool ignored a click on a body edge, so only edges already in
the sketch (the face it sits on) could be constrained to; a mirrored copy's
edges never were. Constraint commands now take a model edge or vertex
({edge|vertex: [x,y,z], body?}) for an operand and project it first, in the
same undo step, and the pick tools pass a clicked edge or vertex that way.
The link keeps the edge's persistent name, so the constraint follows rebuilds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6646
* Control channel: document.revision, a design revision and a document id (#6646)
A program that follows the open design asks document.revision and exports
only when the answer moves: document_id names the open design (kept through
edits, saves and renames, another for each design started or opened, never
shared between two tabs or two runs) and revision moves whenever the design,
what is hidden or an uncaptured component position changes. It stays put on
reads, selections and view changes, unlike the session's view revision.
The revision is read lazily: the session remembers the design it last
reported (held weakly, so Arc::make_mut can never change it in place and its
address can't be reused) and takes a new number from one counter for the
whole run when the design is another one. The control method answers at once,
also while a slow command computes; engine.execute reaches the same command
for scripts and MCP.
* document.revision: hash the run's id prefix with the process's random keys
A clock set before 1970 gave every run the prefix 0, so a restarted app could
answer a new design with the id and revision of the old one. The docs no
longer promise an answer at once: a screenshot waiting for an opened design to
be meshed still holds the requests behind it.
* document.revision: its label in the complete catalogs (ja, zh)
* claim: #6985
* Control channel: control.subscribe and design.changed events (#6985)
A connection that sends control.subscribe gets the active design's id and
revision in the reply (as document.revision answers), then a
{"event":"design.changed","params":{document_id, revision}} line whenever
either changes, so a live link need not poll.
The window publishes the design at the end of each frame's logic and
drawing while a connection is subscribed (one atomic load otherwise). Each
subscribed connection waits on that from a thread of its own and writes
the latest after a 100 ms gap, so a burst of changes is one line. Replies
and events share one lock per connection, each written whole (#6390).
* control.subscribe: the reply before any event, a second one refused
The events thread started before the subscribe reply was written, so a
change right after the window's answer could reach the client before the
reply that named the older revision. The subscription now starts holding
the connection's line lock and writes the reply under it.
A second control.subscribe on a subscribed connection is refused on the
connection itself: its answer could arrive behind a newer event. When no
thread or socket is left for the events, the reply says it did not
subscribe instead of ok.
* control.subscribe: a second one does not lose a finished late reply
The already-subscribed answer came before the busy check, so a request that timed out and had finished since lost its reply: control.pending then said the app ended it without one. It now comes after the busy check and drops a finished late reply as every other request does.
* control_server: declare the subscribe guard at the top of the loop
Clear of the lines #6643 changes (its token check moves to where the declaration was).
* control_server: the subscription's items beside serve
Away from the lines next to send, where #6643 adds token_of.
* control test: New on a blank design reuses its tab, so redo the box first
---------
Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
* claim: #617
* fix(sketch): manual Horizontal/Vertical between a circle centre and an edge (#617)
The manual H/V tool silently did nothing when a point and a straight line
were picked or selected together (only two points or a lone line worked),
while auto-constrain inference applied the alignment. A point picked or
selected with a straight line now aligns the point with the line's
midpoint, in the mode the line's dominant axis suggests, through the
existing midpoint alignment in sketch.constraint.horizontal_vertical.
- tools: constraint_preselection consumes [point, line] selections; the
interactive tool applies the alignment on the second pick (a line).
- context menu: Horizontal/Vertical Constraint items for point + line.
- engine + ui regression tests pin centre-edge alignment and the
edge-to-centre dimension from #617.
Written by Qwen.
* style: formatting cleanup for #617 files
---------
Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
* Extend: join a stop that is already another curve's end
sketch.extend refused a target that coincided with another curve's end
("the extension target coincides with existing geometry") instead of
extending onto that point. The stop is now joined: a new line ends on the
existing point, and a free end grows onto it held by a Coincident. The
preview shows that stop too, so the line it draws is the line the click adds.
A stop at the end of a spline or conic joins that end the same way, and
extending an infinite line now refuses instead of moving one of its two
points.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
* Extend: close an arc, take an equal stop, preview with the command's search
An arc with nothing on its path now grows all the way to its own far end and
closes into a whole circle instead of reporting that there is nothing to
extend to.
Two curves stopping the extension equally far are both on the way, so the first
stop is taken instead of refusing to choose between them.
The line preview and the command now share one stop search (`line_aim`), so the
preview can no longer draw a line the click refuses.
---------
Co-authored-by: n0tfr3shm1lk <n0tfr3shm1lk@ukr.net>
Co-authored-by: Codebuff <noreply@codebuff.com>
* claim: #1994
* plastic.snap_fit: base_height and new_body options (#1994)
Base Height extends the snap fit's base below the picked face; New Body
makes the snap fit its own body so it can be patterned or moved. Both
optional, serde-defaulted, old designs replay identically.
* ui: snap_fit length-field defaults list gains the optional base height
---------
Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
* Browser: a feature row edits its profile sketch, suppresses and rolls back
A sketch button on feature rows with a profile sketch, and the feature menu gains
Edit Profile Sketch, Suppress/Unsuppress and Roll Back Here. Features behind the
history marker or suppressed are drawn faint, as in the timeline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: the sketch button sits right after the feature's name
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6545
* cli_link: the Unix-only link code and its test build on Unix only, so the UI crate builds on Windows (#6545)
slot() is used only by link_in's Unix version, and links_only_where_it_may
makes its own link with std::os::unix::fs::symlink; gate both like link_in.
On Windows the test failed to compile (E0433) and clippy -D warnings flagged
slot as dead code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6755
* Frame rate readout stays beside a right-side pane, not under it (#6755)
The readout and its details sat in the viewport's bottom-right corner even under the
floating Assistant. They now move just left of any pane over that corner and come back
when it closes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every view attached to an API-agent message stayed in the history as a
base64 data URL, and the whole history goes with every request (up to 24
per message). Requests grew by one screenshot per message until the
endpoint refused them with HTTP 413, after which every message failed.
Keep the newest two views as pictures and swap older ones for a short note;
the text sent with them and every other message stay. HTTP 413 now says the
conversation is too large for the endpoint and to start a new chat.
Fixes#2298
Main now speaks the Anthropic protocol too, where a view is an `image`
block rather than an OpenAI `image_url` part: drop_old_views trims both,
before trim_history. ui.ai_http_too_large is in every catalog that has the
other ai_http_* messages.
Co-authored-by: Framewrk CI <ci@framewrklabs.ai>
* claim: #6396
* Sketch: a regular line on a construction line is picked before it (#6396)
Coincident sketch curves tied on pick distance and the one drawn first
won, so a construction line drawn first hid the regular line on part of
it. Construction and centre lines now get half a screen point added to
their pick distance: on a tie the regular curve is hovered and picked,
and a construction line clearly nearer the cursor still wins. Clicking
again and Select Other still reach the construction line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
wgpu's default uncaptured-error handler panics ("wgpu error: Out of Memory").
The headless picture devices already report instead; the window's device had
no handler. Errors the code expects stay under error scopes.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
A line tangent to an arc at the arc's end had no sense: the tangency held
with the line on either side of its touch point, so pinning a line's far
end across the corner left the line cutting back through its fillet, and
when the solver found the mirrored corner the arc kept its stored
direction and became its three-quarter complement, inside out.
The solver now keeps each fillet an arc of its corner. A line tangent at an
arc's end keeps its sense there (it leaves the arc as it did) and the arc
its side of its chord, compared with the positions before an interactive
move or the stored ones. A corner that mirrored gets its arc on the
mirrored side: its ends swapped where the solve ran the arc round the
other way with the lines following, or the arc reflected across the line
that stayed and solved again where a line cut back through. A solve that
would loop the arc the other way round, or fold a line onto it, is refused
as a fold is. A drag's cursor-following steps keep the fillets too, so a
line dragged through its own fillet stops at the arc.
The kit slider adds a real `egui::Slider`, lets egui paint it, then paints an
opaque rectangle over it and draws the kit's track and thumb on top. That mask
was exactly `slider_width` wide, and egui places its handle so the circle's
right edge lands on `rect.right()` at the maximum, so the fill stopped flush
with the handle and its last column survived as a light sliver beside the
value box. The kit's own thumb cannot hide it either: its radius is
`t.space_8` while egui's handle radius is `input_radius`, about two pixels
larger.
Extend the mask one pixel past its right edge. The gap before the editor is
`item_spacing.x`, so nothing else is covered, and only the maximum was
affected: at the minimum the handle's left edge coincides with the mask's left
edge, which a fill does cover.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* claim: #6524
* Sketch display: each sketch samples within its own budget (#6524)
After a large DXF was inserted, lines drawn in a later sketch vanished
once finished: only their end points showed and could be picked, and
hiding the DXF sketch brought them back.
The display cache had one sampling budget for every shown sketch (4096
curves, 131 072 vertices per model state and view). Sketches are drawn
and picked in timeline order, so a large imported drawing used it all
and every later curve came back empty (BudgetLimited): not drawn, not
pickable, while the points, drawn separately, stayed.
Each sketch now has its own budget with the same limits (curves,
profiles, vertices and nodes). The cache as a whole is capped at 65 536
curves and 16 384 profiles, so memory stays bounded however many
sketches are shown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6657
* Context menu: the Appearance flyout closes when the pointer moves to another row (#6657)
Hovering Appearance opened its quick-choice flyout, but resting on a plain
row of the same menu never closed it: only another submenu row replaced it.
Now a flyout closes once the pointer moves onto another row and rests there
for 0.3 s, so a diagonal path to the flyout across a neighbour keeps it open.
A flyout opened with the keys stays open while the pointer is still.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A feature's face points are moved onto their named faces before it is
evaluated, so a To Object extrude follows its target face when an earlier
feature moves it. A pattern's or mirror's copies were built from the
feature as stored, with the face where it was picked: after a gear's disc
was made thicker, the tooth reached the new top but its pattern copies
kept the old height. The copies now use the same moved points; the move
is one helper used in both places.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Test: sketch Intersect keeps its exact cuts, crosses curved faces on the surface, cuts on a top face
Intersect's cut of a box, a cylinder, a sphere and a tilted cylinder, as it is on main, pinned.
Two cases where it is not exact: a plane through a cylinder's rim cuts the mesh's chord (its sides
come out at 6.3159 and 6.3085, not the square root of 40; a hole's walls at 25.0122), and a plane
on a body's top face finds nothing ("projects to nothing on this sketch plane").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsiqyXWBFGTCX9J51bhysc
* Sketch Intersect: crossings on curved faces lie on the surface; a plane on a top face cuts it
Problem: the plane cut that sketch Intersect makes of a body's mesh (project::section) took a
crossing on an edge where a curved face meets another (a cylinder's rim on its cap) from the
merged normals, which are undefined at the crease, so it fell on the chord: a cylinder's sides came
out 0.01 mm off and uneven. A plane through a vertex or on a face counted the vertices on it as
below, so a plane on a body's top face crossed no edge and found nothing.
Solution: the crossings are found by section_segments, which section fits as before: an edge where
a curved face meets another takes the curved face's normals, so the crossing lies on its surface;
a vertex within 1e-9 x the mesh's size of the plane is on it, on one side for every edge (below
when the mesh lies on or above the plane, else above), and an edge ending there crosses at that
vertex, which is an exact point for the fit. Each segment keeps its B-rep face, for a section that
groups them by face. Cuts that were exact are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsiqyXWBFGTCX9J51bhysc
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The first test in each GPU path shard compiles the path pipeline cold
on lavapipe (335-502 s on 16 vCPU, 665 s on 8, against the 600 s setup
budget); the later ones read Mesa's disk cache in about 30 s. Put that
cache on a sticky disk that main commits, so the first test reads it
too until the shaders change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A change computed off the window thread ran here with only the results of the evaluation
ahead's last evaluate primed. A parameter change evaluates twice (before and after its sketch
stores the new solution), so its first evaluate here found nothing to take, and on a design whose
states pass the evaluation cache's budget it recomputed the features on the window thread: the
window froze for seconds after Computing ended. The run now also takes what the evaluation ahead
kept (KeptResults, as an agent's control requests already do).
The catalogue entry Quantity served both the pattern dialogs' number of
instances and the motion study's plotted quantity. Nine locales had
translated the second meaning, so the rectangular pattern dialog showed
e.g. "Größe" above "Anzahl 2" in German.
The motion study's row now reads motion.plot_quantity (a pending file
that keeps every locale's previous word there). Quantity takes each
catalogue's own ui.quantity text, which the "Quantity 2" row already
shows; nothing is newly translated. The glossary lists Quantity under
words with more than one meaning.
Fixes#6538
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On 8 vCPU the first merge-queue run after #7066 failed: one gpu path
test went past the 600 s pipeline-setup budget (665 s; 204 s for the
same test on the PR run) and shard 2 took 35 minutes. lavapipe compiles
on every core, so the shards move to 16 vCPU, and four count partitions
(27 tests each) replace three hash ones (41/40/27).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The 108 gpu path:: tests run one at a time (gpu-path test group) and took
about 36 of Native Linux tests' 45 minutes in a merge-queue run. They now
run in a GPU path tests job, three hash partitions on 8 vCPU, beside the
other tests; Native Linux tests leaves exactly them out. A `gpu` output of
Select checks drives both the job's `if` and the `ci` check.
The Windows jobs now run in a merge group only when its diff reaches what
they exercise (the pull request patterns and the Cargo.lock graphics-stack
check), not on pushes to main, and still nightly and by hand.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The README and docs/downloads.md ship a universal macOS disk image, but the
contributing guide listed only Windows and Linux.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: on Windows, give claude mcp add the CLI's full path (#5895)
The Windows installer puts solidcraft-cli.exe in C:\Program Files\SolidCraft but not on PATH,
so the README's and docs/mcp.md's `claude mcp add solidcraft -- solidcraft-cli mcp` registers a
server that can't start there. Say so next to the command and give the full-path form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: word the Windows full-path note so it holds once the installer sets PATH (#5895)
#5898 makes the MSI add its folder to PATH. Say "if a terminal doesn't find solidcraft-cli
(older Windows installers don't put it on PATH)" instead of stating that the installer never
does, so the note stays true whichever change lands first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: the Windows CLI path is the default, not the only place (#5895)
The MSI installs to ProgramFiles6432Folder and lets people choose the folder
(WixUI_InstallDir), so C:\Program Files\SolidCraft\ is only the x64 installer's default; the
x86 MSI on 64-bit Windows lands in C:\Program Files (x86)\SolidCraft\. Say "by default" in
the README and docs/mcp.md (review on #5896).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
SolidCraft runs through XWayland whenever an X display is available. A
compositor that doesn't let X11 apps scale themselves draws the window at
100 % and stretches it. docs/nix.md said so only for fractional scaling,
but GNOME 47 (RHEL and Rocky Linux 10) does it at any scale: the report
shows 1.05 physical pixels per point at 200 %.
The troubleshooting page gets a section with the cause, the Window system
preference, SOLIDCRAFT_WAYLAND=1 for 0.21.0 and earlier, and the file drop
trade-off; docs/nix.md links to it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The component colour tests no longer assume a design always carries a list of
pending format changes; once the release folds them, there is none.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4214
* Viewport: the grid fades with the distance instead of cutting off (#4214)
The ground grid stopped at a fixed distance from the view, so its edge showed
as a cut across the picture. It now fades out with the distance from the
middle of the view, and it is gone before either of the two lines that used to
cut it: its own edge, and the picture's far plane.
Each grid line is drawn in pieces, and each piece is as faint as the fade
makes it at the distance where it lies. The 'under' batch is drawn by the
overlay pipeline with alpha blending, so the colour's alpha reaches the
frame.
* The headless grid fades with the distance too (#4214)
The headless path (solidcraft-cli snapshot, the home-page thumbnail, the MCP
screenshot) drew the grid from the engine's palette, where a line had no
alpha: the grid still ended in a cut, and at a low camera angle the cut was
in the picture.
A SceneLine now carries an alpha, the CPU rasterizer blends with it and the
GPU capture passes it on, so the engine's grid can fade. The fade itself
moves to the engine, next to the grid's other geometry, and both paths use
it: the headless grid now reaches as far on the ground as the view is wide
(the ground stretches as it is seen at a slant) and dissolves into the
distance instead of ending in a line.
* Fix the headless/viewport grid fade divergence (#4214)
The engine stretched the grid's size with the slant of the plane it is seen at, the viewport stretched it only on the ground, so on a sketch's plane the picture's grid reached further than the window's (153.42 mm against 120.00 mm). Both now share one grid_ext, which the picture and the window draw the same grid from.
* The grid fades out before the picture's far plane cuts the ground (#4214)
The window's grid faded against a far plane of its own: the scene's radius
clipped by the projection's camera (260 mm away at 400 mm of distance), while
the picture itself cuts the ground at the far plane of its own projection
(223.7 mm at the same distance, because the projection frames the scene's
bounds and not its radius). The grid was still a third of its strength where
the picture cut the ground, so the cut showed as a straight line across the
view.
The fade now ends at the far plane the picture's projection uses, and the
grid is gone before that plane cuts the ground.
The test that watched the fade now measures the ink of each row of the
picture, and checks that the grid is already gone in the ten rows above the
row where the far plane cuts the ground: the point it compared before lay
beyond that plane, where both renders draw nothing at all, so the comparison
was always true.
* claim: #5734
* Docs: don't offer winget install before the package is in winget-pkgs (#5734)
The README and docs/downloads.md said Windows users could run
`winget install bherbruck.SolidCraft`, but winget-pkgs has no manifests for
bherbruck.SolidCraft (manifests/b/bherbruck doesn't exist), so winget finds
no package. winget.yml only updates a package that is already listed, and
does nothing until then. Drop the command from both pages, and say in
docs/releasing.md that the first version has to be submitted once by hand
and the command added back when it's listed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Releasing: a first WinGet submission is already open; take it up (#5734)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): finish the open sketch when a modeling feature starts
* fix(ui): wait for the marker roll before starting a feature from a sketch
* fix(ui): start the feature once the marker is back
* test(ui): keep the sketch open in the palette clearance test
Saving into Documents fails with the Windows dialog's "File not found" when
Controlled folder access blocks the app, and the message gives no hint.
Refs #4479.
* claim: #4640
* Docs: AppImageLauncher 2.x can't start the AppImage; update to 3.0 (#4640)
AppImageLauncher 2.x intercepts every AppImage through binfmt_misc and
can't start static-runtime AppImages such as SolidCraft's: it fails with
"execv error: No such file or directory" after a FUSE message. 3.0 starts
it. One paragraph in the Linux AppImage section says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: AppImageLauncher 3.0 is so far only published as betas (#4640)
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
GitHub Pages reports the base URL as http:// for the custom domain, so the
.flatpakrepo file, the remote-add line on the repository page and the mirror
of the previous repository all used plain HTTP. Force https:// once where the
base URL is read.
* docs: two notes for agents and contributors (the gate's working tree, and linking a picture)
* docs: how to show a picture without attaching it
The reviewer asked for the pictures to be attached to the PR (or in a gist),
not hosted on a branch of a fork, so the note no longer teaches the branch
trick.
* docs: say how to attach a picture, and what a gist is for
The review of this pull request pointed at two things in the note on
evidence:
- Attaching works in the browser for anyone who can comment: drag the
file into the comment. The limit is the command-line agent, whose
upload needs write access to this repository. Say that, instead of
claiming that a contributor without write access cannot attach a file.
- A gist suits a log or another text file; it does not render a picture
inline. Say what it is for, so nobody hits that dead end.
The note in AGENTS.md about the gate reading the working tree is
unchanged.
* claim: #4851
* User guide: local models with the Assistant (#4851)
A page on choosing and setting up a local model (Ollama, LM Studio, or any
OpenAI-compatible server) for the Assistant: tool support, VRAM, permissions,
the measured results from the standard test in #4851, and troubleshooting
for the common failures.
* Guide: local models table with the full set of measured results
Point to the `<file>.bak` copy every save keeps, the design.solidcraft in crash
reports, the recovery folder for unsaved designs, and how to attach the file
to an issue.
"Start every session here" sent readers to plan/STATUS.md,
plan/execution-plan.md, plan/architecture.md and
plan/adr/0001-geometry-kernel.md. plan/ is gitignored, so nobody who
clones the repository has those files.
Point the steps at what ships instead: ROADMAP.md, CONTRIBUTING.md's
"Where help is wanted most", "How the code is laid out" and "Kernel
work is paused" sections, docs/architecture.md, the help wanted, good
first issue and needs-triage labels, and the licence rules with
deny.toml and cargo xtask licences --check.
Signed-off-by: Ray <raykholo@users.noreply.github.com>
* Roadmap: a short user-facing ROADMAP.md, developer detail moves to docs/roadmap-detail.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Roadmap: feature milestones with progress counts, daily releases through the in-app updater
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Roadmap: count issues as pieces, show work built on the drawings and render branches
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Joint dialog kept a round edge (a hole's rim), a straight edge and a
corner as plain points where they were picked. When the part got thicker,
a hole moved or the part got longer, the origin stayed where it was and
the jointed part overlapped the other one. An edit that left the picked
spot where it was (lengthening an arm away from the jointed hole) looked
fine, which hid it. Faces and hole walls were already found by name.
The dialog now sends a round edge as its circle's centre with its z, and a
straight edge or corner as an edge pick with its z. The engine keeps them
by name (the round end by its wall and the face across its rim, the edge
and corner by their edge and face names), with the frame the pick showed;
where it can't (no planar face beside the edge, no names), it keeps the
point as before. Edge origins get an optional axis (along the edge, or a
fixed direction) so a hinge on an edge keeps turning about it.
Tests: every joint type and as-built, for each way the dialog picks an
origin, with the arm made thicker, its hole moved and the arm lengthened,
inside one design and through Update Links.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Move and Align on placed components: picks and vectors where they are shown
A body in a component placed away from its own frame (an imported part
set upright) moved the wrong way: Move's vector, axis and pivot were
taken in the root's frame, so moving the upright plate up moved it along
its own frame's z. They now turn with the frame of the bodies moved.
Align mapped each pick into the frame of the body under it, so aligning
two separately placed parts mixed two frames (the plate landed at right
angles), and its snap points were never mapped at all. Align now finds
its picks, face normals and snaps where the view shows them and takes
both ends into the moved bodies' frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Preview: a feature's tool is drawn where its component is shown
Extrude, revolve and the primitives preview their tool on its own. That
preview was built in the model's frame, so on a component placed away
from it (an imported part set upright, edited with its component
active) the tool showed at right angles to the picked face, though the
feature itself came out right.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Follow main's lazy body frames and first-part grounding in the placed-component work
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>
* Cache assembly browser data between unchanged frames
Extend #1762's cached component membership, placements and viewport hover
feedback with cached occurrence labels, folder entries, row keys and
tooltips. Skip offscreen row work and reuse browser hover buffers while
reading selection, visibility and locking live. Cache #2681's standard-part
configuration labels with the document snapshot instead of formatting
them on every redraw. Preserve #3009's live selection and hover marks
with highlight keys owned once by cached entries, and retain #2431's
feature rows in the reusable drawing order.
Related to:
- tangency-labs/solidcraft#662
- tangency-labs/solidcraft#1762
- tangency-labs/solidcraft#2681
- tangency-labs/solidcraft#3009
- tangency-labs/solidcraft#2431
Co-authored-by: Codex <noreply@openai.com>
* Browser cache: lock state moved to the session visibility
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser cache: reveal a placement's body by its row, and drop a needless borrow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Pipe: an Operation row, so a pipe can join, cut or intersect (#2820)
The engine's solid.pipe takes an operation, but the Pipe window had no way to
choose one, so a pipe always made a new body. The window now has the same
Operation row as Loft and the primitives, and editing a pipe keeps its
operation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fit the Pipe operation row to main: sketch-qualified pick, length_input pattern
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>
* claim: #4846
* Assistant: say so when a turn ends without using SolidCraft's tools
When tools were offered and a completed turn made no tool call and no
change, the chat panel shows a short notice pointing to tool-capable
models: always for an empty reply, and for a text-only reply to an
explicit edit request. Plain answers to questions stay quiet. The
control channel exposes it as turnWarning.
* Fix the Anthropic loop test for the tools event; add ja and zh for the no-tool-call notice
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>
* Every new command comes with input and output schemas
A test fails for a command without a contract or an output type, unless it is a planned!
placeholder or listed in schema/unconverted.txt, which holds the 400 commands that predate
the rule and only shrinks: the test also fails when a listed command has its schemas, when
the list names a command that doesn't exist, or when it isn't sorted.
`checked!` now takes only the command's id and checks the contract that id publishes
(`run_checked_as`), so the contract enforced is always the one described. Another test runs
every command that has a contract in strict with an unknown key, so a contract is never
published without `checked!`, under the command's own id, enforcing it.
A new book page, Command schemas, explains how to write a contract and a reply type, the
tests to add, the shared field types and how to add one, with a checklist; the failure
messages point to it. "Adding a command", CONTRIBUTING.md and AGENTS.md now include the
schemas, and the MCP tool description and docs/mcp.md no longer list the converted commands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* List the commands added on main since: view.layout, view.history_layout, view.normal_to
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Command schemas guide: what participants and targets do, checking the evaluation, warning-free replies
- `participants` changes the result for some features and nothing for others; where it and
another key override each other, or where it has no effect, say so in that key's
description or in the summary.
- The example primitive's `targets` default to every body in the feature's component, not the
bodies it meets; they're ignored when `participants` lists bodies.
- Check what each value does where the feature is evaluated, not only how the handler reads it.
- A reply test runs every form in Warn and checks for no `param_warnings` (Strict for a reply
that isn't an object).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* List the 43 commands added on main since: frame, component, render and others
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* List the 11 commands added on main since: browser folding, standard views, parameter sweep, path flange
browser.collapse_all, browser.expand_all, inspect.parameter_sweep, sheet.path_flange and the
seven view.* standard views (back, bottom, front, isometric, left, right, top) landed on main
without schemas; the gate flags them, as intended. 457 commands listed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Schema rule warns instead of failing for now
A command without input and output schemas prints a warning listing it
(test output and a CI annotation). SCHEMA_RULE_STRICT in coverage_tests.rs
(or SOLIDCRAFT_SCHEMA_RULE_STRICT=1) makes it fail again. Docs say "should,
and will become required".
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>
* claim: #3965
* Parameters: a click can correct an invalid name (#3965)
Refusing the new row was putting focus back on the field being left, so the correction was typed into the expression.
* Parameters: the invalid-name test types its expression draft (#3965)
A new row starts with a blank expression, so the regression test types 10 mm and checks that correcting the name leaves that draft in place.
* claim: #4146
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport theme: a light 3D view in a dark window, and the reverse (#4146)
Preferences › Interface › Theme gets a Viewport row under the theme tiles:
Follow theme (the default, as before), Light or Dark. Light or Dark draws
the 3D view in the built-in Light or Dark theme's colours whatever the
window uses, so a dark window can show the model on a light view and the
other way round; it works with every theme, theme files included.
The view takes the background and grid, bodies, selection and hover, the
origin, previews, sketches with their dimensions and glyphs, the navigation
cube and triads, box selection and the analysis colours. Panels, dialogs,
menus and the chips over the view keep the window's. Text and warnings drawn
straight onto the view (the sketch status line, the loose-end and midpoint
markers, the section's cut outlines, the 3D sketch tool and the sketch tools'
previews) take the view's side, so they read on its background.
theme::ViewportTheme is the setting (saved as `viewport_theme`: theme, light
or dark; an unknown saved value reads as Follow theme, as ColorVision's
does). Tokens::get() takes the view's colours from the other side only when
the view's dark or light differs from the window's; Tokens::view() is the
view's side for colours drawn onto it. The ground shadow follows the view
(the scene and highlight caches already key on the colours themselves),
ui.render draws in the view's colours, and ui.prefs takes `viewport_theme`
and reports `viewport_dark`. An older release ignores the setting and draws
everything in the theme.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport theme: follow the single set of axis colours
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>
* claim: #754
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27
* Sketch Dimension: arc length from an arc and its end point (#754)
The arc-length dimension existed (sketch.dimension type arc_length) but
the Dimension tool could only give an arc its radius. An arc and one of
its own end points, in either order, now dimension the arc's length:
click the arc, then its end. The pair was refused before ("cannot
dimension that combination"), so no existing result changes. A point
where the arc's end is (a coincident line end) counts as that end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: hovering a circle's edge wakes its centre
A circle's or arc's centre was hard to take with Line or Dimension: it is a small
point in the middle of empty space, taken only within 8 points of the pointer, and
nothing showed where it was. Hovering a circle or arc of the sketch being edited
now wakes its centre: drawn (a cross in a ring) and taken from 16 points away, so
the pointer can go from the edge across the inside to it. Hovering another curve
or point puts it back to sleep, as does leaving the sketch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* A woken centre's extra reach yields to a curve under the pointer
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>
* Browser: surface bodies get the surface icon in the Bodies folder (#4165)
Every row in the Bodies folder had the solid body icon, so a surface (an
open shell, such as a Surface Revolve of an open curve) looked like a
solid. A body whose shell does not close now shows the existing surface
icon; solids keep the body icon.
Body::is_surface walks every shell's edges, so ModelBody caches it once per
body (shared by clones, like outline_bounds) instead of the browser
checking it every frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add the surface icon to the requested-icons manifest
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>
* Look At in the viewport context menu for a planar face or plane
Fixes#4105
* Look At tests: turn the camera without the animation
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>
* claim: #4846
* Warn when Ollama models lack tool support (#4846)
Check Ollama model capabilities in the background and cache results per
endpoint and model. Show an advisory warning for models without tools,
expose it through ui.agent_chat, and document the behavior.
Cover capability parsing, warning selection, background probing, caching,
and selection changes with synthetic regression tests.
* Keep the Ollama capability probe out of the web build
The panel never calls it there, so it would only be dead code.
* Wrap the model warning instead of cutting it off
In a narrow panel the end of the sentence, the part that says what to do, was truncated.
* Fix AgentConfig default after rebase onto main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the Ollama no-tools warning into a pending locale file with ja and zh
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>
solid.loft already closes to a point (a section {sketch, point}), but the Loft
window's Profiles input took profiles only, so a pyramid or cone tip couldn't
be made from the UI. The input now also takes a finished sketch's point, sent
as an apex section in pick order; profiles of one sketch still make one
section.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #2348
* Open linked designs from assembly browser
* claim: #2348
* Test linked design action with multiple components selected
* Fix linked design lookup in browser storage and translate its menu action
* Add Turn Up Axis to complete UI catalogs
* Keep browser DXF download after linked-design checks
* feat: add colored components inspection toggle
* feat: persist component colors and add cycling controls
* feat: assign component colors in creation order
* fix: update component color menu integration and translations
* fix: proper description for pending format change.
* fix: deleted duplicated format change description from docs/pending.
* Compare the component colour counter in the features-only check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Vadim Kalinsky <vadim@kalinsky.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a click picks the sketch's own curve over a projected one under it
* Select Other tests: follow origin visibility rename
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Own-curve pick test: click away from the lines' midpoints, which are picks of their own now
* Use the select-other test helpers main already has
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>
* claim: #5543
* Extrude: a profile drawn twice is cut once, not as two coincident tools (#5543)
A circle drawn twice at one place is two profiles of one region. Extruded
together they were two coincident tools: a cut through a 121-hole grid with
four such circles retried the boolean with the tool nudged (four times) and
took 25 s instead of 0.5 s.
merge_regions, which every extrude, revolve and surface feature runs on the
regions it builds from, now leaves out a region whose boundary repeats an
earlier one (within 1e-7 of its size; corners and segment middles on the
other loop both ways, holes alike). The selection itself keeps its
multiplicities, as profile_refs' tests require.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6186
* Timeline: a reorder evaluates the design once, from the cache it already has (#6186)
timeline.reorder checked the move on a copy of the model, then dropped it,
so setting the reordered design evaluated the moved part of the timeline
again; reorder_many (and moving features into a group) checked on an empty
model, recomputing the whole design even for a move near the end. The
check now starts from the session's own model, and the session keeps the
model it was checked on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Marker is Copy: drop the clone
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>
Search Console lists docs.solidcraft.io/, /introduction.html (the same page) and /guide/mcp.html as duplicates without a user-selected canonical. The Pages build now adds a canonical link to every book page (index.html and introduction.html both name /), skips the redirect stubs that already have one, marks 404 and the toc frame noindex, and writes a robots.txt pointing at a generated sitemap.xml.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5849
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G1b41qKhyhmZoiAFYt9YwX
* Context menu: a right-click on a face of a selected body keeps the selection (#5849)
A right-click selects what is under the cursor first, so the menu is about it. The check
for "already selected" compared the pick with each selected item by identity, so a face
of a selected body never matched: right-clicking a face of a selected body replaced the
whole selection with that one face, and the face menu opened instead of the body menu.
A face, edge or vertex of a selected body now counts as part of the selection, whether the
body is selected on its own or through an occurrence. The right-click then leaves the
selection alone, as it already did for a face in a multi-selection of faces (#3733).
selection::same_item is unchanged: dialogs and Select Other compare picks with it, and
there a face of a selected body is a different pick.
Fixes#5849.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G1b41qKhyhmZoiAFYt9YwX
* Pass the held keys in the selected-body context menu test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Move: placements of one part moved together each go the way asked (#4768)
A part placed more than once (an imported assembly's corners, screws or
fan blades) is one body in its component. Moving several of its
placements together moved that body once per placement listed, each
placement turning the move its own way, so the copies flew apart.
When solid.move names one part through two or more placements, it now
moves those placements in the world, as occurrence.move does with space
world: a placement held by a rigid group (an imported assembly) moves with
the outermost placement holding it, one inside another being moved isn't
moved again, and root-component bodies listed are moved as bodies, all in
one undo step. Other moves are unchanged, and so is a move with copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Free the grounded first part before the placement-move tests move it
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>
* Test: a text's words follow its text parameters, and so does its cut
A sketch text whose words are an expression of text parameters (`Label + '-L'`): a parameter edit
and a configuration row that sets the parameter relabel the text, the text's cut follows, and the
expression is saved with the design.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsiqyXWBFGTCX9J51bhysc
* Text parameters: a sketch text's words built from them follow every change
A design's parameters are numbers; a text's words (a part label, a size code) could only be typed.
Text parameters hold words: `parameters.add` or `parameters.change` with `unit: "text"` (the
expression is the text, quotes optional), and `sketch.text` and `sketch.edit_text` take
`text_expression` (quoted texts and text parameters joined by `+`, `Label + '-L'`) instead of
`text`. The text is laid out anew from its expression on every evaluation, so a parameter edit,
a configuration row or a script relabels it and the features built on it (a cut, an emboss)
follow in the same rebuild. The design file keeps the text parameters and each text's expression
(one pending format change); a configuration table's parameter column can be a text parameter;
the Parameters dialog lists them under the unit `text`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsiqyXWBFGTCX9J51bhysc
* Text parameters: the joints' same-but-features check compares the text parameters
The joints are solved once per change unless the design differs only in its features
(same_but_features, #6372), which names every Document field. The text parameters are a
field of their own and change what a text's words evaluate to, so two designs that differ
in them are not the same but for their features.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`cargo clippy --workspace --all-targets -- -D warnings` fails on Rust 1.97: the
reflection radiance bound in crates/render/src/raytrace/image.rs (#5494) trips
`clippy::manual_range_contains` (`v < 0.0 || v > 1e12`).
It now reads `!(0.0..=1e12).contains(&v)`. The two differ only for NaN, which the
`is_finite` check in front of it already refuses, so nothing else changes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Project: don't lay a second copy over geometry the sketch already shows
A sketch on a face already holds the face's border, linked. Projecting that face
again, or one of the border's edges, added a second set of curves and points over
the first: on a rounded corner that shows as extra points and lines. Project now
skips a reference whose curves are all linked into the sketch already (lines,
circles, arcs), reports it as `already_projected`, and refuses a pick of nothing
new so it adds no undo step. Points on their own (the origin, a vertex) still
project.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Projecting the face a sketch already shows adds no second copy in the face scenario
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>
* claim: #976
* One set of X, Y and Z colours for every axis (#976)
The origin axes, the navigation cube's triad, 3D sketch triads and joint
origins drew `axis_x/y/z` (255,0,0 / 0,255,0 / 2,2,248), the view triad and
the headless picture drew `triad` (220,60,60 / 60,170,60 / 60,100,230) and
the Move gizmo drew `gizmo` (225,70,60 / 80,190,80 / 70,130,240). The
`triad` and `gizmo` tokens are gone: every axis reads `axis_x/y/z`, now the
view triad's values (the engine's `view::colors::AXIS_*`, so headless
pictures are unchanged, and the set the red-green table was measured for).
Older theme files' `triad` and `gizmo` lists still read and set the axes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5860
* Doc: stop the names of split faces from growing with every split
* Joints: the long-piece-name test takes the longest name a piece now gets
Split-face names are bounded at 512 bytes since #5860, so the test cannot
build one over 512; it takes the longest the history gives.
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>
* Docs: refresh every UI screenshot, GIF and video from the current UI
Re-record the README demo, the rover build, the sample plate and the class-A surface pictures
with 0.22.1 in the default dark theme. The demo script is fixed for the current sketch dimension
boxes and MCP file limits, and the rover replay and class-A pictures now have scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: cut the idle time out of the demo and rover recordings
The README demo plays at 6x and the rover replay at 15x with a constant-rate camera orbit, with the stretches where nothing on screen changes cut out by the new trim_idle.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#5187 fixed the frozen Render preview: product frames now set ss_ready
themselves instead of keeping the flag from the last Design frame, so
paint() no longer blits that stale offscreen picture. Nothing tested it.
The new test draws a see-through Design frame (offscreen, for
order-independent transparency) and then an opaque product frame on the
same renderer, and checks the middle pixel shows the product. With the
product branch's ss_ready update removed it fails with the stale red
Design colour, [255, 129, 129, 255].
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(uk): translate 188 pending messages
Bring the Ukrainian catalog up to date with en.json after rebasing onto the latest main. Covers the crash-report form, AI preferences, preferences pages, solid tool help, free orbit and history controls. Keys stay sorted via cargo xtask lists.
* i18n(uk): translate 114 pending messages
Rebase onto the latest main and bring the Ukrainian catalog up to date with
en.json. The new messages cover the mouse-gesture editor and navigation
preset help, the frames and sheet-metal dialogs, fillet control radii, the
timeline folder actions, the motion keyframe controls, library appearance and
library file actions, and the measure dialog.
Also correct "Add Key" to "Додати ключовий кадр": the only user of that
message is the motion keyframe button, and the catalog already translates
"Keys" as keyframes.
Keys stay sorted; cargo xtask lists --check and the i18n catalog tests pass.
* Sort the Ukrainian catalogue
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>
* Home: a theme's File view follows its own colours where it sets none (#4868)
and no built-in theme sets them, so every theme kept Dark's or Light's blue rail and grey cards
under its own app bar and headings. A theme that leaves them unset now gets them from its own
palette: page and cards from its panel and dialog, the rail from its accent deepened as Dark's
and Light's are, the primary action from its soft accent, and each ink the one of its own colours
that reads best there. Dark and Light themselves are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Leave a theme that sets no colours as its base palette
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>
* i18n(da): complete upstream catalog and align CAD help terminology
Add 371 missing Danish translations, refresh changed source messages,
and independently rewrite six help descriptions from SolidCraft sources.
Preserve all 2397 keys, canonical ordering and placeholders.
* Sort the Danish catalogue
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>
* test: cover malformed and unbounded recovery metadata
Add regression tests for oversized JSON, mismatched entry IDs, and
non-regular recovery metadata. These expose failures in the baseline
recovery reader before the implementation fix.
Signed-off-by: Taha Zarif <tahazarif380@gmail.com>
* fix: bound recovery metadata reads and validate entry identity
Reject oversized and non-regular recovery metadata before reading it,
and bound the read if a file grows after inspection. Require each JSON
entry ID to match its filename for orphan listing and recovery loading,
preventing unrelated save paths from being attached to a recovered design.
Signed-off-by: Taha Zarif <tahazarif380@gmail.com>
* Format recovery.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Signed-off-by: Taha Zarif <tahazarif380@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6226
* Offset Face: split a face off its seam and try again, as booleans do (#6226)
A hole's wall arrives from a STEP file as one whole turn bounded by one wire that
runs along its seam twice, where SolidCraft builds the same wall as two halves.
Offset Face heals the body before it moves anything, and healing such a face
fails inside truck with `This wire is not simple`, so the panic guard turned the
whole operation into a kernel internal error. Since importing is how a part with
a hole usually arrives, Offset Face did not work on imported parts with holes at
all.
`seam_faces` already answers this for booleans: split every such face in two
along a plane through its axis, run the operation, and compose the history back
over the body as it was given. This gives the offset path the same retry.
- `seam_faces::one_body` is `boolean`'s answer for an operation that takes one
body, with `needed_one` beside `needed`.
- `offset_faces` now does its healing, its face picking and its offset inside one
closure, so the retry runs the whole of it on the split body; the first attempt
is guarded, so the failure is an error the retry can act on rather than an
unwind. The faces are picked per body, because the pieces are numbered
differently once a face is split — the points do not move, so they pick the
same surface either way.
The regression test is in `seam_faces_tests.rs`, beside the boolean ones: a
20 x 20 x 10 block with a 6 mm hole, read back from STEP, whose top moves 2 mm
and lands on the same volume as moving the built body's top, with its history
intact.
Checked against `main` before and after: the three kernel tests that fail here
(`blend::…seam_twice`, `step_in::…parallel_body_preparation`,
`tests::a_box_cut_round_a_small_hole…`) fail the same way without this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: CarlosKeese <dudukeese@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* claim: #4544
* Add Norwegian Bokmål translation
* Refresh Norwegian Bokmål translations against current main
* Drop Norwegian messages whose ids are no longer in the English catalogue
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>
* claim: #5103
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Redefine Sketch Plane names the plane a sketch is on now (#5103)
For a sketch with no body-face attachment the panel only said so. It now shows
a Current plane row: the origin plane, a construction plane's name, or the
plane it is offset from, turned from or flipped from. English, Japanese and
Chinese messages added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop needless to_string calls in the plane name format
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>
* Themes: Okibi (dark) and Akari (light), from the Okibi design system
Two built-in themes mapped from the Okibi design system v1.8
(https://kaievns.github.io/okibi-akiri-design-system-spec/): cool charcoal
ash with a single ember accent, and its warm-paper light twin. Every one of
the 156 colours is set, so nothing falls back to the Dark or Light palette.
* Give the Okibi and Akari themes an icon palette
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>
* claim: #6392
* perf: apply a joint solve with indexed joints and occurrences
Every change to a design re-solves its joints and applies the solution. Applying it searched
the joints for each value and, for each occurrence the solve places, searched the occurrences
for it and walked its parents' first occurrences by searching again.
Joints and occurrences are now indexed once, keeping the first of each id as the searches
found it, and the parents' transform is read through the index as the loop leaves it
(component_transform shares the walk, transform_through).
Synthetic design, headless (2000 components at the root with one component inside each, 4000
joints, a solution placing every occurrence): apply 14.1 -> 2.2 ms (median of 12).
The new test applies a solution that moves a component and the components inside it, and
checks each lands where the solution put it, read through its parent as the loop has just
moved it.
* claim: #6394
* perf: group bodies, features and occurrences once in component.list
component.list searched every body, every feature (three times) and every occurrence (twice)
for each component, so its cost grew with components times contents. Bodies, features,
occurrences and children are now grouped by component once, keeping their order, and each
component reads its own groups. The output is unchanged.
The new test lists nested components and checks each gets only its own bodies, features,
sketches, occurrences and children, in timeline order.
* Dialog curve picks keep their sketch (#3726)
* Format
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Format
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Adapt context-menu test to Sel::SketchCurve's sketch field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Path Flange picks keep their sketch too
Path Flange (#4096) landed after this PR and still looked its path up in the newest sketch with those ids.
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Home: stop a recent card spinning forever when its picture can't be made (#4655)
A recent design whose thumbnail job returned no picture (empty or unreadable design) kept the loading placeholder, which draws a spinner, with no retry. Record such keys as a terminal 'failed' state and draw a box placeholder instead; opening or saving the design clears it and tries again. Sample previews are untouched: a failed sample build still keeps its shipped picture.
* Format the gallery sample row
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>
The row itself showed the key the command runs from (#1810), but its help
tooltip still named the default one.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
With G bound to the centre rectangle, G, Escape, R started the centre
rectangle again, because R's family remembered the variant used last
(#4308). A remembered variant that has a key of its own no longer takes
over the family's key; one without a key still comes back on it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
When an agent's models come from several providers (Hermes and OpenCode
name each one "Provider · model" or say "Provider: ..." in its
description), the chat header shows a provider drop-down before the model
drop-down, and the model drop-down lists only that provider's models
without the provider in front. It opens on the provider of the model in
use; picking a provider alone changes nothing. ui.agent_chat takes
provider and reports providers and provider.
* i18n(pt-BR): translate 319 missing messages
Catch up with en.json: the new Preferences pages, assistant settings,
navigation preset help, the bug report form, history names, feature
folders, component links and copies, joint keys and the reference
geometry setting. Messages added to en.json after this branch started
stay in English for now.
* i18n(pt-BR): translate Delete as Apagar, not Excluir
"Excluir" (Latin excludere, to shut out) means to leave out or exclude;
"apagar" is the Portuguese for deleting, wiping something out. Use
apagar for every Delete already in the catalog (22 messages: commands,
the delete confirmation, rules, rows, sets, keys, crash reports), with
its forms: apaga, apagados, apagado(s), apagá-lo. The Delete key's name
stays as it is printed on the keyboard.
* Revert "i18n(pt-BR): translate Delete as Apagar, not Excluir"
This reverts commit 3bc73c5930.
Keep the catalog's existing word for Delete until the term is agreed
for the whole catalog in its own issue; the change can follow as a
separate PR.
* i18n(pt-BR): use Excluir in the five new Delete messages
Apply the review's suggestions: the new messages keep the catalog's
existing word for Delete.
* claim: #3983
* Agent skill for reproducible bug reports (#3983)
skills/solidcraft-reporting has an agent gather the version, steps and
exact error, reproduce the problem in a fresh design (a command script or
one batch with rollback off), shrink it while it still fails, check the
numbers with measure and a screenshot, keep private designs out, search
for duplicates, and write the report in one shape: Reproduced on, Steps
with a minimal script, Expected, Actual, Attachments.
Listed in skills/README.md and docs/mcp.md; the bug template's steps
field points agents to it. The skill-name check now covers its files.
* Reporting skill: GitHub takes a design only as a .zip
* Show scroll arrows on the theme row when more themes are hidden
* Fade the theme row edges and use the chevron icon for its scroll arrows
* Remove the outline left on the theme row arrows after a click
* Trim the theme row scroll arrow tests
* Drop the earlier theme row arrows' test and labels
* claim: #6389
* perf: write each control reply to the socket in one piece
The control server wrote each reply with writeln!(out, "{reply}") on the unbuffered TcpStream.
Formatting a serde_json::Value hands the writer every string, number and punctuation mark
separately, so each became its own write system call. A reply of a few megabytes (component.list
on a design with thousands of components) took about half a second to send.
Every reply is now serialized to one buffer and written at once. Writing one large Value to a
loopback TcpStream: 1.79 MB took 1,052 ms with writeln! and 4.9 ms with one write_all.
The test counts the write calls for a 2000-component reply (one) and reads it back.
In an assembly the bottom timeline listed every feature of every component,
unreadable with a few dozen parts. While a component is active (or one is
picked in the browser), it shows only that component's features and those of
the components inside it. A toggle ahead of the features names the component
and switches to the whole design and back. The marker and drops map from the
shown features to the design's positions.
Tests: the_timeline_shows_only_the_active_components_features,
a_slot_among_the_shown_features_is_a_design_position.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4323
* Gruvbox Dark and Dark Soft: readable selected tab labels (#4323)
The Model tab and the shared tabs (Assemble, Insert, Inspect, Manage,
View) draw their selected label in selected_ink on accent_soft. Both
Gruvbox dark variants inherited the default dark selected_ink #9cc7f5,
which reaches only 4.48:1 on #48544f (Gruvbox Dark) and 4.13:1 on
Set selected_ink to the Gruvbox foreground #ebdbb2 in both files:
5.76:1 and 5.31:1. Tabs with their own accent keep their own ink.
A test checks the selected label of every workspace and contextual tab
against its soft background in Dark, Light and every built-in theme
file.
Every STL, OBJ and 3MF export meshed each body with a chord tolerance of
2e-4 of its size, with no way to ask for finer: a 3 mm hole in a 200 mm
part came out with about 14 sides. file.export and file.save_mesh take
`quality`: standard (as before, the default), fine (5e-5) or finest
(1e-5), and Save As Mesh has a Quality row, remembered like its format.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5024
* Hole: refuse a position that isn't on a face (#5024)
`solid.hole` took any `position` and drilled from the nearest face, so a
point 1 mm past a box's side was accepted and cut a hole across the edge.
A position given as a point (numbers or length expressions) must now lie on
a face of a body, within the same tolerance the plastic features use for
their placement (the check is shared with them). Holes at sketch points are
unchanged.
* Hole: a position over the mouth of a bore or opening of a planar face is on that face (#5024)
A counterbore is centred on an existing bore, in the plane of the face round it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: #5329
* Update Links: show which linked design it reads, off the window thread (#5329)
Update Links read and rebuilt every linked design on the window thread, so the
window froze, with nothing on screen, until it finished. From the toolbar it now
runs as a file operation: off the window thread, with the overlay naming the
linked design being read (cover (2 of 3)), its rebuild step and progress, and
Cancel. The assembly stays as it was until the update is in, and it lands as one
undo step. The command reports each design through doc::progress::item.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The cap pass redraws every back face behind a marked pixel with depth test
Always to put the section plane's depth there, and it also wrote colour.
The last back face drawn painted the cut, so a body behind the cut (one
inside another's hollow, an assembly's inner parts) showed through it in
its own colour and outline, like an X-ray. The pass now writes depth only;
the marking passes have already drawn the nearest back face's colour (the
same split as #4622).
Test: a_body_behind_the_cut_does_not_colour_the_cap in
crates/gpu/tests/capture.rs (a red body in a green box's hollow, 4 mm
behind the cut: the picture is the same with and without it, hatched and
plain, orthographic and perspective). On main 472 pixels differ.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The viewport's picker took the nearest face along the ray whatever the
section removed. Over a cut it hovered the cut-away face in front of the
plane (the outer wall of a sectioned hub, say); the shader clipped that
face's tint, but its outline, drawn dimmed where the model hides it, ran
behind the cap and showed through it. While orbiting with the pointer on
the model the hovered face changed from frame to frame, so lines and
patches of the far side came and went over the hatch.
The picker now does what view.pick already does: faces, edges and corners
on the removed side are skipped (the ray walks past them), and where the
plane crosses a body nearer than any kept face, the cap hides the faces,
edges and corners behind it and nothing is hovered there. The walk that
tells whether the plane is inside a body moves into
view_pick::body_cut_at so both pickers share it.
Test: view_pick_tests::the_mouse_picker_stops_at_a_section_cap (a box
with a pocket, cut through the pocket: over the cut material nothing is
picked, over the pocket its floor, and with the section cleared the top
face). It fails on main, which picks the removed top face.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5938
* Edge flange: height measured from the outer sharp, the inner sharp or the bend tangent (#5938)
An edge flange feature takes height_from (outer, inner or tangent) in sheet.flange and Height From in the Flange dialog, saved only when it is not the outer sharp. A height shorter than the bend takes is refused with the smallest height that fits.
* Edge flange: material outside and tangent bend positions (#5941)
An edge flange's position can now be material_outside (the inner virtual sharp on the edge) or tangent (the bend's outer face tangent to the edge's face), in sheet.flange and the Flange dialog. The bend starting at the edge was already the outside position, which now also answers to bend_outside.
* claim: #6252
* Open hosted designs in the web app and add an embed layout
Fixes#6252.
Fetch HTTPS design links with bounded reads and open them as unsaved copies.
Keep model panels and joint controls in embeds without browser persistence,
application chrome, or unload prompts. Add a full-app link and iframe docs.
Cover query parsing, hostile URLs, size limits, and the compact layout.
* Translate the linked-design messages into Japanese and Chinese
* claim: #4167
* Viewport: ambient occlusion no longer shows ghost copies of the model (#4167)
The AO shader used the same 32 sample offsets at every pixel, and nothing smoothed the
result. On a flat face, each sample darkened its own shifted copy of a nearby part's outline.
Where the radius is large on screen (a big design seen close up), those copies show as faint
separate ghosts of the part. Where it is small, they pack into stepped bands along contacts
(#3407).
The sample pattern is now turned by a different angle at each pixel, using interleaved
gradient noise of the pixel position (Jimenez 2014). The copies break up into an even fine
grain, which 2x supersampling smooths further. The cost is per pixel only (one noise value,
one sine and one cosine). The 32-sample loop, the depth prepass and AO off are unchanged.
Test: ambient_occlusion_draws_no_copies_of_a_part (viewport.rs) renders a block on a plate
from above with AO off and on. AO must still darken beside the block, the plate must stay
clear beyond the radius, and the profile beside the block's straight side must have no
copy-sized steps (0.0144 before, 0.0026 after; limit 0.005). The test-only offscreen helper
gains an AO radius, like render_offscreen_ssaa.
Fixes#4167
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:57:29 +00:00
836 changed files with 113451 additions and 8672 deletions
Thanks for taking the time to report this! Inside the app, **Report an Issue** fills most of this in for you, with the command, its settings and your environment.
Before reporting a boolean or modelling failure, please try to reproduce it on the latest nightly or a build from current `main`, and say which build you used.
- type:textarea
id:what
attributes:
@@ -18,7 +19,8 @@ body:
attributes:
label:Steps to reproduce
description:|
What you clicked or typed, in order. For a problem on a particular design, `solidcraft-cli script design.solidcraft --out repro.json` turns its history into a script that `solidcraft-cli eval repro.json` replays; attach it, trimmed to the failing step if you can.
What you clicked or typed, in order. For a problem on a particular design, `solidcraft-cli script design.sc --out repro.json` turns its history into a script that `solidcraft-cli eval repro.json` replays; attach it, trimmed to the failing step if you can.
Using an AI agent with SolidCraft's MCP server? The `solidcraft-reporting` skill in [`skills/`](https://github.com/tangency-labs/solidcraft/tree/main/skills) has it reproduce the problem in a fresh design and write these steps for you.
# Documentation, packaging, the other workflows and the GitHub bots' scripts select nothing:
# no native job runs for them, on pull requests or on main.
# Tests are built and run in one job on a big runner, slowest first, starting beside the repository
# checks (no archive to upload and download). A few heavy tests (see .config/nextest.toml) are
# skipped on pull requests and run on pushes to main and nightly. The Windows jobs (D3D12 shaders
# and app startup), the reference tests and the doc tests run on main and nightly only, not on
# pull requests (the fast lane, in Select checks); a break there is fixed forward on main. Only
# main saves the build caches; pull requests restore them. sccache is on a Blacksmith sticky disk that
# only main commits.
# checks (no archive to upload and download). The GPU path tracer's tests run one at a time
# (.config/nextest.toml), so they have a job of their own in four shards (GPU path tests). A few
# heavy tests (see .config/nextest.toml) are skipped on pull requests and run on pushes to main and
# nightly. The reference tests and the doc tests run in the merge queue, on main and nightly, not
# on pull requests (the fast lane, in Select checks); a break there is fixed forward on main. The
# Windows jobs (D3D12 shaders and app startup) run nightly, and in the merge queue only for a group
# that changes what they exercise. Only main saves the build caches; pull requests restore them.
# sccache is on a Blacksmith sticky disk that only main commits.
# A push to main runs only the repository checks when a passing merge-queue run tested that very
# commit, or when nothing a build reads changed since the last push to main whose run passed.
# Otherwise it runs everything.
@@ -30,7 +32,7 @@
# reads `ci` from the newest run of a commit, so a run without it would block a PR that passed.
# Cross-compilation is opt-in through Run workflow; release builds remain in release.yml.
# Every Linux job runs on Blacksmith's Ubuntu 24.04 runners (blacksmith-<N>vcpu-ubuntu-2404), sized
# per job: 32 vCPU for the tests, 16 for Clippy, the archive build (nightly) and the merge queue's, 8 for compiles, doc and reference tests, 4 for the light checks, 2 for Select checks and ci.
# per job: 32 vCPU for the tests, 16 for Clippy, the GPU path tests, the archive build (nightly) and the merge queue's, 8 for compiles, doc and reference tests, 4 for the light checks, 2 for Select checks and ci.
# The Windows shader job runs on Blacksmith's Windows Server 2025 runners (blacksmith-8vcpu-windows-2025);
# the Windows app startup smoke stays on windows-latest, which has a desktop session that presents frames.
# Pull requests, forks included, the merge queue, pushes to main and the nightly run all use them:
@@ -96,13 +98,17 @@ jobs:
runs-on:blacksmith-2vcpu-ubuntu-2404
permissions:
contents:read
# To ask whether the merge queue already tested a commit pushed to main.
# To ask whether the merge queue already tested a commit pushed to main, and which labels the
# pull requests of a merge group carry (the render-tests opt-in).
pull-requests:read
actions:read
outputs:
native:${{ steps.paths.outputs.native }}
kernel:${{ steps.paths.outputs.kernel }}
goldens:${{ steps.paths.outputs.goldens }}
windows:${{ steps.paths.outputs.windows }}
# The GPU path tests run (see the rule where it is computed). The job's `if` and the `ci` job's check both read this, so they cannot disagree.
gpu:${{ steps.paths.outputs.gpu }}
full:${{ steps.paths.outputs.full }}
# Code changed on a pull request that has no approved-for-ci label yet: only Quick compile.
SolidCraft is a clean-room, open-source, pure-Rust parametric 3D CAD application:
sketches with a constraint solver, a parametric timeline of features, a B-rep
kernel, and a desktop UI that agents can drive. Projects published by
[storytold](https://github.com/storytold) can serve as references for implementation conventions.
Any code adapted from other projects (MIT OR Apache-2.0) is noted in ATTRIBUTION.md.
SolidCraft is a clean-room, pure-Rust parametric 3D CAD app (sketch solver, feature timeline, B-rep kernel, an egui UI that agents drive). This file is a router: the hard rules, then the skill to read for your task. Humans: CONTRIBUTING.md.
## Product name
SolidCraft everywhere: crates `solidcraft-*`, binaries `solidcraft` and `solidcraft-cli`,
`.solidcraft` designs, the `solidcraft/N` format tag, `SOLIDCRAFT_*` environment variables,
settings and recovery folders, the MCP server name, docs, CI and packaging. The old name
survives only in the explicit compatibility shims listed in `docs/compatibility.md`; don't add
new uses of it anywhere else. Public docs/web/Flatpak deployments use project Pages
`/solidcraft/`; do not add permanent old-name Pages aliases.
## Start here
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Act when the code, docs and rules settle a question; ask only when it changes what users see. Local `plan/` notes are optional.
## Start every session here
1. Read `plan/STATUS.md` (current milestone, next task), then the task in `plan/execution-plan.md`
and the relevant `plan/architecture.md` section.
2. Kernel and library decisions: `plan/adr/0001-geometry-kernel.md` (what we may depend on, what
is rejected for licence reasons).
3. Follow the autonomous operation protocol (`plan/execution-plan.md` §6). Don't stop to ask
unless it lists the decision as the owner's.
## Hard rules (what enforces each)
- Independent: never copy another product's code, icons, text or templates; never drive or take data from commercial CAD. `skills/solidcraft-ground-rules/SKILL.md`.
- No GPL/LGPL/AGPL code, not even reading it (OpenCASCADE, FreeCAD, SolveSpace, CadQuery, LibreCAD, QCAD): `cargo xtask licences --check`.
- Every asset is original or openly licensed with a row in ATTRIBUTION.md; read `assets/AGENTS.md` before icon work: `cargo xtask assets`, `cargo xtask icons`.
- Never crash: no unwrap/expect/panic/unsafe in non-test code (unsafe allowlist in crash-safety), errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
- One outer UI container, one camera path (`navigation::handle`), colours from theme tokens: `cargo xtask ui-containers`.
- Application code is Rust only and builds for wasm: `cargo xtask wasm`, `cargo xtask web-offline-test`.
- The product name is SolidCraft; the old name lives only in `docs/compatibility.md`.
- Sorted lists (message catalogs, `mod` lines, languages) stay sorted: `cargo xtask lists`. Never edit generated files (`docs/features.md`, `THIRD-PARTY-LICENSES.txt`) in a PR.
- A design-format change is ONE new file in `crates/doc/src/format/pending/`: `scripts/test-format-pending.sh`.
- A command has input and output schemas (will become required; `schema/unconverted.txt` only shrinks): `skills/solidcraft-commands/SKILL.md`.
- New UI strings go in `assets/locales/pending/<slug>.json`, ATTRIBUTION rows in `attribution/pending/<slug>.md`: `cargo xtask lists`.
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line, and no second PR for an issue a lower-numbered one closes: `pr-checks.yml`.
- A regression test goes in the area's existing test file, never a per-bug file (#3226).
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR, never force-push `main`, no pushes just to catch up with `main`: `skills/solidcraft-contributing/SKILL.md`.
- Show observable changes with real screenshots or a recording, captured yourself.
- Run `cargo xtask ci` before every commit, without switching branches while it runs. `cargo xtask skills` keeps this file and `skills/` honest.
`plan/` is gitignored (local only).
## Never crash
People trust SolidCraft with their designs; a crash loses their work. **This outranks feature work.**
- **No panics in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`,
`unimplemented!`; no `unsafe` (`unsafe_code = "forbid"`). The exceptions, in the app crate and in
`crates/threedx-client` (which deny it instead), have no safe API: the macOS-only
`crates/threedx-client/src/macos.rs` (loads the 3Dconnexion driver's client framework at run time
and calls through its function pointers), two functions in
`apps/solidcraft/src/open_documents.rs` (macOS hands Finder files only to an Objective-C
method), the Windows-only `apps/solidcraft/src/native_crash.rs` (the exception filter that
records crashes inside native code such as graphics drivers), the Windows-only
`apps/solidcraft/src/native_frame.rs` (the window procedure that answers the resize-border hit
test of the window without OS decorations, and gives its class the icon the taskbar falls back
on) and the Windows-only
`apps/solidcraft/src/space_mouse/windows_hid.rs` (asks Plug and Play which HID interfaces are 3D
mice, so no other device is sent anything).
- **Errors are `Result<T, E>`** through the crate's error type and `?`. An unfinished feature
returns an error ("not supported yet: …"); it never panics.
- **Input-derived numbers are hostile** (command parameters, design files, control-channel
input): `get()` not `[i]`, finite checks, caps on sizes and loop counts.
- **Third-party kernel calls are guarded:** everything that calls `truck` goes through
`solidcraft_kernel::guard`, which turns a panic into `KernelError::Internal`.
- **Last-resort guard:** `Session::execute` runs every command under `catch_unwind`; an escaped
panic restores the design and reports `EngineError::Internal`.
- **Crash reports:** the desktop app writes every panic and every unclean exit to a local crash folder,
with the design and the commands that reproduce it (`solidcraft_engine::crash`, docs/crash-reports.md).
When someone says the app crashed, look there first: `solidcraft-cli crashes`, `crashes show <id>`,
artwork from commercial CAD products — ever.** Command icons are the original SVG masters in
`assets/icons/ui/design/svg/`, compiled through `crates/ui-egui/src/icon_svg.rs`.
**Before any icon, icon assignment or media edit, read `assets/AGENTS.md`,
`assets/icons/ui/AGENTS.md` and `assets/icons/ui/STYLE_GUIDE.md`.** Use the canonical
bases and named finished references there; never invent a parallel icon style. Any file asset must be original or openly licensed and **must have a row in ATTRIBUTION.md**
(`cargo xtask assets` enforces it). Screenshots of commercial CAD software are never committed.
Breaking this rule is the most serious mistake you can make in this repo.
- **Everything is a command.** User-visible behaviour = a command in `crates/engine/src/cmd/*`
(`CommandSpec`: id = our own dotted, lower-case id `<area>.<thing>[.<variant>]` such as
`solid.extrude` or `sketch.rectangle.two_point`, never another product's command id; label;
toolbar tab and panel; icon; shortcut; params doc; `enabled`; JSON `run`) + tests. The toolbar,
dialogs, sketch tools, palette, scripts, CLI and control channel all reach the same commands.
- **Programmatic calls never open dialogs.** `engine.execute` runs the JSON form; only toolbar
clicks (`ui.start`) start interactive tools and dialogs, which then run commands.
- **Layering** is enforced by `cargo xtask layers`: L0 `geom` → L1 `sketch`, `kernel`, `render`
@@ -51,10 +51,10 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/icons/app/macos/exports/icon.icns | SolidCraft contributors | macOS fallback compiled by Apple's actool from the approved assets/icons/app/macos/source/SolidCraft.icon composition | MIT OR Apache-2.0 |
| assets/models/test_boat.bin | 3DBenchy.com | 3DBenchy.com | Public Domain |
| assets/models/make_test_boat.py | SolidCraft contributors | original script that packs a model into the compact file above | MIT OR Apache-2.0 |
| docs/screenshots/class-a-blend.png | SolidCraft contributors | screenshot of SolidCraft itself: two boxes bridged by `surface.blend`(a 40x10 sheet, `solidcraft-cli snapshot` of plan/evidence script ev_blend) | MIT OR Apache-2.0 |
| docs/screenshots/class-a-crown.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch crowned with `surface.patch crown=0.3` over a saddle rim (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
| docs/screenshots/class-a-boundary.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch through two sketch lines built by `surface.boundary` with `crown: 0.4` (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
| docs/screenshots/sample-plate.png | SolidCraft contributors | screenshot of SolidCraft itself (`solidcraft --sample`) | MIT OR Apache-2.0 |
| docs/screenshots/class-a-blend.png | SolidCraft contributors | screenshot of SolidCraft itself: a 24 mm and a 10 mm box bridged by a `surface.blend`sheet (G1 at both edges), rendered by the app's `ui.render` in the dark theme with examples/demo/class_a_shots.py | MIT OR Apache-2.0 |
| docs/screenshots/class-a-crown.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch crowned with `surface.patch crown=0.3` over a skew four-line rim, rendered by the app's `ui.render` in the dark theme with examples/demo/class_a_shots.py | MIT OR Apache-2.0 |
| docs/screenshots/class-a-boundary.png | SolidCraft contributors | screenshot of SolidCraft itself: a surface through two 3D sketch splines built by `surface.boundary`, rendered by the app's `ui.render` in the dark theme with examples/demo/class_a_shots.py | MIT OR Apache-2.0 |
| docs/screenshots/sample-plate.png | SolidCraft contributors | screenshot of SolidCraft 0.22.1 itself (`solidcraft --sample`, default dark theme, 1600x1000) | MIT OR Apache-2.0 |
| assets/samples/exports/0.png | SolidCraft contributors | original Sample Plate from `crates/engine/src/sample.rs`, exported by the SolidCraft thumbnail renderer | MIT OR Apache-2.0 |
| assets/samples/exports/1.png | SolidCraft contributors | original Sheet Metal Bracket from `crates/engine/src/sample.rs`, exported by the SolidCraft thumbnail renderer | MIT OR Apache-2.0 |
| assets/samples/exports/2.png | SolidCraft contributors | original Hinge Assembly from `crates/engine/src/sample.rs`, exported by the SolidCraft thumbnail renderer | MIT OR Apache-2.0 |
@@ -62,10 +62,10 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/samples/exports/4.png | SolidCraft contributors | original Bolted Flange from `crates/engine/src/sample.rs`, exported by the SolidCraft thumbnail renderer | MIT OR Apache-2.0 |
| assets/samples/exports/5.png | SolidCraft contributors | original Vent Plate from `crates/engine/src/sample.rs`, exported by the SolidCraft thumbnail renderer | MIT OR Apache-2.0 |
| assets/samples/exports/scripts.json | SolidCraft contributors | SHA-256 fingerprints of the original sample scripts, generated with the shipped thumbnails | MIT OR Apache-2.0 |
| docs/screenshots/demo-0.8.gif | SolidCraft contributors | original screen recording of the actual SolidCraft 0.8 English desktop UI modelling an original Raspberry Pi 4 case (no logos or branding; the engraved text is set in the system's DejaVu Sans Bold), generated with examples/demo/record_ui.sh and its original animated pointer overlay (9× playback) | MIT OR Apache-2.0 |
| docs/screenshots/demo-0.8.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.8 English desktop UI modelling an original Raspberry Pi 4 case (no logos or branding; the engraved text is set in the system's DejaVu Sans Bold), generated with examples/demo/record_ui.sh and its original animated pointer overlay (9× playback) | MIT OR Apache-2.0 |
| docs/screenshots/rover-build.gif | 0xlillith (model); SolidCraft contributors (recording) | screen recording of the actual SolidCraft desktop UI replaying the timeline of the SELENE rover (`Rover-recorded.solidcraft` from https://github.com/0xlillith/solidcraft-rover, an original model by 0xlillith built through SolidCraft MCP); the model's lettering follows Ubuntu Light outlines (Canonical Ltd., Ubuntu Font Licence 1.0, as noted in that repository); the emblem is the SolidCraft project icon (2× playback) | MIT OR Apache-2.0 |
| docs/screenshots/rover-build.mp4 | 0xlillith (model); SolidCraft contributors (recording) | screen recording of the actual SolidCraft desktop UI replaying the timeline of the SELENE rover (`Rover-recorded.solidcraft` from https://github.com/0xlillith/solidcraft-rover, an original model by 0xlillith built through SolidCraft MCP); the model's lettering follows Ubuntu Light outlines (Canonical Ltd., Ubuntu Font Licence 1.0, as noted in that repository); the emblem is the SolidCraft project icon | MIT OR Apache-2.0 |
| docs/screenshots/demo.gif | SolidCraft contributors | original screen recording of the actual SolidCraft 0.22.1 English desktop UI (default dark theme, software renderer) modelling an original Raspberry Pi 4 case (no logos or branding; the engraved text is set in the system's DejaVu Sans Bold), generated with examples/demo/record_ui.sh and its original animated pointer overlay (6× playback, idle pauses cut) | MIT OR Apache-2.0 |
| docs/screenshots/demo.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.22.1 English desktop UI (default dark theme, software renderer) modelling an original Raspberry Pi 4 case (no logos or branding; the engraved text is set in the system's DejaVu Sans Bold), generated with examples/demo/record_ui.sh and its original animated pointer overlay (6× playback, idle pauses cut) | MIT OR Apache-2.0 |
| docs/screenshots/rover-build.gif | 0xlillith (model); SolidCraft contributors (recording) | screen recording of the actual SolidCraft 0.22.1 desktop UI (default dark theme, software renderer) replaying the timeline of the SELENE rover (`Rover-recorded.solidcraft` from https://github.com/0xlillith/solidcraft-rover, an original model by 0xlillith built through SolidCraft MCP); the model's lettering follows Ubuntu Light outlines (Canonical Ltd., Ubuntu Font Licence 1.0, as noted in that repository); the emblem is the SolidCraft project icon (15× playback, idle pauses cut, constant-rate camera orbit) | MIT OR Apache-2.0 |
| docs/screenshots/rover-build.mp4 | 0xlillith (model); SolidCraft contributors (recording) | screen recording of the actual SolidCraft 0.22.1 desktop UI (default dark theme, software renderer) replaying the timeline of the SELENE rover (`Rover-recorded.solidcraft` from https://github.com/0xlillith/solidcraft-rover, an original model by 0xlillith built through SolidCraft MCP); the model's lettering follows Ubuntu Light outlines (Canonical Ltd., Ubuntu Font Licence 1.0, as noted in that repository); the emblem is the SolidCraft project icon | MIT OR Apache-2.0 |
| docs/design/ux-redesign/model-floating.png | SolidCraft contributors | original UX mockup (Model tab, floating layout) rendered from an HTML mockup that uses the original Technical Accent icon set; no third-party artwork, and not a screenshot of any other CAD product | MIT OR Apache-2.0 |
| docs/design/ux-redesign/model-compact.png | SolidCraft contributors | original UX mockup (Model tab, compact docked layout) rendered from an HTML mockup that uses the original Technical Accent icon set; no third-party artwork, and not a screenshot of any other CAD product | MIT OR Apache-2.0 |
| docs/design/ux-redesign/sketch.png | SolidCraft contributors | original UX mockup (Sketch tab) rendered from an HTML mockup that uses the original Technical Accent icon set; no third-party artwork, and not a screenshot of any other CAD product | MIT OR Apache-2.0 |
@@ -128,14 +128,14 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/fonts/ibm-plex-mono/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/ibm-plex-sans/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/icons/app/linux/exports/hicolor/128x128/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/16x16/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/24x24/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/256x256/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/32x32/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/48x48/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/512x512/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/64x64/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/128x128/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/16x16/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/24x24/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/256x256/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/32x32/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/48x48/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/512x512/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/64x64/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/shared/source/app_icon.rs | SolidCraft contributors | Original SolidCraft drawing code: the borderless mark, drawn from the faces and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg | MIT OR Apache-2.0 |
| assets/icons/app/shared/source/windows_ico.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/icons/app/windows/exports/solidcraft-executable.ico | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
@@ -158,6 +158,7 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/locales/ja.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/ko.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/ms.json | A.Hakim (a-hakim) | Malay translation of the English message catalog | MIT OR Apache-2.0 |
| assets/locales/nb.json | SolidCraft contributors | Original Norwegian Bokmål translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
| assets/locales/nl.json | SolidCraft contributors | Original Dutch translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
| assets/locales/pl.json | cyryllo | Polish translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
| assets/locales/ru.json | Alex Pro (del000) | Russian translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
@@ -170,9 +171,13 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/locales/zh.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/zh-TW.json | SolidCraft contributors | Original Traditional Chinese (Taiwan) translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
| assets/materials/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/platforms/macos/dmg/background.tiff | SolidCraft contributors | original disk image window background (blueprint sheet: grid, double border, circles, centre lines, leader arrow, title block) drawn by assets/platforms/macos/dmg/make_background.py in IBM Plex Sans/Mono; 1x and 2x representations | MIT OR Apache-2.0 |
| assets/platforms/macos/dmg/make_background.py | SolidCraft contributors | authoring script that draws the disk image background (Pillow); not part of any build | MIT OR Apache-2.0 |
| assets/platforms/macos/dmg/layout.json | SolidCraft contributors | disk image window size, icon size and icon positions shared by the background script and settings.py | MIT OR Apache-2.0 |
| assets/platforms/macos/dmg/settings.py | SolidCraft contributors | dmgbuild settings (MIT, https://github.com/dmgbuild/dmgbuild) for the SolidCraft disk image; original settings, no dmgbuild code copied | MIT OR Apache-2.0 |
@@ -199,6 +204,8 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/styles/ui/themes/graphite.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
| assets/styles/ui/themes/nosferatu.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Dracula by Zeno Rocha and the Dracula Theme contributors, https://github.com/dracula/dracula-theme, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/catppuccin-mocha.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Catppuccin by the Catppuccin organisation, https://github.com/catppuccin/catppuccin, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/okibi.json | SolidCraft contributors (Kai Evans) | original SolidCraft theme file mapping SolidCraft's UI colours to the Okibi palette (dark) of the Okibi design system by Kai Evans (https://kaievns.github.io/okibi-akiri-design-system-spec/) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/akari.json | SolidCraft contributors (Kai Evans) | original SolidCraft theme file mapping SolidCraft's UI colours to the Akari palette (light) of the Okibi design system by Kai Evans (https://kaievns.github.io/okibi-akiri-design-system-spec/) | MIT OR Apache-2.0 |
| assets/styles/web/loading-logo.svg | SolidCraft contributors | Animation of the SolidCraft mark, built from the paths and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg; original animation | MIT OR Apache-2.0 |
| assets/styles/web/loading.css | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| crates/io/src/native_cad/fixtures/block.FCStd | SolidCraft contributors | original 10 × 20 × 30 mm test box saved by FreeCAD 1.1.4 run as an external program (`make_block_fcstd.py` beside it); no third-party geometry | MIT OR Apache-2.0 |
@@ -281,8 +288,57 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/icons/ui/design/svg/model/circle_tt.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/circle_ttt.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/coil.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/colored_components.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/colored_components_off.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/combine.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/component.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
### Render workspace icon drafts
Original SolidCraft drawings, Open for owner review; no runtime assignments.
| File | Author | Source | Licence |
|---|---|---|---|
| assets/icons/ui/design/svg/model/render_all_shots.svg | SolidCraft contributors | original Camera icon master (64 grid): stacked camera bodies with a lower-right play badge | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_appearance.svg | SolidCraft contributors | original Looks icon master (64 grid): shaded look sphere on the top corner of an accent-shaded cube | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_aspect_frame.svg | SolidCraft contributors | original Camera icon master (64 grid): wide rounded frame with two opposite corner brackets | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_backdrop.svg | SolidCraft contributors | original Studio icon master (64 grid): grey cube in front of a tall upright accent panel | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_cancel.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round stop badge | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_check.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round check badge | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_compare.svg | SolidCraft contributors | original Output icon master (64 grid): two grey pictures split by a vertical slider with opposed arrows | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_copy_look.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere in a square frame offset from a second frame outline | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_execute.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round play badge | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_export_image.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a large outward diagonal arrow at the upper right | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_exposure.svg | SolidCraft contributors | original Camera icon master (64 grid): split light and accent disc with plus and minus marks | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_fit_product.svg | SolidCraft contributors | original Camera icon master (64 grid): grey cube inside four corner brackets | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_frame_guides.svg | SolidCraft contributors | original Camera icon master (64 grid): rounded frame with a rule-of-thirds grid and four accent nodes | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_from_material.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere struck through above a plain grey slab | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_ground.svg | SolidCraft contributors | original Studio icon master (64 grid): grey cube on a slab with an accent contact patch | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_lens.svg | SolidCraft contributors | original Camera icon master (64 grid): concentric lens rings around an accent centre | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_library.svg | SolidCraft contributors | original Studio icon master (64 grid): row of three look spheres, middle one accent, on a shelf bar | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_light_area.svg | SolidCraft contributors | original Lights icon master (64 grid): slanted flat panel above a dotted accent stem | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_light_by_highlight.svg | SolidCraft contributors | original Lights icon master (64 grid): shaded sphere with a ringed highlight target and dotted stem arriving from above | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_light_point.svg | SolidCraft contributors | original Lights icon master (64 grid): round source disc with centre dot above a dotted accent stem | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_light_spot.svg | SolidCraft contributors | original Lights icon master (64 grid): cone source with curved rim above a dotted accent stem | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_lighting_presets.svg | SolidCraft contributors | original Studio icon master (64 grid): two stacked cards, the front one holding a light bulb | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_output_size.svg | SolidCraft contributors | original Output icon master (64 grid): picture frame with a diagonal double-headed dimension arrow | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_paint.svg | SolidCraft contributors | original Looks icon master (64 grid): flat brush with an accent tip, light ferrule and slate handle | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_pick_up.svg | SolidCraft contributors | original Looks icon master (64 grid): eyedropper with a grey bulb, slate crossbar and accent sampling tip | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_quality.svg | SolidCraft contributors | original Output icon master (64 grid): three rising bars, the tallest in accent | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_replace_all.svg | SolidCraft contributors | original Looks icon master (64 grid): grey sample sphere with an arrow to an accent sphere beside a second one | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_results.svg | SolidCraft contributors | original Output icon master (64 grid): stack of two pictures, the front one showing a landscape | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_same_look.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere and three cubes, the two matching cubes ringed | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_shot.svg | SolidCraft contributors | original Camera icon master (64 grid): camera body with a lower-right plus badge | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_shots_strip.svg | SolidCraft contributors | original Camera icon master (64 grid): strip of three small frames, the middle one in accent | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_solo_light.svg | SolidCraft contributors | original Lights icon master (64 grid): point source and dotted stem inside four corner focus brackets | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_sun.svg | SolidCraft contributors | original Lights icon master (64 grid): source disc with eight short radial rays | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_transparent_background.svg | SolidCraft contributors | original Output icon master (64 grid): grey cube on a checker tile | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_update_shot.svg | SolidCraft contributors | original Camera icon master (64 grid): camera body with a lower-right refresh badge | MIT OR Apache-2.0 |
### UI icon masters (continued)
| File | Author | Source | Licence |
|---|---|---|---|
| assets/icons/ui/design/svg/model/compute.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/configuration_table.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/conic.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
@@ -372,6 +428,21 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/icons/ui/design/svg/flat/shield.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/status_recovery.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/frame_member.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/render_environment.svg | SolidCraft contributors | original Studio icon master (64 grid): dome with a grey cube inside | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/style_shaded.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/style_wireframe.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/style_hidden_edges.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/style_translucent.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/select_faces_edges.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/select_bodies.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/select_features.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/select_parts.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/select_components.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/select_sketch.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/display.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/grid.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/ground_shadow.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/ambient_occlusion.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/placeholder.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/plane.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/plane_angle.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
@@ -514,6 +585,18 @@ Editable drawing code, native projects and shipped exports are collected under `
| docs/book/src/og.png | SolidCraft contributors | copy of assets/branding/web/source/og.png staged by cargo xtask book (gitignored) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/path_flange.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| docs/book/src/generated/hem.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/hem.json`; gitignored), original sheet metal demonstration | MIT OR Apache-2.0 |
| docs/book/scripts/hem.json | SolidCraft contributors | original parametric recipe for an 80 × 50 mm sheet with an 8 mm returned edge, authored for the command guide | MIT OR Apache-2.0 |
| assets/styles/ui/themes/clay.json | SolidCraft contributors | original SolidCraft theme file (Clay, light): warm paper palette chosen for SolidCraft | MIT OR Apache-2.0 |
| assets/styles/ui/themes/clay-dark.json | SolidCraft contributors | original SolidCraft theme file (Clay Dark): warm charcoal palette chosen for SolidCraft | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/cone.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/locales/ar.json | Abdullah (A-AL-ANAZI) | Original Arabic translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
| assets/locales/he.json | SolidCraft contributors | Original Hebrew translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
| assets/styles/ui/themes/cga.json | SolidCraft contributors | original SolidCraft theme file using the 16 colours of the IBM Color Graphics Adapter (black, cyan, magenta, and white of its high-intensity palette 1 for the interface) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/1-bit.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
| assets/styles/ui/themes/1-bit-dark.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
| `assets/locales/pending/navbar-customisation.json` | SolidCraft contributors | Original navigation bar editor translations | MIT OR Apache-2.0 |
| `assets/locales/pending/navbar-slots.json` | SolidCraft contributors | Original navigation slot translations | MIT OR Apache-2.0 |
Agents: start from the router in [AGENTS.md](AGENTS.md), which points to a short skill in [`skills/`](skills/README.md) for each kind of work. This page stays the full human guide.
SolidCraft is an open-source parametric 3D CAD app written in pure Rust.
You sketch on a plane, constrain the sketch, turn profiles into solids, and
change a parameter to watch the timeline rebuild. It runs as a desktop app (Windows, Linux), in
change a parameter to watch the timeline rebuild. It runs as a desktop app (Windows, macOS, Linux), in
the browser (wasm + WebGPU/WebGL2), headless from a CLI, and as an MCP server that AI agents can
model with.
@@ -11,7 +13,7 @@ build it, how the code is laid out, the few rules that aren't negotiable, and wh
needed most.
Before choosing a task, check the [command inventory](docs/features.md) and
[roadmap](ROADMAP.md) for implemented capabilities and current priorities.
[roadmap](docs/roadmap-detail.md) for implemented capabilities and current priorities.
Questions, ideas or want to say what you're working on? Join the [Discord](https://discord.gg/FtU4NQNVDH).
@@ -130,8 +132,11 @@ The [architecture page](docs/architecture.md) has the layer table. The short ver
- Every call into truck goes through `solidcraft_kernel::guard`.
- Every crash fix comes with a regression test.
5. **Everything is a command, with a test.** A new feature means a `CommandSpec` in
`crates/engine/src/cmd/`, engine tests that measure the result (volume, area,
`crates/engine/src/cmd/` with input and output schemas (see
[Adding a command](docs/book/src/dev/commands.md#adding-a-command); a test warns about a
command without them, and will fail it once required), engine tests that measure the result (volume, area,
face/edge/vertex counts against a value you can derive), and for UI work a scenario (below).
A command on the toolbar also ships with its help tooltip (step 6 of Adding a command).
6. **One set of camera controls.** Every page or canvas that shows a view (the modelling
viewport, the Render preview, and any new page such as a flat pattern or drawing) calls
`navigation::handle` (`crates/ui-egui/src/navigation.rs`) for its camera input, with
@@ -190,9 +195,11 @@ in the PR. `cargo xtask test` builds the tests with it and then runs every test
shared bound on top-level test/side commands for `cargo xtask test` and `cargo xtask ci`.
For example, `SOLIDCRAFT_TEST_JOBS=2` allows two commands at once; the sequential wasm/doc
worker consumes one of those slots. Every binary and side check still runs after failures.
Without the variable the existing concurrency is unchanged. `RUST_TEST_THREADS`,
`ACTION_MATRIX_JOBS` and Cargo's build-job setting remain independent caller overrides;
this limit does not choose their values or change test filters, timeouts or baselines.
Without the variable the existing concurrency is unchanged. With it set, each test binary
inherits `RUST_TEST_THREADS` and `ACTION_MATRIX_JOBS` of `max(1, cpus / jobs)` — the machine's
parallelism shared between the binaries in flight — unless you set either one explicitly;


*A Raspberry Pi 4 case from an empty design: a sketch dimensioned with the board's parameters, extrude, fillet, shell, the rim rounded in one pick, an M2.5 standoff patterned on the 58 × 49 mm hole rectangle, port cut-outs, vent slots, "Pi 4" engraved in DejaVu Sans Bold and a PETG material defined in the design, with its mass in Properties, then height 24 → 32 mm in Parameters and it all rebuilds. 9× playback. [Full video](https://github.com/tangency-labs/solidcraft/raw/refs/heads/main/docs/screenshots/demo-0.8.mp4), recorded with the [demo script](examples/demo/record_ui.sh).*
*A Raspberry Pi 4 case from an empty design: a sketch dimensioned with the board's parameters, extrude, fillet, shell, the rim rounded in one pick, an M2.5 standoff patterned on the 58 × 49 mm hole rectangle, port cut-outs, vent slots, "Pi 4" engraved in DejaVu Sans Bold and a PETG material defined in the design, with its mass in Properties, then height 24 → 32 mm in Parameters and it all rebuilds. 6× playback with the idle pauses cut. [Full video](https://github.com/tangency-labs/solidcraft/raw/refs/heads/main/docs/screenshots/demo.mp4), recorded with the [demo script](examples/demo/record_ui.sh).*
## Download
- **Windows:** the [installer][windows-msi], or `winget install bherbruck.SolidCraft`.
- **Windows:** the [installer][windows-msi].
- **macOS:** the [disk image][macos-dmg] (Apple silicon and Intel).
- **Linux:** the [AppImage][linux-appimage], or `sudo snap install solidcraft` ([Snap Store][snap-store]).
- **Web:** [try it in your browser][web-app], no install.
@@ -74,7 +74,7 @@ other is a tracked bug.
- **Parameters and configurations** with unit-aware expressions.
- **Assemblies** with joints, motion studies, contact sets, interference and mass properties.
- **Sheet metal** with flanges, hems, fold/unfold and flat patterns, and plastic enclosure features.
- **Files:** STEP, IGES, 3MF, STL, OBJ and glTF in and out where supported, URDF export, `.solidcraft` designs.
- **Files:** STEP, IGES, 3MF, STL, OBJ and glTF in and out where supported, URDF export, `.sc` designs (`.solidcraft` files from earlier releases still open).
- **Desktop app, browser app, headless CLI and MCP server**, all running the same commands.
The full list is in [docs/capabilities.md](docs/capabilities.md) and [docs/features.md](docs/features.md);
@@ -96,8 +96,10 @@ More commands, the Windows cross-build and the browser build are in [docs/buildi
`solidcraft-cli mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server. Headless it
runs its own design session, or with `--connect` it drives the running app so you can watch.
Claude Code: `claude mcp add solidcraft -- solidcraft-cli mcp`. Tools, access rules and an
example session: [docs/mcp.md](docs/mcp.md).
Claude Code: `claude mcp add solidcraft -- solidcraft-cli mcp`. If a terminal doesn't find
`solidcraft-cli` (older Windows installers don't put it on `PATH`), give its full path (by default
`"C:\Program Files\SolidCraft\solidcraft-cli.exe"`). Tools, access rules and an example session:
[docs/mcp.md](docs/mcp.md).
## Scripts
@@ -119,11 +121,18 @@ the clean-room and licence rules, and where help is wanted most. Open work is in
SolidCraft's continuous integration runs on [Blacksmith](https://www.blacksmith.sh), which
sponsors its CI minutes.
## Disclaimer
The files in this repository are provided as is, without warranty, liability or indemnity, and
Tangency Labs and the contributors accept no responsibility for any harmful or unlawful use of
them: read [DISCLAIMER.md](DISCLAIMER.md).
## Licence
MIT OR Apache-2.0, at your option. Developed under the
[clean-room contribution rules](AGENTS.md); third-party code and assets retain their
documented licences and attribution.
documented licences and attribution. The licence covers the code, not the name and logo: see
[TRADEMARKS.md](TRADEMARKS.md) for how to name a fork or a product built on SolidCraft.
SolidCraft is independent. Behaviour is designed from public documentation, general CAD
conventions and user feedback. Reference tests use analytic values and open-source tools.
Status as of **2026-10-14**. SolidCraft supports parametric modelling (sketch →
features → parametric timeline) as an open, pure-Rust, agent-drivable application.
## At a glance
| Question | Answer |
|---|---|
| Where are we? | **M0 and M13 done; M1, M2, M5–M7, M9, M10, M14 in progress.** Sketch with solver and the full SKETCH create/modify set; extrude, revolve (two sides, symmetric, to object), fillet/chamfer (variable radius, chord length, a radius per edge, corner patches), holes, threads (cosmetic and modelled), patterns, mirror, shell (inside/outside/both), draft, loft, sweep, coil, split body and split face, delete face, construction planes, axes and points; persistent naming of faces and edges; parameters and configurations; components, joints, motion studies, exploded views; sheet metal and a plastic enclosure subset; STEP/IGES/3MF/STL/OBJ/DXF; FreeCAD/Rhino import; autosave and crash recovery; desktop and browser (wasm) app, CLI, MCP server. |
| Features | Every registered command by area — [docs/features.md](docs/features.md) (`cargo xtask features`); registry breadth does not establish workflow or geometric depth |
| Reference tests | [Current case count and results](docs/oracle.md): public cases in `tests/oracle/` with analytic or FreeCAD-computed expected values, checked by the reference-test job in [CI](.github/workflows/ci.yml). |
| Tests | Solver, profiles, kernel booleans/blends/measures, expressions, timeline, persistent naming, file formats and migrations, assemblies, sheet metal, engine end-to-end, hostile-input fuzz over every command and headless UI scenarios. `cargo xtask ci` reports the current passing counts. |
| Time to a useful alpha (M0–M6) | ≈ **82 agent hours** remain (sum of the M1–M6 estimates below) |
Optional GPU viewport ambient occlusion is available in View settings and Graphics preferences,
with persistent settings, Performance/Quality graphics presets (Quality combines AO, Fine tessellation and adaptive 2× supersampling; manual combinations show Custom), and the
`view.ambient_occlusion` and `view.graphics_preset` commands. See [display settings](docs/ambient-occlusion.md).
## Scope (owner decision, 2026-10-07)
The issue batch adds preservation of blind pockets in through cuts, associative projected
offsets and plastic face placements, XZ (+Y normal) with a v6 migration preserving older
| M1 | Sketch depth: command coverage implemented — projection/intersect/include as linked geometry (auto-project on faces, lost-reference warnings, break link), full SKETCH create/modify set (slots, polygons, tangent arcs/circles, ellipse, fit/control splines, conic, text, trim/extend/break, fillet/chamfer, offset, mirror, patterns, move/scale, blend curve, centerline), constraints incl. curvature and polygon with over-constraint refusal, Shift-picked line midpoints in Horizontal/Vertical using persisted points and ordinary constraints (#2087), driven/arc-length/linear-diameter dimensions, AutoConstrain, DXF/SVG insert, 3D sketch curves (Project To Surface, Intersection Curve, Isoparametric Curve, Spun Profile), exact free-form profiles (B-spline/NURBS edges, any spline degree, curves cut by crossings), sweeps and pipes along 3D sketch curves, canvases and decals, curvature combs. Exact free-form Extend and certified associative offsets now preserve geometry and IDs through edits, deletion and reopen. Occurrence-frame 3D wires now store explicit coordinate spaces and retain points and analytic fits through moves, refresh, undo and reopen. Constructive sketch solving (Owen 1991, Fudos & Hoffmann 1997) ahead of the numerical solver: points and rigid clusters are placed on the branch the sketch had, so dimension edits and drags keep their solution; edits that would turn a sketch over are refused; drags re-solve only the sketch per frame. Remaining workflow repairs: mixed offset junctions, legacy natural-spline seams, and damaged legacy wire links lacking frame provenance | in progress | 4 |
| M2 | Robust modelling: coplanar booleans in the kernel, holes (placed on faces, at distances from reference edges, and on sketch points), threads (cosmetic, then modelled), patterns, mirror, shell, draft, split, construction geometry | in progress | 16 |
| M1 | Sketch depth: command coverage implemented — projection/intersect/include as linked geometry (auto-project on faces, lost-reference warnings, break link), full SKETCH create/modify set (slots, polygons, tangent arcs/circles, ellipse, fit/control splines, conic, text, trim/extend/break, fillet/chamfer, offset, mirror, patterns, move/scale, blend curve, centerline), constraints incl. curvature and polygon with over-constraint refusal, Shift-picked line midpoints in Horizontal/Vertical using persisted points and ordinary constraints (#2087), drawing snaps to where two lines, circles or arcs cross (#4967), driven/arc-length/linear-diameter dimensions, AutoConstrain, DXF/SVG insert, 3D sketch curves (Project To Surface, Intersection Curve, Isoparametric Curve, Spun Profile), exact free-form profiles (B-spline/NURBS edges, any spline degree, curves cut by crossings), sweeps and pipes along 3D sketch curves, canvases and decals, curvature combs. Exact free-form Extend and certified associative offsets now preserve geometry and IDs through edits, deletion and reopen. Occurrence-frame 3D wires now store explicit coordinate spaces and retain points and analytic fits through moves, refresh, undo and reopen. Constructive sketch solving (Owen 1991, Fudos & Hoffmann 1997) ahead of the numerical solver: points and rigid clusters are placed on the branch the sketch had, so dimension edits and drags keep their solution; edits that would turn a sketch over are refused; drags re-solve only the sketch per frame. Remaining workflow repairs: mixed offset junctions, legacy natural-spline seams, and damaged legacy wire links lacking frame provenance | in progress | 4 |
| M2 | Robust modelling: coplanar booleans in the kernel, holes (placed on faces and on sketch points), threads (cosmetic, then modelled), patterns, mirror, shell, draft, split, construction geometry | in progress | 16 |
| M3 | General fillets and chamfers (curved edges, chains, vertex blends); saddle rolling-ball surfaces now use continuous seam sections and arc-length fitting with shared contact boundaries and bounded accuracy checks; tiny rim edges remain selected instead of nearby wall seams when filleting (#2498) | in progress | 30 |
| M4 | Files: STEP import (done — [docs/step-import.md](docs/step-import.md)), FreeCAD/Rhino solids through cadmpeg (done, desktop, browser and CLI), 3MF export and 3MF/STL import as mesh bodies (done), 3MF with B-rep face tags ([docs/formats/3mf-face-tags.md](docs/formats/3mf-face-tags.md), done), constant-section parametric mesh reconstruction, DXF sketches, OBJ import | in progress | 6 |
| M5 | Persistent naming of faces and edges (docs/book/src/dev/naming.md): faces named from their exact kernel-reported history (sketch curve sides, caps, revolve sections, primitive sides, blends, shell inner walls, pattern/mirror copies; split pieces told apart by the faces around them, lumps of a cut body named after their faces), edges by their faces; fillets/chamfers, sketches on faces, shell/draft/offset/replace faces, hole placements, joint origins and face appearances resolve by name first and warn when the entity split or vanished: done. sheet metal edges by base sketch line and flange side, shell inner faces, sweep and loft roles: done. kernel-reported face history: done (merged faces answer to every name; operations without history yet fall back to geometric names, flagged unverified; format 14 keeps older designs' geometric names). Left: history through sheet metal, plastic, surface and emboss features; FaceSource certificates through history | in progress | 20 |
| M6 | UI depth: selection feedback and origin (done), timeline editing (done: hover and selection highlight modifier faces, including fillets and chamfers; edit feature, history marker drag, dependency-checked single and multi-item reorder, suppress, rename, delete with dependents, re-resolved references; redefining selections refreshes persistent edge/face references while retaining feature identity and dimension names; responsive feature history with width-driven icons/names/title, snapping width resize, bottom/left/browser-stack docking, shared-column collapse, rollback bar, multi-selection and persistent folders), right-docked dialogs (done), live previews for every feature dialog (done), keyboard-first commands with on-canvas value boxes and sketch inline dimensions (done), drag manipulators with automatic join/cut (done: extrude incl. two sides, fillet, shell, hole depth, move, pattern spacing, revolve rotator, extrude taper), dark theme (done), right-click menu with Repeat (done), measure tool (done), section analysis (done), selection filter (done), drag-solve of sketch points (done); appearance controls now preserve body/face/component targeting, named transparent looks, reuse/search and override removal; Browser Expand All / Collapse All (`browser.expand_all`, `browser.collapse_all`) on the design and component menus; preferences | in progress | 6 |
| M7 | Thin extrude (walls along open or closed curves: centre, inside, outside or two sides; mitred corners, crossing walls joined; #721), sweep, loft (incl. to a point), rib and web (to next / depth), emboss (planar faces and exact radial emboss/deboss of bounded orthogonal profiles on complete exterior cylinders/sleeves), thread (cosmetic, and modelled M3–M20 outside and inside: the ISO profile screwed in as a faceted cut), coil (smooth B-spline helical walls), pipe, pattern on path, patterns of bodies and components, replace face (parallel), align, remove, feature copy/paste: done. Unique eligible cylindrical targets are inferred for new Emboss features. Left: general curved-face emboss and exact (unfaceted) modelled threads | in progress | 12 |
| M8 | Surface workspace (#772). Done: Patch (sketch regions; edge loops filled G0, plus `crown`), Stitch, Thicken (planes and cylinders), Extend (straight edges of planar surfaces), Trim (by a plane, a face or sketch curves swept onto the surface, keeping a side or the picked regions; mutual trim of two surfaces, extended to meet and stitched, #767), Delete Face (healing), Boundary Fill, zebra/draft/curvature-map analysis, Surface Extrude/Revolve/Sweep/Loft from open and closed curves, Offset, Unstitch and Reverse Normal; the continuity-driven core (`kernel/src/bridge.rs`, G0–G3 Hermite rows) with **Blend** (`surface.blend`, #764: edge chains along one or several faces, closed rims paired seam to seam or from picked seams, a dialog), **Match** (`surface.match`), **Boundary** (`surface.boundary`, #763 — two sketch curves exact with G0–G3, three or four as the flat patch of their loop, and curve networks: Coons and Gordon surfaces through sketch, 3D-sketch or edge curves, a point for a three-sided side, G1/G2 against the faces along one direction's edges, joined into their body on request; G1/G2 on two sides that meet at a corner waits for the Gregory-type corner), **Fair** (`surface.fair`, CV fairing), **continuity analysis** (`inspect.continuity`, #770; a toolbar tool with edge markers and a legend, and curvature combs along face isocurves) and a multi-section **loft** with per-end continuity and crown, and guide rails on solid and surface lofts (one or two, scaling or stretching the sections between; #201). Also in the Class-A pass: G1…G4 blend curves in the sketch, chord and variable-radius fillets on curved edges (with honest refusals where the fit cannot hold), and a G2 corner patch where filleted edges meet (kernel; the command refuses until the patch's sections can be sewn to the blends' contacts). Left: bias/form-factor fillet sections (design recorded), a Gregory-type corner, surface Fillet between separate surface bodies (#769), Ruled (#762), Untrim (#761), curved-face Extend (#760), and every remaining #757–#772 item | deferred | — |
| M9 | Components and assemblies: New Component / Create Components from Bodies, active component, per-component origin/bodies/sketches/timeline entries, occurrences with transforms, Copy/Paste vs Paste New, nested Browser tree with visibility and isolate; Ground, Move/Copy of occurrences, joints (rigid, revolute, slider, cylindrical, pin-slot, planar, ball) by joint origins with limits and Drive Joints, closed kinematic loops (four-bar, slider-crank: the loop's other joints follow the driven one on its branch, and driving stops where a loop can no longer close), cams (`joint.cam`: a revolute cam drives a slider or revolute follower along a closed sketch profile or the cam's edges, knife, roller or flat followers, worked out again when the profile changes; drives, studies, closed loops, contact sets), As-built Joint, Rigid Group, Contact Sets (driving and motion studies stop where bodies in a set would pass through each other), Motion Study (joint values keyed along a timeline, playback, export of occurrence positions as JSON/CSV), motion analysis (joint values and a point's path charted over a study as value, velocity and acceleration; the parts that collide along a study, with the steps where they do), Exploded views (automatic or by hand, shown without changing placements, and exported as an animation coming apart and back together); interference, per-component physical properties; STEP with assembly structure; URDF export for robot simulators (links with meshes, mass and inertia; joint chains whose values match Drive Joints; docs/urdf.md). Physical properties now measure material-aware component/occurrence placements, descendants and repeated instances, including pending moves and body reassignment offsets; contact geometry and automatic exploded centres respect those offsets. Groundwork done: component tree, occurrences with transforms (instances, Paste New), world placement, active component as authoring frame, ground, Capture/Revert Position, moving bodies and sketches. UI: ASSEMBLE panel dialogs for New Component, Joint (snaps on faces, edges, circles and vertices; animated motion preview; offsets, flip, limits), As-built Joint, Joint Origin, Rigid Group, Drive Joints (live slider), Motion Link, Cam and Interference (overlaps shown red); joints in the timeline and the Browser | in progress | 32 |
| M10 | Sheet metal: flange (base, edge, contour), hem, unfold/refold, convert, flat pattern + DXF, rules: done. PLASTIC enclosure subset: Boss, Rib, Web, Lip/Groove, Snap Fit, Rest, Plastic Rules (library, edit, assign; rule draft and clearance as feature defaults): done, tested against hand-computed geometry. UI: SHEET METAL and PLASTIC tabs with dialogs (live preview, Edit Feature, pre-selection) for Flange, Hem, Unfold/Refold, Convert, Sheet Metal Rules, Create Flat Pattern (flat view with bend lines, bend table CSV/JSON and DXF export with exact circle/arc/line cutouts), Boss, Lip/Groove, Snap Fit, Rest, Manage and Assign Plastic Rules; Fold (bend along a sketch line); flanges meeting at a corner closed at any angle with the rule's gap; edge flanges with their own angle and height per edge in one feature; corner reliefs (notch, round, rectangular, tear) chosen by the rule; Convert to Sheet Metal from a solid or shelled body by its bend edges (flat faces, sharp bends) | in progress | 36 |
| M11 | 2D drawings | deferred | — |
| M12 | Release: cross-platform packages, verification and deliberate publication implemented; original current-UI demo refreshed. macOS Developer ID signing and notarization are in the release workflow (#5749; they switch on when the Apple secrets exist). Windows installer signing and broader performance work remain | in progress | 16 |
| M14 | Configurations: a table of rows (parameter expressions and feature suppressions per row) switched with config.activate (undoable, all or nothing), kept through parameter renames and feature deletes (config.configure, config.table, config.column/row/activate). Per-row body materials and appearances (library looks, custom colour/opacity and clearing overrides), undoable activation and save/reopen: done. Ordered typed configuration rules, atomic validation and structured rule editing: done. Component appearance and certified semantic face style columns, atomic preview/apply and independent-copy remapping: done. Exact constructed sketch-side and immutable imported-face lineage now preserve legacy names and reject retargeting. Left: ambiguous shifted/two-sided caps | in progress | 4 |
| M13 | Never lose work: atomic saves (temporary file, flush, rename; a crash at any moment leaves the old file or the new one) with the replaced version kept as `<file>.bak`; autosave of unsaved changes to the recovery folder every 5 minutes (preference) and after big operations, only when something changed; "Recover unsaved design?" on the next launch (`doc.recovery_list`, `doc.recover`, `doc.recovery_discard`); a kill-mid-save test (process killed while saving a 0.5 MB design 25 times). File format: `"format": "solidcraft/12"`, older formats upgraded step by step (`doc::format`), newer ones refused with a message, out-of-range numbers and broken placements refused; a corpus of designs saved by M0, components-era, joints-era and current builds (apps/solidcraft-cli/tests/corpus) that must open and evaluate as saved; damaged and hostile variants (truncation, bit flips, hostile values, removed keys, repeated features, deep nesting) never panic | done | 6 |
| M15 | Frames and weldments (#4003): structural members along 2D and 3D sketch lines, butt and mitre corners, trim to faces and bodies, end caps, gussets, cosmetic welds and a cut list. Done: the profile library (`frame.profile.list`, `frame.profile.properties`, `frame.profile.insert`): bar, round bar, rectangular and round tube, angle, channel and I-beam, each SolidCraft's own parametric definition with exact fillet arcs and exact section properties (area, centroid, second moments), tested against hand-computed values; members (`frame.member`): one body per straight line of a 2D or 3D frame sketch, following parameter edits, with a built-in profile (its dimensions parameter expressions) or a profile sketch (its drawn material by alternating nested loops), anchor, angle and mirror; each member's name kept with its line. Left: corners, trims, end caps, gussets, welds, cut list, FRAME tab and dialogs | in progress | 60 |
M6 navigation preferences include **Touchpad**: Shift + pointer movement pans, Alt/Option +
pointer movement orbits without a mouse button, and vertical scrolling zooms. Other presets
retain their saved two-finger scroll preference; camera motion is available through
`view.navigate` and modifier-aware `ui.move` automation.
With the **Fusion** preset and two-finger pan enabled, two fingers pan and Shift + two fingers
orbit freely, with pinch zoom and mouse bindings unchanged. The shared navigation handler
applies the same gestures to model and Render views and blocks orbit on flat pages.
editable, and Automatic size returns to the face's suggestion. Cosmetic/modelled and
full/partial-length options remain available. This does not add tolerance classes or
left-hand threads.
- New Body extrusion and face-offset previews show completed intermediate geometry while a
drag continues, then catch up to the exact final value. Changes to the selected geometry,
operation or feature still discard obsolete results. Distance arrows and their value boxes
follow the current frame's input.
- Large fine modelled threads use pitch-based helix resolution and tighter faceted
reconstruction fallbacks, preserving thin crests and closed shells on large shafts.
## Physical material data
- Physical material card validation (#923/#922) provides typed SI mechanical and
thermal data, per-property sources/conditions and isotropic/orthotropic stability
checks through read-only `material.properties.validate`. It does not assign or
persist properties, provide a solver or enable rendering; see
[the contract and staged follow-ups](docs/physical-material-cards.md).
- Directional elasticity (#994) queries the validated card in material-local axes
through read-only `material.properties.directional`, with source/condition review.
It does not assign body frames, predict structural behavior or enable a solver.
## Library foundations (#785, #786, #787)
- Local scalar libraries (#846) inspect/export bounded portable catalogs and copy exact
selected appearance/material revisions into existing design definitions in one undo step.
Appearance offers local loading/search/export; physical materials use existing commands.
This slice is independent of the render stack and does not change the document format.
See [the contract and limits](docs/book/src/dev/local-libraries.md).
- Proposed phases cover offline bundled/project/user packs, physical materials and
appearances, editable wood mapping, and parameterized component/assembly families.
Stable identity/revision, copied design definitions, provenance and resource limits
apply from the first implementation. Existing material, appearance and standard-part
commands remain the application paths. See [the proposal](docs/book/src/dev/libraries.md)
for source/license review and acceptance gates. This is planning only: pack loading,
texture rendering and expanded component families are not implemented by this entry.
## Render foundation
- Original starter appearances (#1722): 34 opaque recipes through existing commands, with six explicitly unavailable glass recipes and preserved legacy looks; see [the contract](docs/render-starter-appearances.md).
- Bounded linear film accumulation (#1696) supplies caller-admitted mean/variance storage and complete-pass views; actual progressive consumers remain separate. See [the film contract](docs/render-film.md).
- Definition-local visual mapping (#1214) supplies a bounded numerical frame, physical repeat coordinates and a deterministic one-time grain-axis suggestion. It adds no saved record or texture consumer; see [the mapping contract](docs/render-visual-mapping.md).
- Explicit texture normalization (#1218) produces completed float RGBA with
role-aware linear color/data semantics, bounded matrix/TRC ICC conversion and
opt-in EXIF orientation. It retains high-precision raw samples and records lossy
gamut clipping only when explicitly requested. It is not a renderer/resource
consumer; see [the supported policy and limits](docs/render-texture-normalization.md).
- Opt-in opaque PBR (#1046) connects persisted surface parameters to the integrated
product viewport and CPU PNG, using a fixed direct-light studio. Geometry and mass
stay unchanged. HDRI, transmission and textures remain separate gates.
See [the consumer and limitations](docs/render-opaque-pbr.md).
- Combined integration is tracked in #842, with independent scalar libraries in
#863 and richer PBR/material-card transport in #1086. See [phase boundaries and validation gates](docs/render-integration.md).
Render/library consumers use the merged asynchronous picker (#1127). Material-card
UI (#1094), texture mapping and HDRI lighting remain explicit follow-ups.
- R4a adds bounded CPU product-image jobs, cancellation and atomic PNG export through
engine commands, the Render panel, CLI and persistent MCP. Immutable captures and
results stay outside persistence/undo; native workers and cooperative browser tasks
share the same work protocol. See [jobs, limits and export](docs/render-jobs.md).
- R3 adds an overlay-free product scene and the integrated Render workspace, with
runtime GPU host capabilities, per-document drafts and named camera/background
controls. Design keeps its camera and drawing policy. Raster transparency remains
approximate; PBR and environments are later slices. See
[workspace and scene contracts](docs/render-workspace.md).
- R2 adds bounded named render setups in document format 21 and the read/save
and selected occurrence frames; cancel and successful edits preserve one-step undo history.
Placed feature edits preview through the edited timeline entry, and cylindrical Emboss guides
use the sketch's actual target, author or uniquely placed third-component frame.
Revolve rotators follow the actual signed axis and support symmetric and two-sided extents;
dragging a To Object extent converts its derived value to an explicit angle. Legacy reversed-axis
symmetric geometry is preserved through format 11's explicit legacy convention. New revolves
use the sketch-normal direction for each selected profile, including To Object and curved
target trimming, independently of the selected axis sign.
- Cylindrical emboss/deboss wraps bounded orthogonal line profiles, including holes, concave
outlines and nested islands, onto complete exterior cylinders or coaxial sleeves with exact
radial walls. New features infer a unique eligible cylinder when the face field is empty;
ambiguous targets require an explicit face. Saved target
and author occurrence paths retain the chosen placements; missing/split faces refuse rather
than retarget. General free-form profiles, seams, stepped cylinders and other curved surfaces
remain unsupported. STEP round-trip and independent radial/volume checks cover this subset.
- Shell and offset faces work on bodies of planes, cylinders, cones, spheres and tori (convex or not:
filleted boxes, bosses, domes, countersinks; corners where four or more planes meet, such as a
floor falling to a drain, as long as the corner's edges are all convex or all concave); draft on convex planar bodies and on walls (planes, cylinders → cones) between caps; lofts through three or more sections are smooth (B-spline sides through every section), two
sections ruled. Circular-meridian barrels retain curved inner walls through either axial
opening (#2421). Solid lofts preserve holes through sections with matching hole counts
(#2435), including inner walls and caps with openings; a hole cannot appear or disappear
between sections, and sections with holes cannot collapse to a point.
- Sketch corner tools: fillet and all three chamfer modes pick a connected corner even when
its endpoint overlaps the fixed origin or a loose point. Ordinary drawing still snaps to
the origin; clicked UI regressions verify geometry, closed profiles and undo.
islands share the same boundaries in flat preview, DXF and unfolded solids. Detached material
pieces survive unfold/refold as one logical sheet group, including split flanged sheets.
Cut-outs made in the flat pattern cross bends at any angle or curved: their edges fold to
helices and curves on the bend, their walls square to the sheet. Free-form bases use bounded
sampling.
Parallel prismatic partial-depth cuts retain pocket side and depth through transfer and
unfold/refold, with separate pocket contours in preview and DXF. Planar Intersect preserves
exact holes and retained islands, including the union of multiple selected profiles, and
records complementary depth layers as pockets while retaining the stock gauge. Unsupported
tapered or bend-crossing machining refuses atomically instead of restoring material later.
Sketches attach to a sheet's flat faces (base and flanges, either side) by panel, in the
flat pattern's coordinates, and follow it through rebuilds and unfold/refold.
A flange on another flange's far edge (a return) follows that flange's closed end and keeps
clear of the flange beside it, and the returns of one feature are mitred toward each other
with the rule's gap where their flanges meet at a corner; returns of separate features, and
flanges on returns, are not closed toward each other.
- Rolling-ball fillet accuracy: independent offset-cylinder tee references check surface
distance, actual trimmed tessellation, supporting-cylinder contacts and tangency at two
scales. The measured trimmed-part error is below 0.0001 mm; this is an analytic geometry
check. Under-resolved fits return an error.
- Contact driving checks the trajectory between clear endpoints, including linked joints,
playback and sparse motion exports. Checks use bounded tessellation samples, not exact
continuous collision detection; local thin walls and small details remain limitations.
See [contact-set limits and recovery](docs/contact-sets.md).
## Assembly dragging
The design's first part is grounded when it is made, and a left-button drag repositions a free part (`occurrence.drag`: one undo step, exact occurrence paths including repeated jointed definitions, any floating-chain member carries the chain, grounded parts and joints hold where present, Escape cancels).
## 3D sketch bridges
3D sketch drawing snaps to points on other visible sketch planes without flattening them onto
the active plane. Bridge endpoints capture their source positions; associative endpoint links,
construction-plane selection of 3D wires and loft guide rails remain separate work.
Feature dialog header chevrons collapse and restore the form without discarding selections
or edited values, including the Appearance panel.
## Construction on 3D paths
Plane Along Path has a viewport path selector, fractional position, live preview and timeline
editing. Construction planes and points resolve drawn 3D wires without applying their sketch
plane a second time. Curved paths use sampled segments; multi-curve chains, absolute distance
and reference-object placement remain separate work, as do loft rails and centerlines.
## Design properties (#1015)
- Designs carry project, part number, revision, author, organization, description and licence
in an optional `metadata` object (File → Design Properties…, `document.metadata.*`,
`inspect_design`). STEP and IGES headers and 3MF model metadata take the fields their format
has a place for. Other tools keep their own data under a reverse-DNS key, kept as it is
through open, edit and save and never exported. See [design properties](docs/book/src/guide/files.md#design-properties).
Per-component properties (a part number per component) can follow with the same type.
## Document-opening performance
- Desktop STEP import prepares consecutive independent solids in bounded batches with up
to eight workers, retaining serial traversal, result order and persistent source identities.
The web path remains serial. Original multi-solid fixtures show 64–81% lower complete
import times; single-solid assembly parts are not scheduled across nodes. See
[measurements and limits](docs/perf.md#parallel-step-body-preparation-2026-10-12).
- Design containers (#303, phase one) separate and deduplicate STEP, raw images and
lossless binary mesh arrays while keeping plain JSON as a supported Save As/export
format. Generated-mesh storage measurements reduce serialization and decoding work;
a seekable reader defers original blobs until after provisional display.
See [container format](docs/design-container.md) and [performance measurements](docs/perf.md).
- Warm saves with explicit `cache: true` can include a versioned native B-rep cache through SolidCraft-owned topology
and geometry records, with a separate truck adapter and bounded binary reader. Exact
face-source certificates and operation history survive codec round trips. A provisional
picture can be generated before original blobs are read; editing and measurement wait
for authoritative reconstruction. Cached metadata binds preview and reconstruction to
the same document despite concurrent file writes. Default saves omit this cache and never force cold
evaluation or meshing; explicit `preview: true` remains a separate warm-mesh option.
Keys, resource budgets and document/revision guards reject incompatible, hostile or
stale results. A second modelling kernel still requires its own tested adapter.
- Warm saves with explicit `cache: true` can also carry a bounded PNG thumbnail from already cached meshes.
Home reads it without rebuilding the design when its local picture is missing;
mismatched or malformed thumbnails fall back to normal generation. The image is
kernel-independent and saving never forces cold meshing to produce it.
- Parameters with units and expressions, and configurations.
- Faces and edges keep their names through edits, so references survive changes.
- Components, assemblies, joints, motion studies and exploded views.
- Sheet metal with flat patterns and DXF export, and a plastic-enclosure subset.
- A Render workspace with GPU rendering, studio lighting, materials and image export
(first release in 0.22.0).
- Import and export of STEP, IGES, 3MF, STL, OBJ and DXF, and FreeCAD and Rhino files.
- Autosave and crash recovery, and the browser build of the same app.
The full list of commands is in [docs/features.md](docs/features.md). Release notes for each
version are on the [releases page](https://github.com/tangency-labs/solidcraft/releases).
## In progress now
Each feature below is a [milestone](https://github.com/tangency-labs/solidcraft/milestones) holding
the issues for it, so the count shows how far along it is. Counts are as of 2026-10-25, one per
feature or request. Work merged on a feature branch (`drawings`, `render`) counts as built but
reaches the app only when that branch merges. They are rough, not a promise.
- **[Rendering & materials](https://github.com/tangency-labs/solidcraft/milestone/17)**: in progress, 9 of 34 pieces done, 3 of them built only on the `render` branch and in the app when it merges. The basic Render workspace is already in releases (0.22.0); the interface rework (#6281) and the material and light libraries are what is left.
- **[Drawings](https://github.com/tangency-labs/solidcraft/milestone/8)**: in progress, 3 of 21 pieces built (2 in releases, 1 on the `drawings` branch), and 3 more partly built there (sheets and borders, bill-of-materials types, the paper scene). Not in the app yet: it lands when the `drawings` branch merges. Today you can export the current view as a DXF.
- **[Sheet metal](https://github.com/tangency-labs/solidcraft/milestone/15)**: in progress, 1 of 30 requests done. Flanges, hems, unfold and flat patterns already work; the 30 are extensions, mostly the per-edge flange options (segments, miters, partial flanges, reliefs) and bend allowance.
- **[Sketching](https://github.com/tangency-labs/solidcraft/milestone/14)**: in progress, 12 of 50 pieces done. Dimension types, selection and snapping options.
- **[Solid modeling](https://github.com/tangency-labs/solidcraft/milestone/2)**: in progress, 9 of 50 pieces done. Blends, holes, threads and patterns.
- **[Assemblies & joints](https://github.com/tangency-labs/solidcraft/milestone/3)**: in progress, 4 of 19 pieces done. Joints, motion studies and exploded views already work; placement rules, linked sub-assemblies, fastener stacks and weldment corners are the pieces left.
- **[Import & export](https://github.com/tangency-labs/solidcraft/milestone/5)**: in progress, 7 of 39 pieces done. FreeCAD, SolidWorks and point-cloud import, and mesh export options.
- **[AI agents & MCP](https://github.com/tangency-labs/solidcraft/milestone/18)**: in progress, 2 of 8 pieces done. Agent chat and the MCP server.
- **[Interface, web app & plugins](https://github.com/tangency-labs/solidcraft/milestone/4)**: in progress, 31 of 96 pieces done. Ribbon redesign, navigation, themes, the web app and plugins.
- **[SMLib kernel migration](https://github.com/tangency-labs/solidcraft/milestone/1)**: in progress, 1 tracking issue open. The kernel is moving from truck to NVIDIA's SMLib (usd-brep, Apache-2.0) on the `kernel-smlib` branch; it is not in releases yet. Tracked in [#4012](https://github.com/tangency-labs/solidcraft/issues/4012).
## Next
- **[Surfacing](https://github.com/tangency-labs/solidcraft/milestone/16)**: next, 6 of 14 pieces done. Patch, trim, blend and match exist; the rest of the surface toolset is planned.
- **[Manufacturing / CAM](https://github.com/tangency-labs/solidcraft/milestone/9)**: next, 1 pieces tracked, none done. There is no CAM in SolidCraft yet. Send to Slicer and a first toolpath slice are in review; proposals are [#1153](https://github.com/tangency-labs/solidcraft/issues/1153) and [#1621](https://github.com/tangency-labs/solidcraft/issues/1621).
Features are not tied to a release or a date. Work lands in the daily releases as it is finished.
Anything without a milestone, such as bug fixes, is not a feature and is not listed here.
## How releases work
- Releases ship daily, as numbered versions (v0.22.0, v0.22.1) with nightly pre-releases in
between. They are delivered through the in-app updater.
- SolidCraft tells you when a newer stable release exists and never updates without asking.
Where it can replace itself safely (AppImage, Windows installer and portable builds) there is
an **Update now** button; downloads are checked against the release's SHA-256 list. See
[updates and privacy](docs/book/src/guide/updates.md).
- Design files carry a format number. A new version opens designs from older versions, but an
older version refuses a design saved by a newer one (0.22.0 writes format 35, and 0.21.0 and
earlier will not open it). Each release's notes say when the format changed. Keep a copy of
an important design before you move to a new version.
## Licence
SolidCraft is licensed MIT OR Apache-2.0, at your option; see [LICENSE-MIT](LICENSE-MIT) and
[LICENSE-APACHE](LICENSE-APACHE). Versions already released stay available under those licences
constUSAGE_RUN: &str=" solidcraft-cli run <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]\n solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n";
constUSAGE_RUN: &str=" solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n";
/// `eval`'s lines in the usage.
constUSAGE_EVAL: &str=" solidcraft-cli eval <script.json|design.solidcraft|part.step> [--json-out FILE] (alias: inspect; or --in FILE)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n";
constUSAGE_EVAL: &str=" solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n";
/// `script`'s lines in the usage.
constUSAGE_SCRIPT: &str=" solidcraft-cli script <design.solidcraft|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
constUSAGE_SCRIPT: &str=" solidcraft-cli script <design.sc|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
/// `bench-open`'s lines in the usage.
constUSAGE_BENCH_OPEN: &str=" solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]\n (--ops: after opening, run each command of the script and undo it, timing the command, the\n evaluations it caused and the view it leaves: placing and meshing what changed)\n";
constUSAGE_BENCH_OPEN: &str=" solidcraft-cli bench-open <design.sc | part.step> [--parse-only] [--save OUT.sc] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]\n (--ops: after opening, run each command of the script and undo it, timing the command, the\n evaluations it caused and the view it leaves: placing and meshing what changed)\n";
constUSAGE_SNAPSHOT: &str=" solidcraft-cli snapshot <script|design> [--in design.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]\n solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)\n";
constUSAGE_SNAPSHOT: &str=" solidcraft-cli snapshot <script|design> [--in design.sc|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]\n solidcraft-cli snapshot --in <design.sc|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)\n";
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1";
letmac="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15";
letchrome_ios="Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/138.0.7204.156 Mobile/15E148 Safari/604.1";
letchrome="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36";
assert!(apple_touch(iphone,5));
assert!(apple_touch(chrome_ios,5),"every browser on iOS has the same picker");
assert!(apple_touch(mac,5),"an iPad presents itself as a Mac");
assert!(!apple_touch(mac,0),"a Mac has no touch screen");
assert!(!apple_touch(chrome,10),"a touch screen elsewhere filters as before");
assert!(!apple_touch("",0),"hostile input is just not an iPhone");
| AppImage | Uses the Linux 256 px image and `.DirIcon` alias |
| FreeBSD | Shares Linux hicolor images and freedesktop metadata |
| Web | HTML/Trunk and `manifest.webmanifest` stay in `apps/solidcraft-web/`; copies Japanese font/licence to `fonts/`, uses the collected CSS, and copies the Linux 32/256/512 px images to `icons/` for the favicon and installed app |
All ids below are **Open** for owner review, except `render_scene` and `render_image`,
which are existing accepted icons used only as references. No commands, toolbar
entries, browser selectors or runtime request manifests are changed by this draft set.
`render_remove_override` is the one remaining alias (of `render_from_material`);
`render_execute`, `render_environment` and `render_fit_product` now have their own
masters in `design/svg/model/`. Accepted source masters stay unchanged.
Review with `python3 -B assets/icons/ui/design/tools/icon_status.py --serve`,
search `render_`, and select Open. See [STYLE_GUIDE.md](STYLE_GUIDE.md) for
acceptance; drafts do not imply implemented commands.
The finished references in each row were read and previewed in both themes
before authoring. Draft contact sheets (96, 24 and 16 px, both themes) are local
review evidence, not shipped assets. The icons are grouped in five families, each
with one shared design rule.
## Lights
Rule: a slate source glyph with light passive fill, 2.55-unit outlines and a dotted
accent stem (three dots, 4.25 diameter, 7 units apart) showing the direction.
| Meaning | Id | Finished references | Drawing |
|---|---|---|---|
| Light area | `render_light_area` | `box`, `camera` | Slanted flat panel on the canonical axes above the dotted stem. |
| Light spot | `render_light_spot` | `camera`, `material` | Cone with a curved rim above the dotted stem. |
| Light point | `render_light_point` | `camera`, `material` | Round source disc with a centre dot above the dotted stem. |
| Sun | `render_sun` | `sphere`, `camera` | Source disc of the same size with eight short radial rays instead of a stem. |
| Light by highlight | `render_light_by_highlight` | `sphere`, `material` | Shaded sphere with a ringed highlight target; the dotted stem arrives from above. |
| Solo light | `render_solo_light` | `fit`, `camera` | Point source and stem inside four corner focus brackets. |
## Looks
Rule: the lit look sphere (top-left highlight, bottom-right shade) plus one cue that
says what happens to it; receiving bodies stay canonical grey or cube shapes.
| Meaning | Id | Finished references | Drawing |
|---|---|---|---|
| Appearance | `render_appearance` | `material`, `box` | Shaded look sphere on the top corner of an accent-shaded cube: a look assigned to a body. |
| Paint | `render_paint` | `material`, `paste` | Flat brush with an accent tip, light ferrule and slate handle. |
| Pick up | `render_pick_up` | `material`, `copy` | Eyedropper with a grey bulb, slate crossbar and accent sampling tip. |
| Replace all | `render_replace_all` | `copy`, `box` | Grey sample sphere with an arrow to an accent sphere, beside a second accent sphere. |
| Copy look | `render_copy_look` | `material`, `copy` | Look sphere in a square frame, offset from a second frame outline. |
| Remove override | `render_remove_override` | `appearance`, `box` | Alias of `render_from_material`: the custom look is crossed off. |
| Same look | `render_same_look` | `select`, `box` | Look sphere and three cubes; the two matching cubes are ringed. |
| From material | `render_from_material` | `material`, `extrude` | Look sphere struck through above a plain grey slab, so the material shows. |
## Studio
Rule: the standard grey cube stands in the set-up it is lit or placed in.
| Meaning | Id | Finished references | Drawing |
|---|---|---|---|
| Library | `render_library` | `library`, `copy` | Row of three look spheres, the middle one accent, on a shelf bar. |
| Lighting presets | `render_lighting_presets` | `new`, `render_scene` | Two stacked cards, the front one holding a light bulb. |
| Environment | `render_environment` | `environment_map`, `render_scene` | Dome with a grey cube inside. |
| Ground | `render_ground` | `box`, `render_scene` | Grey cube on a slab with an accent contact patch. |
| Backdrop | `render_backdrop` | `render_scene`, `render_image` | Grey cube in front of a tall upright accent panel. |
## Camera and shots
Rule: one chunky camera body with a lower-right round badge for shot actions; the
other camera icons are simple frames.
| Meaning | Id | Finished references | Drawing |
|---|---|---|---|
| Shot | `render_shot` | `camera`, `new` | Camera body with a plus badge: save a shot. |
| Update shot | `render_update_shot` | `camera`, `undo` | Camera body with a refresh badge. |
| Render all shots | `render_all_shots` | `copy`, `render_image` | Stacked camera bodies with a play badge. |
| Shots strip | `render_shots_strip` | `camera`, `copy` | Strip of three small frames, the middle one in accent. |
| Fit product | `render_fit_product` | `fit`, `camera` | Grey cube inside four corner brackets. |
| Lens / field of view | `render_lens` | `camera`, `fit` | Concentric lens rings around an accent centre. |
| Exposure | `render_exposure` | `material`, `environment_map` | Disc split light and accent, with plus and minus marks. |
| Aspect frame | `render_aspect_frame` | `render_image`, `fit` | Wide rounded frame with two opposite corner brackets. |
| Frame guides | `render_frame_guides` | `render_image`, `fit` | Rounded frame with a rule-of-thirds grid and four accent nodes. |
## Output and result
Rule: one picture base (frame with landscape) with a lower-right round badge; the
size, background and quality icons draw their own simple subject.
| Meaning | Id | Finished references | Drawing |
|---|---|---|---|
| Render image | `render_execute` | `render_image`, `camera` | Picture with a play badge: create a rendered image. |
| Render output size | `render_output_size` | `render_image`, `fit` | Picture frame with a diagonal double-headed dimension arrow. |
| Transparent background | `render_transparent_background` | `material`, `render_image` | Grey cube on a checker tile. |
| Quality | `render_quality` | `list`, `library` | Three rising bars, the tallest in accent. |
| Check render | `render_check` | `render_image`, `new` | Picture with a check badge. |
| Cancel render | `render_cancel` | `render_image`, `delete` | Picture with a square stop badge. |
| Export image | `render_export_image` | `render_image`, `copy` | Picture with a large outward diagonal arrow at the upper right. |
| Compare | `render_compare` | `render_image`, `material` | Two grey pictures split by a vertical slider with opposed arrows. |
| Results | `render_results` | `render_image`, `copy` | Stack of two pictures, the front one showing a landscape. |
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.