Fold the pending design format changes (browser-nested-groups, point-cloud-properties, print-faces) into format 45.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9741
* motion.list gives study keys in motion.study units
Angle keys came back in radians while motion.study takes degrees, so an
agent that listed a study and wrote it back changed every angle by a
factor of 57.3, silently. Convert key values on the way out like
joint.list does (degrees for angles, mm for distances), give each key a
unit and its joint's name, and add a round-trip regression test.
---------
Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
* claim: #9407
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Section caps: a body touching another along a curved face keeps its own cap (#9407)
Seen through a cut, a far wall and another body's face lying on it (a shaft
in its bore, a ring in its housing) are the same surface faceted two ways, so
the marking pass let the other body's face win in streaks. The section's
passes now first count the faces behind the cut in the stencil (back +1,
front -1): where the count says the cut runs through a body but a front face
won, the back faces are drawn again pulled toward the eye by a few pixels and
take the cap back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9279
* Parameters: an option to hold edits until Apply or OK (#9279)
"Apply each change as you leave its field" (on by default, remembered in
the preferences as parameters_apply_live). Off: an expression edit is
checked when its field is left but waits, without a preview; Apply or OK
sets every held edit in one parameters.change {changes}, so the design
rebuilds once and one undo takes them back. Cancel, the close button and
Escape drop them. Turning the option back on applies what waits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: the parameters_apply_live preference in ui.prefs (#9279)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameters: the hint says edits wait for Apply or OK when they do (#9279)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Adds 130 German translations for messages that exist in en.json but are
missing from de.json, in a new pending file as described in
assets/locales/pending/README.md. Covers the remaining CPU lighting and
reflection controls, render diagnostics, lens and film effects, GPU path
tracer options, HDR source details, the resource library and the raw
linear EXR options.
* claim: #9144
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DXF export: the full release 2000 file structure strict readers need (#9144)
The view and flat pattern DXF left out parts of the AC1015 file that a
strict reader expects: the CLASSES section, the VPORT, VIEW, UCS, APPID
and DIMSTYLE tables (with *Active, ACAD and Standard) and the ACAD_GROUP
dictionary under the root dictionary. They are written now, along with
$DWGCODEPAGE; the active viewport looks at the whole drawing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8776
* Crash reports: a window opened after startup no longer labels the run "during startup" (#8776)
egui-wgpu reports "window surface" and "surface configure" for every native
window, also the ones opened after startup, such as the agent chat in its own
window. Each one overwrote the journal's "running" phase, so a later unclean
exit was reported as "during startup (surface configure, N s after launch)".
crash::startup_step records a startup step only until the app runs, and the
app's egui-wgpu hook calls it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9472
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
* Browser: the recover-unsaved-work card always shows Recover and Discard
A window's scrolling body was only offered the height the card had the
frame before. A card first laid out while the page was still tiny (a
browser canvas not sized yet) kept a body of no height for good, so only
its title showed and the required choice could not be made. The body is
now offered the room the screen leaves and shrinks to its content, which
also covers the desktop recovery card and every other system window.
Fixes#9472
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
---------
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
Adds 130 German translations for messages that exist in en.json but are
missing from de.json, in a new pending file as described in
assets/locales/pending/README.md. Covers the quick measurement, a few
Preferences labels and a large part of the Render panel (adaptive
sampling, surface aging, camera background, CPU lighting and environment).
* claim: #9565
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
* feat(browser): reorder features by dragging rows of the History in the browser
Let users drag features in the History shown in the browser to move them, with the same drop bar and checks as the History at the left, while the rollback marker still drags on its own.
refs: #9565
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
---------
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
* Add Finnish (fi) UI localization (#9626)
A fi catalog ("Suomi") translated from en.json, all 4801 messages,
registered in LOCALES between es and fr, with
finnish_catalog_is_registered_and_translates_keys, the language listed
in docs/localization.md (registered and partial) with its terms, and a
pending attribution row.
Fixes#9626
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Finnish: the 3D mouse Restore defaults button gets its own label
It shared its text with Restore defaults elsewhere on the page, so the layout test found
the wrong button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Janiz-zzz <267742026+Janiz-zzz@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #9492
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
* fix(viewport): keep running when the view area comes out inside out
Make the layout beside the docked Assistant give way instead of closing the app when a window reconfigure leaves the view area inverted.
refs: #9492
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
---------
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
* claim: #3305
* fix(agent-chat): support ACP configuration options (#3305)
Handle current select options for model, thinking effort and mode, with confirmed snapshots and legacy compatibility. Full quality gate attempted; draft delivery authorized with unrelated geometry/property/timing failures documented in PR #3867.
* fix(agent-chat): keep configuration selectors inline (#3305)
Use compact value pickers and bounded wrapping to preserve conversation space; verified at normal and narrow widths with the installed OpenCode V2 server.
* style(agent-chat): format tool events
* claim: #9358
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameters: the expression field opens wide while you edit it (#9358)
A parameter's expression that is too long for its cell opens over the
cells to its right while it has focus, wraps instead of scrolling, and
shows the live value or error under it. Enter still applies it and the
field returns to its column; a short expression stays in its cell. The
add row's expression field does the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On a loaded runner the hover alone has taken 70 ms against a 190 ms index, which failed
unrelated pull requests. Waiting for the index would still cost all of it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6106
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Extrude: a sketch picked in the Browser or History takes all its profiles (#6106)
With Extrude, Revolve or Emboss open, a plain click on a sketch row in the
Browser or on a sketch in the History now fills Profiles with every profile of
that sketch (the island inside another profile's hole is left out). Before, the
click only changed the selection, which the dialog never reads.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: #2227
* 3D mouse: optional orbit while a sketch is open (#2227)
A new Navigation preference, "Turn sketches with the 3D mouse"
(space_mouse_sketch_orbit, off by default), lets the cap and a device
app turn the view while a sketch is open and seen square on, as the
mouse already can. Off keeps today's behaviour: pan and zoom until the
view is tilted away from the sketch (#6687). Restore defaults on the
3D mouse section resets it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 3D mouse: keep the settings list in order after the rebase
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: #9495
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assistant: say when a reply hit its length limit or was declined, not that the model lacks tools (#9495)
The API client never read why an answer stopped, so a Claude reply cut
off at max_tokens while thinking (its thinking text is empty by default),
or a refusal, ended the turn as normal with nothing said. The chat then
blamed tool support: "The model didn't use SolidCraft's tools".
Reply now carries the stop reason: Anthropic stop_reason max_tokens and
refusal, or OpenAI finish_reason length and content_filter and a message
refusal. Any other value is a normal end. The turn ends with that reason,
which already keeps the tool-calling notice away, and the chat shows
what happened in place of the raw "(max_tokens)", for ACP agents too. A
tool call cut off at the length limit has incomplete input, so it is
neither run nor kept in the history.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Hole: each dropdown option shows the hole it makes while hovered
Extents, Hole Type, Hole Tap Type and Drill Point list names only, so seeing
what an option makes meant picking it. Hovering an option now shows a small
section of that hole beside the list, drawn by the dialog's own section
painter from a copy of the form with the option applied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhpMr6L9PR8SJXjM6MbQrD
* Hole: give the dropdown previews a gallery entry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danieldimitrov86 <danieldimitrovdev@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
While Frame shows the picture's frame, the preview projects into that frame
rather than the viewport, but Fit Product and the fit on entering Render
framed the product for the viewport's free area, so a picture taller than
wide cut the product at its sides. The fit now uses the viewport layout
seen from the frame (the frame as the viewport, the free area inside it
with the usual 16-point margin).
Entering Render fitted before the saved setup replaced the draft, so a cold
workspace with a portrait setup fitted for the old draft's aspect; the fit
now runs once the setup in effect is loaded.
render.camera.fit takes the picture's aspect (0.000001 to 1000000) through
Camera::fit_aspect, which publishes only a valid fitted camera: an aspect
that would divide the distance into infinity is refused and the previous
camera, document and undo stay as they were.
* Claim #9479
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Preferences: an option to keep value boxes out of the view (#9479)
Design > "Show values beside their handles in the view", on by default.
Off, a command's value box (Extrude's Distance, Fillet's Radius, a hole's
reference distances...) isn't drawn over the model; the command window's
value field takes the keyboard that opening the window or a pick gives the
box, its text selected, so a number typed after a pick still sets it. The
handles stay and still drag the value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Preferences: the value box option in Arabic, Hebrew and Vietnamese too (#9479)
The complete catalogs must carry every English message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #8941
* Render: a size the CPU methods refuse names the profile that renders it (#8941)
The Shots strip offers 4K UHD whatever the method. Under the default CPU
raster, and under CPU ray tracing's default Compact profile, Render was then
refused with English engine text that didn't say how to render that size.
The refusal is now translated and names the smallest CPU resource profile that
admits the size (with CPU ray tracing, for raster, which has no profiles). The
size lists mark such presets. Nothing is switched: method, profile and
resolution still change only when chosen, and GPU path tracing is unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8563
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameters: sort each section by name (#8563)
The Name heading of every Parameters table steps through A to Z, Z to A and the
listed order. Each section sorts on its own: Favorites, User Parameters and each
model group (the groups keep the design's order). Names compare in natural order
(d2 before d10, letters ignoring case). The choice is kept in egui's persisted
memory like a section's open state. While sorted, a user parameter's move arrows
are disabled. The sortable heading and natural_cmp live in ui_kit::table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameters sort: gallery entry for the sortable heading, natural_cmp private
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: nest groups and reorder components
Groups can hold other groups (up to 8 levels) and be reordered with the
items around them. Sub-components can be reordered too, through a new
`components` list in `browser_order` that is not drawn as a folder.
Document: `BrowserGroup` gains `parent`; group items and `browser_order`
lists hold `BrowserEntry` values (a number is a group, a string an item).
One pending format file; older designs read unchanged. Loading and
refreshing drop what the lists name that is gone, and cut cycles and
over-deep nesting to the top level.
Engine: `browser.group` takes `parent`; `browser.move`, `browser.order`
and `browser.ungroup` handle groups. A change to only the browser's
groups and order no longer re-evaluates the model.
UI: nested rows, dragging a group row (top of a row: before it, the rest:
into it), New Group inside a group, and dropping a placement on the top
of a sibling to reorder it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Fixed formatting
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Fixed unintended side effect in render scene browser test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Preserve group order in render workspace
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Increased test coverage
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Updated clipboard test from upstream main for group item type
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: #9281
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch text: a number parameter in the words shows its value (#9281)
A text's words expression can now name a number parameter: `'W' + Width`
reads "W85 mm" (lengths in the design's units, angles in degrees, up to
three decimals). `number(Width)` drops the unit and `number(Width, 2)`
fixes the decimals. The words and the cut follow a parameter edit in the
same rebuild, a rename reaches inside number(), and deleting the parameter
is refused while a text shows it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch text: a Text starting with = builds its words from parameters (#9281)
In Edit Text, a Text that starts with = is the words' expression
(='W' + number(Width)): OK keeps it, the dialog shows it again with its =,
and an untouched OK no longer turns it into typed words. Typed words still
replace it, and a saved text that happens to start with = stays typed
until it is edited. The field's tooltip says how.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch text: the formula hint in every complete catalog (#9281)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9246
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: Down arrow in an empty field lists every named parameter (#9246)
In an empty expression field, Down opens the completion list with every
named parameter (not just the first eight), armed on the first one; Up and
Down walk it, the list scrolls to keep the chosen row in view, and Enter
inserts it. A field that already holds text keeps Down for itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: Preferences caps the Down-arrow parameter list (#9246)
Down arrow in an empty field lists the parameters up to "Parameters
listed by Down arrow" in Preferences > Design (default 50, 5 to 200),
favourites and the design's own parameters first, so a large design
does not build a list of hundreds of rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: a label that fits for the parameter list cap (#9246)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: the parameter list cap in every complete catalog (#9246)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
After a lost device the next start began from on_exit, while eframe was
still writing app.ron, and the old process then hung in
vkDestroySwapchainKHR on NVIDIA. Start it from Drop for App instead, after
eframe has joined the settings save and before it drops the window's GPU
surface, and end the process there after a loss. The update restart moves
there too. The loss reason is passed to the next start and shown in its
note.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The test asserted the readout was still empty right after the first poll, but the measuring thread can finish first, so it failed now and then in merge groups and on unrelated pull requests. It now asserts only that the measurement arrives.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Everything en.json gained since the last catch-up except the render.*
messages: Preferences, navigation bar, assistant settings, materials,
hole presets, joint move, edit in place, point clouds, view angles,
tool help texts and error hints. Added as one pending locale file.
* Sketch: the coordinate readout starts right of the pointer arrow (#9480)
The readout of a sketch tool's point (2D and 3D sketches) started 10
points below and right of the snapped point, under the system arrow,
which hangs about 12 points wide below the hot spot and covered the
first value. It now starts 18 points right and 10 below the pointer
(the point snaps away from it), from the snapped point only when there
is no pointer over the view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: the readout clears the next larger pointer too (#9480)
The reporter uses the system's pointer size 2, whose arrow is about
18 × 28 points: start the readout 26 points right of the hot spot
instead of 18, and test against that arrow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The clamp that crashed #9492 (f32::clamp between a view's edges, which
panics once the view comes out inside out) also sits in the room tool
windows get beside the docked Assistant, the Render selection bar's
anchor and a left-anchored panel's anchor. Use shell::within there,
the clamp that gives the lower edge for an empty range (#9271).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9081
Co-authored-by: Cursor <cursoragent@cursor.com>
* Tell same-named recent designs apart (#9081)
Show the file name with extension, ellipsize long paths in the middle so the end stays visible, and label imports by kind (STEP, STL, …) instead of always Design.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Start: Recent paths take their ink from the theme, and the middle-ellipsis helper is exported for tests only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9170
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FAWPuFoh9qxn2uS2j53QZ
* Model cache: a design with mesh bodies keeps its saved model (#9170)
One mesh body (an inserted STL or 3MF, or a STEP part shown as a mesh of
its faces) left the model cache out for the whole design, so opening it
evaluated every feature again. The doc records refused mesh bodies, and
the native B-rep codec the io cache stores bodies with refuses them too.
Kernel work is paused, so the kernel is left alone: the io cache stores a
mesh body as its own slice kind (SCMESH01, a JSON header with the counts,
colour and paint, then the vertices as f64 and triangles as u32), read
back through mesh_body, with_color and with_paint. A mesh body is kept
only when its slice reads back as the same vertices bit for bit, the same
triangles, colour and paint. The decoder checks the counts against the
bytes before allocating. The picture shown while opening draws a mesh
body with its own triangles (native_preview_mesh refuses mesh bodies).
Cache version 4 -> 5; docs/design-container.md describes the mesh slice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FAWPuFoh9qxn2uS2j53QZ
* Model cache: decode mesh slices with as_chunks, as clippy asks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #9417
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assistant: drop earlier thinking blocks when history is trimmed (#9417)
Claude signs each thinking block against the conversation before it. Once
drop_old_views swaps an older view for a note, or trim_history drops the
oldest messages, every later block stops matching and the API refuses the
request with HTTP 400 "Invalid signature in thinking block". For the
Anthropic protocol, a trim now also removes the thinking and
redacted_thinking blocks from earlier replies, which the API allows. A
reply that was only thinking keeps a short note. Requests where nothing
was trimmed are sent unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9324
* Sketch: Project takes a construction axis, not only X, Y and Z (#9324)
sketch.project with {"axis": "Axis1"} upper-cased the name, and the axis link knew only the
origin axes, so a construction axis failed with "unknown axis `AXIS1`". X, Y and Z (any case)
are still the origin axes; any other name must be a construction axis, kept as its feature names
it. The link finds the axis where the timeline before the sketch puts it (construction_axis_in,
as Revolve does) and projects it as an origin axis: its line in the sketch plane, or a point
when the axis stands square to it. Renaming the axis renames the link, as for a construction
plane's projection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #8398
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Closing: stop the Home picture builds instead of waiting for them (#8398)
Dropping SolveApp waited up to 10 s for the Home picture threads to end
(#6063). The thumbnails thread imports and rebuilds every cold recent
file (STEP and mesh imports, designs without a stored picture) one after
another, and its channel only closes once all are done, so closing the
window froze until every cold recent was built or the 10 s ran out.
The picture threads now build under a shared progress watch that
finish_pictures cancels first: the design being built stops at its next
feature (a STEP import at its next step), the remaining cold files are
skipped, and a build stopped part way draws and caches no picture.
Pictures already on the GPU are still waited for, so the driver crash
#6063 fixed stays fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Quick measurement: the selection's length, area or distance in the view's corner (#7000)
With edges, faces, sketch curves or points selected, the view's bottom right corner above History
shows what they measure, as the Measure tool lists it: an edge's length (Ø and R for a round
one), a face's area, a point's position; two items' distance and angle; several edges' or faces'
count and total. It is computed off the window thread from a copy of the session whenever the
selection or the design changes, and a click copies it. Preferences > Interface turns it off. The
Measure dialog's rows come from the same function.
* Quick measurement: off by default, turned on under Preferences > Interface (#7000)
The quick_measure preference now defaults to false, so nothing changes until it is turned on.
Its tests turn it on, and a new test checks that nothing shows until it is.
* claim: #8315
* fix(control-server): release the watch seat deterministically (#8315)
The seat a subscribed connection holds on the DesignWatch lived in the
events thread alone, so it left the watch only when that thread ended:
on the first write a closed client refuses, which needs the design to
move, or when the thread noticed the end of the stream, which waits for
the connection's own thread to come out of its blocking read. A closed
connection could therefore keep counting on the watch for a second or
more, and the test that asks for the count to be gone could lose its
race with the ten seconds it waits.
The seat is now shared: it leaves the watch once, whichever thread sees
the connection's end first, the connection's own thread at the end of
the stream or the events thread at the first refused write. The count
of subscribed connections is right as soon as the connection is gone,
and the events a subscribed connection is told of do not change.
* claim: #6262
* Inspect Sketch: the ribbon button opens a Sketch report window (#6262)
sketch.inspect gets a place on the Sketch tab (Inspect panel). ui.start opens a
read-only Sketch report: solver status, degrees of freedom left, counts of
curves, points, constraints and profiles, the curves and points that are not
fully constrained, and Copy JSON. engine.execute still only returns the JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Inspect Sketch: a Sketch Report button on the Sketch tab while a sketch is open (#6262)
SKETCH/INSPECT placed it in the Inspect tab's Surface Quality drop-down, which
is hidden while a sketch is edited. It now has its own Inspect panel on the
Sketch tab, labelled Sketch Report; the test clicks the ribbon button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Report: strings in a pending locale file, help for sketch.inspect (#6262)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Report: the Sketch tab fits at 1366 px; the width test closes the report (#6262)
The panels left of the Finish group keep the gap before it when they are fitted,
so they collapse before they reach it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Frank <plexi32@gmail.com>
Opening a DWG stopped with "design file is not UTF-8 JSON". It is told
apart by its signature, whatever the file is called, and the error points
to STEP or DXF.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a projected face follows a resize that moves its corners far (#9001)
A sketch on the face of an angle kept its projected face boundary (and
reported "projected face boundary identity is ambiguous") when the wall
thickness changed: the inner corner moved by more than a tenth of the face,
past the nearest-point match, and the same-order fallback refused faces.
Accept the same-order layout when, for every point, the point read in that
order is also clearly the nearest one on the same loop with lines the same
way. Rims and holes, and rotated readings of a rectangle, stay refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9341
* Insert Part: seat a fastener in several holes at once (#9341)
parts.insert / parts.fastener take `at` as one pick or a list of picks.
The part is built once as one component; each hole gets its own
placement and rigid seat joint. All placements are made before any is
seated, since joint.create takes the part's seating point where the
placement is. The insert now runs on a scratch session like
parts.resize, so it is one undo step, and a pick off every body fails
it before anything is made.
The Insert Part dialog's hole input is multi-select ("Holes
(optional)") and OK passes every pick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Part: picks on one hole's rim seat one part (#9341)
A hole's rim is drawn as two half-circle edges, and one click on it
picks both, so four clicks gave eight picks and two screws per hole.
Picks whose wall point lies level on the same hole axis, on the same
body placement, are one hole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A comma separates arguments only directly in a function call, so plain
parentheses take a decimal comma like the rest of an expression:
`-(width-0,5)/2`. The UI no longer rewrites commas before wrapping a value
in the design unit's parentheses.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9297
* Assistant on Windows: find Node in its install folder when the launcher's PATH lacks it (#9297)
An npm-installed agent starts on Windows through its .cmd shim, which runs `node`, with the PATH
search_dirs makes: SolidCraft's own PATH, then npm's and other installers' folders. Node's own
install folder was not among them, so started from a launcher that leaves it off the PATH (the
ArtCraft launcher), the agent stopped with "'node' is not recognized". The search now also takes
%ProgramFiles%\nodejs, %ProgramFiles(x86)%\nodejs and nvm-windows' %NVM_SYMLINK%, after
everything it took before. search_dirs reads its variables through search_dirs_in, so a test can
give it a launcher's environment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Point clouds: colour and filter by any property of a PLY file, on range tracks (#5151)
A PLY file's other vertex properties are kept as named numbers on the
points (whole-numbered for integer types; a comment `property: 0 name,
1 name` names a label's values). A cloud is coloured by any of them, a
ramp or a palette for whole numbers, and filtered by up to eight, each a
range or a set of named values. The GPU colours and filters the points
itself, so editing a range uploads nothing.
In the Edit Point Cloud card each range is a track, as in an audio
editor: a histogram of the property over what the other filters keep,
stacked by colour, under an overview strip. Ranges are drawn, moved,
snapped to colour changes, typed (Start, End, Length, relative values)
and played (Space or Play). The automatic colour range is exactly the
values the property takes over what the filters keep.
pointcloud.insert and pointcloud.edit take color_by: <property>,
color_range and filters; pointcloud.list reports them with the
properties. docs/point-clouds.md describes it all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: note the saved properties, colouring and filters as a pending format change (#8816)
A design now saves a cloud's per-point columns and their value names, colouring by a property and
filters; an older release must refuse it instead of dropping them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7971
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Toasts: a close (×) with a countdown ring instead of a Close button (#7971)
Every toast the user may close (the status, the 3D mouse offer and the
stand-up note) has a close (×) at its top right, handle toast:<name>:close,
in place of a Close button; it does what the button did, is a kit-drawn
button with an accessible name and hover text, reachable by Tab, and dead
behind an issue report. Toasts that go by themselves draw a thin ring
around it that shrinks over their time; pointing at the offer or the note
starts their time again, as before, so their ring is full while pointed at.
The 3D mouse offer keeps three buttons, Calibrate…, Use Direct Driver (the
detail in its tooltip) and Never show again, on one row: the corner
toasts are 400 points wide, as the row's estimated widths need 356.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Toasts: lay buttons out at their drawn widths; corner toasts 360 wide (#7971)
The toast's button rows used an estimate of each button's width that ran
about 40 points over the kit button as drawn, which wrapped the 3D mouse
offer's three buttons and left the main action short of the right edge.
Each button's drawn width is now remembered and the rows laid out with it
(the estimate stands in for the one frame before a button was first
drawn), so the main action is flush right; the corner width is the
smallest round one that fits the offer's three buttons in a row: 360.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Toasts: keep the analysis status test whole after rebasing onto main
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>
On Macs patched with OpenCore Legacy Patcher the OS reports a newer
macOS version than the GPU's Metal compiler supports, so wgpu picks a
shader standard the compiler refuses (`invalid value 'metal3.2'`) and
the window never draws.
The variable has to be set before the process starts, and Rust 2024
makes `set_var` unsafe (this crate denies unsafe code), so main()
now re-launches the binary with SYSTEM_VERSION_COMPAT=1 when the OCLP
NVRAM key (OCLP-Version) is present, and exits with the child's status.
A process that already has the variable set never re-launches, so
there is no loop and a user's own value is respected. If the check or
the spawn fails, startup continues normally.
Tested on an OCLP Mac: one re-launch message, graphics render
correctly; with SYSTEM_VERSION_COMPAT=1 set beforehand it does not
re-launch.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Help: add manual update checks and status window
* Test manual update checks and localized results
* Add update check to the native Help menu
* Localize Help update checks in all app languages
* Document manual update checks from Help
* Document ui.help updates control action
* claim: #7535
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vendor accesskit_macos 0.26.3 as published (#7535)
The published package, unchanged, so the next commit shows the fix as a diff.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* macOS: quitting on a Mac with a Touch Bar no longer crashes (#7535)
eframe's accessibility adapter (accesskit_macos 0.26.3) swaps the window's
view to a subclass of its own. When AppKit's Touch Bar observes the first
responder (nextResponder), key-value observing subclasses the view again.
Dropping the adapter on quit reset the view's class, which threw away KVO's
subclass, so AppKit's removal of its observer raised NSRangeException ("not
registered as an observer") and the app ended with EXC_BREAKPOINT.
accesskit_macos 0.27.2 fixes this (AccessKit/accesskit#835), but egui 0.36
is on accesskit 0.24, so vendor 0.26.3 with 0.27.2's src/subclass.rs and its
tests: the adapter leaves the class alone when KVO subclassed the view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8894
Co-authored-by: Cursor <cursoragent@cursor.com>
* Web: load Simplified/Traditional Chinese fonts for zh and zh-TW (#8894)
BIZ UDPGothic only covers Japanese, so Chinese UI showed tofu. Ship locale-glyph Craft UI SC/TC subsets and fetch the matching face on demand.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* claim: #8980
Hide constraint glyphs when zoomed out (claim).
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
* Sketch: hide constraint glyphs when zoomed out (#8980)
Fixed-size constraint glyphs stayed visible at every zoom and cluttered the
view when far out. Hide them below a sketch-plane scale threshold, with the
old always-on behaviour behind hide_constraint_glyphs_zoomed_out.
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
* i18n: complete-catalog strings for zoom-hide preference (#8980)
Add ar/he/vi translations for prefs.hide_constraint_glyphs_zoomed_out
(and its note) so cargo xtask i18n --check no longer reports 6 gaps in
complete catalogs.
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
Native compilers inline every call, and `main` inlined 33 BVH walks: three
sample_path calls, and a shadow walk per light source family. NVIDIA's
compiler (driver 610.88) crashed in nvgpucomp64.dll or ran out of memory.
- main settles the adaptive budget first, then samples at one call site.
- direct_light takes over direct_studio, visible and the fixed-light loop:
one loop over the sources with one shadow-ray call site, drawing random
numbers in the same order.
- next_vertex (a select) replaces (a+1u)%3u: after a BVH walk that index
crashed NVIDIA's compiler within seconds.
main now inlines 5 BVH walks; main_inlines_few_bvh_walks counts them with
naga, so the limit holds without an NVIDIA GPU.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Problem: every state of a saved model wrote the evaluation key of each sketch before it, so a
design of many sketches and many features after them repeated the same map over and over. In
the SELENE rover (5,484 features, 2,116 bodies) that was 27 of the 33.5 MB manifest: the
manifest hit its 32 MiB limit, the save thinned the cache (#3220) to 2,140 of 5,484 states, and
every open evaluated the 3,344 left-out features again. Reading the kept states one after
another took a few seconds more.
Solution: as for sheet bodies (#6152), the records keep a list of sketch-key sets, each distinct
set written once, as the keys it sets and drops relative to the set written before it; a state
names its set by place. A thinned model keeps the whole list and counts it once against its
budget. The reader counts every set's keys, and each state's, towards MAX_HELD before it copies
them (a short record can stand for a large set), then builds the states on threads
(kernel::map_on_threads) once the shared records are read. The cache format is now /5: an older
cache is a miss and the design is evaluated.
SELENE's manifest is 15.9 MB with every state kept; opening it evaluates nothing. Parse and
evaluate on opening, release build, median of 5 on a shared machine: 7.36 s -> 1.14 s. The
evaluated bodies are identical (inspect.measure of all 2,116 bodies).
Claude-Session: https://claude.ai/code/session_013WUFq6QFBqmZjxwaKy2J1M
Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
* claim: #5574
* Pick hidden geometry near long clicks in a five-row list (#5574)
* fix: make Select Other configurable and fit its list to the window
---------
Co-authored-by: Kirill Frolov <k.a.frolov@vk.team>
* claim: #7168
* Saved model cache: keep fast opening after an update (#7168)
The model cache was read only by the exact build that wrote it (same app
version and commit), so every update evaluated every design again on its
first open. It is now keyed by the evaluation source instead: a digest of
the committed source the model is computed from (doc and the workspace
crates it is built from, the patched truck crates, locked external
dependencies, workspace build settings without the version, and the
cache reader), with the platform. An update that changed none of that
opens designs from their cache; one that changed any of it evaluates
them again, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7944
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Theme: System follows the desktop's dark/light setting on Linux (#7944)
On GNOME (Wayland, or X11 through XWayland) the window toolkit never reports
the desktop's dark or light preference, nor its changes, so Theme: System kept
its colours. The Linux app now reads org.freedesktop.appearance color-scheme
through the XDG Desktop Portal's Settings interface (then GNOME's color-scheme
and gtk-theme when it has no preference) on a thread of its own, follows the
portal's SettingChanged signal, and falls back to gsettings or a slow poll.
The window reads the answer through Services::desktop_dark; the decision from
the raw values is prefs::desktop_prefers_dark. Windows and macOS keep the
window toolkit's theme.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8429
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print face: Send to Slicer lays a body on its marked face (#8429)
A body can have one flat print face (Document::print_faces, held by the
certified target a sketch on a face holds). body.print_face {body, point?}
marks or clears it. Send to Slicer, Save Mesh with a slicer and EasyPrint
turn and move the exported copy so the face lies on the bed at Z = 0 with
the centre of mass over the origin; a face that can't be found is a
warning and that body is exported as it lies. Move/Copy and Mirror give a
copy its source's print face. One pending format file; the command
has its own icon (print_face).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print face: Ctrl+B, the face menu and the Send to Slicer row (#8429)
Ctrl+B and the face context menu mark the selected flat face as its
body's print face, or clear it. Send to Slicer shows the print face of
the one body being sent in a Print face input, takes a flat face picked
in the viewport and clears it when emptied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print face: a light tint in the viewport and its strength in Preferences (#8429)
Each visible body's print face is tinted towards the theme's section
colour and outlined, at the strength set under Preferences > General >
Slicer (print_face_highlight, 0 to 1, 20 % by default; 0 hides it). The
user guide describes the print face, and the known limitation after a
shell of a shelled body is listed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9118
* Agent chat window: an off-screen chat window no longer stalls the main window (#9118)
The chat popped out into its own window is an immediate viewport: eframe draws
and presents it inside the main window's frame. Both surfaces waited for vsync,
and a Wayland compositor sends no frame callbacks to a window it isn't showing,
so the main window stalled behind the chat window's present.
A surface other than the main window's is now configured with Mailbox, else
Immediate, when the configured mode waits for vsync and the surface offers one
(viewport_present_mode in the vendored egui-wgpu). The main window keeps vsync
and still paces both.
* claim: #8718
* Browser: the eye sits in a column at each row's right end (#8718)
Tree rows draw the visibility eye right-aligned at the row's end, clear of
the fold arrow and the radio and of a floating scroll bar; names stop short
of it. Preferences > Interface > Eye next to the fold arrow
(browser_eye_left) keeps the old position. Rows report their control
positions so placement handles and inline rename follow the layout.
* Docs: the browser's eye column and browser_eye_left (#8718)
* Browser: the eye's tip shows by the eye at the row's end (#8718)
* Browser: a gap keeps the fold arrow and the radio apart; the eye clears a floating scroll bar (#8718)
The eye's old slot leaves a 10 pt gap instead of 4 pt, so the radio's hit
area is 7.5 pt from the fold arrow's and History pattern rows step in from
their sketch as far as browser children do. The eye's inset follows the
scroll style: only a floating bar laid over the rows takes room. Rows
publish indent:<label> where their depth puts them.
* Tests: browser depth checks run on the default layout, by indent handles (#8718)
* claim: #8993
* Reverse zoom direction no longer flips 3D mouse zoom (#8993)
The checkbox sits under Wheel and trackpad and now reverses the wheel
(and the navigation bridge's wheel-style steps) only. The 3D mouse cap
zooms by its own per-axis invert switch alone: device_camera no longer
takes a reverse_zoom argument, nor does the test model in Preferences.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8390
* Sketch modify: pick a whole profile or chain for the shared Entities input (#8390)
- Click inside a closed profile selects its whole boundary (and its holes);
double-click a curve selects its whole chain, for Mirror, Move/Copy, Scale
and the patterns. Single curves and points keep working as before.
- The mirror line may itself be among the entities: the command leaves the
line in place and mirrors the rest, instead of rejecting it.
* Mirror: the shared line case is valid, not an error (#8390)
Update the half-filled-window test: a mirror line that is also among the
entities is now accepted (the command leaves it in place and mirrors the
rest), matching the engine.
---------
Co-authored-by: Dane Doubell <2853036+danedoubell@users.noreply.github.com>
* claim: #8908
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update Cancel clears the UI and stops before checksum downloads.
Drop the install job as soon as Cancel is pressed so Downloading ends
immediately, and check the cancel flag around feed refresh and
checksum GETs so a stall before the package body no longer ignores it.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Assistant composer: the input box fills its area, and the hint stays on one line
The composer footer inset the box by its own margin on top of the box's text
padding. The footer now has no margin, so the box spans it on all four sides and
only the text padding remains. The hint is painted on a single row, cut with an
ellipsis when the panel is narrow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assistant composer: the hint takes its colour from the theme, not a literal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Ribbon drop-downs as a grid of large icons, as an option (#4739)
Preferences > Interface gets two settings, both off by default:
- Menus: List | Large icons. With Large icons a panel's drop-down shows its
commands as rows of up to five icons at 32 px (twice the list's), keeping
the list's group headings and order. The name, shortcut and help are in
the tooltip, as on a ribbon button. Arrow keys move across and between
rows; a click starts the command; right-click and drag pin it as before.
Resting on an icon for two seconds arms a pin in its corner; a quicker
click anywhere starts the command, so nothing is pinned by accident. A
pinned command shows its pin.
- "Leave pinned commands out of the drop-down menus": a drop-down (list or
grid) leaves out the commands the ribbon shows as buttons; a panel too
narrow for its buttons lists them again, and a menu is never left empty.
No icon artwork or colours change. Sketch > Create keeps its family list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch > Create in the large-icon grid: every variant under its family (#4739)
With Menus: Large icons, Sketch > Create showed its family sub-menus as
plain text rows (Line, Rectangle, Circle, ...), unlike every other menu. It
now shows every variant as a large icon under its family's heading, then the
other tools under "More". The list keeps its family sub-menus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Large-icon menus: groups side by side, no footer (#4739)
A menu of two or more headed groups (Sketch > Create's families, Model >
Modify's Edges, Faces, Bodies, Specialise) puts the groups side by side, each
heading over a column of its icons, at most two across. Menus without groups,
or one too wide for the window, keep rows of up to five. The pinning note
under the large-icon menus goes: the tooltips and right-click menus say it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Large-icon menus: a separator line between groups side by side (#4739)
As between the ribbon's sections: a 1 px line in the theme's border colour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Large-icon menus: at most three rows, growing to the right (#4739)
A menu fills five columns, more when that would take over three rows (Model >
Create: 7 x 3); in grouped menus a group too big for three rows gets more
columns. Only a window too narrow for that wraps into more rows. A test checks
every panel's menu of every tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Large-icon menus: group headings centred over their icons (#4739)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop-down group headings: smaller than the drop-down's header, not in capitals (#4739)
Edges, Faces, Bodies (and Sketch > Create's families) were drawn in capitals
at font_small, which reads larger than the drop-down's own header
(font_caption). They now use font_micro, written as named, in the list and the
large-icon grid. Review feedback on #5012.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Large-icon menus: start a command from the grid at its icon, as main's rows do (#4739)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Ribbon icon grid strings: a pending locale file, with ar, he and vi (#4739)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Large-icon menus: commands in no group stand apart, so a left-out group doesn't move them (#4739)
With Convert to Sheet Metal pinned and left out of Model > Modify, its
Specialise group went and Delete, in no group, moved under Bodies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9332
* Expressions: hyperbolic functions sinh, cosh, tanh and their inverses (#9332)
`sinh`, `cosh`, `tanh` and `asinh` take a unit-less number and give one,
as `exp` does; `acosh` refuses x < 1 and `atanh` refuses |x| >= 1, worded
as `asin` and `ln` refuse what lies outside their domain. They are in the
expression help (`FUNCTIONS`, the parameter autocomplete), the
hostile-expression fuzz tokens, the parameters and equation-curve guides,
and the checking skill's list of functions. Horn flares (Salmon's hypex
`cosh h + T sinh h`), catenaries and the tractrix can now be written as
they are stated instead of expanded into `exp`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6896
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WUFq6QFBqmZjxwaKy2J1M
* Sketch: find profiles island by island and say when the search is cut short (#6896)
Every live drag frame ran the profile search over the whole sketch: every
spline piece of a large imported logo was sampled, checked for crossings
and traced again, about 100 ms per frame at 6,000 degrees of freedom.
Profile curves are now split into islands whose enlarged piece boxes do
not touch (the margin exceeds every tolerance the search uses), each
island's loops are found on their own and cached by a fingerprint of its
geometry, and the loops are nested into profiles over the whole sketch in
the order a single search finds them, so profiles, ids, holes and
whole-boundary certificates stay the same. The end-near-crossing lookup
covers its whole reach, independent of the grid's cell size.
The 50,000-piece limit now holds per island, and a sketch that still
exceeds it gets a warning instead of silently missing profiles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WUFq6QFBqmZjxwaKy2J1M
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`schema::issues(id, params)` looks up the command's typed contract and
lists what is wrong with the parameters (none without a contract or for
an unknown id). The batch preflight, the MCP bridge's pre-send check and
the app's check of a design-switching command each did that lookup
themselves; they now call it. No behaviour change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
sketch_consumed knew Extrude, Revolve and the sheet metal flanges but not
a Hole drilled at a sketch's points, so that sketch stayed drawn inside the
holes. A Multiple hole now counts as using its sketch; Edit Feature shows
its points while the dialog is open. Single holes keep their sketch shown,
since a hidden sketch offers no points to snap to.
* claim: #9299
* Recover unsaved design?: Discard all, with a confirmation (#9299)
A Discard all button at the left of the window's footer, away from Later.
It asks first ("Discard 7 autosaves? This cannot be undone.", Discard all /
Cancel), then discards every listed autosave the way its own Discard button
does (doc.recovery_discard on each id). Cancel goes back to the list
unchanged. The button publishes the handle recovery:discard_all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9265
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Preferences: a setting in a row with no label is found by the words it shows (#9265)
A search hid a row by its label before its controls were drawn. Seven checkbox rows and two segmented rows on the Interface page have no label, so "status bar", "search results" and "reveal on hover" found nothing. A hidden row with no label now keeps the words of its controls, as a dropdown keeps its value, and is drawn when they match. Each such row has a key of its own, so its neighbours are not drawn with it.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
* claim: #9195
* Viewport: an opened design is framed at the home view, in the middle of the view (#9195)
A fit the viewport cannot frame yet is kept and asked again on the next frame instead
of being consumed and lost, so a design opened from the Home screen or by launching on
a file no longer comes up at the default camera, wherever its own coordinates put it.
Framing then centres the model on the view rather than on the free rectangle between
the floating controls: `Layout` carries `middle`, the view without the panels, computed
before the compass, the navigation bar and the OK/Cancel corner join the obstacles. The
model is still sized to `drawable`, so it stays clear of every control, but only a panel
moves it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Viewport: keep the centred fit clear of the controls, and frame --sample (#9195)
The fit framed the model into a rectangle the size of `drawable` centred on `middle`, which is
not inside `drawable`, so the model could sit under the compass, the navigation bar or the
render product frame. The model is now sized to `drawable` as before, and only the room left
over in it moves the model towards the middle of the view.
`s27_move_triad` turns the Z ring on its far side: the model now sits further right, and the
Move dialog that docks on the right afterwards covered the ring's old grab point.
A launch with `--sample` builds the design straight into the session and never asked for a
fit; it now comes up at the home view, framed, like an opened design.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport tests: the cursor orbit's pivot keeps the pixel it was picked on (#9195)
With the model now centred on the view, one of the test's perspective drag points lands within
the pick tolerance of the box's top edge, and the pick snaps the pivot onto that edge, a few
pixels from the cursor. The orbit keeps that point still, as it should; the test compared it
with the cursor instead of with where it started.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Both commands need a choice, but start_with_variant_memory only special-
cased the parameterless display toggles, so a click on the ribbon (or
command search, a shortcut, ui.start) ran them with {} and showed only
the engine's "style must be ..." / "tool must be ..." error.
They now open a short menu, the way Units does: under the ribbon row
that started it (a second click closes it), or in the middle of the view.
Visual style lists the four styles with the current one checked; the
Navigation tool lists Orbit, Pan and Zoom, and picking the latched one
releases it, as its navigation-bar button does. The four styles come
from one list shared with the navigation bar's menu.
A sketch that failed (a dimension set negative by a parameter) left every
feature built on it with "unknown sketch 1 (it must come earlier in the
timeline)", which points at the timeline order. The evaluation now notes
the features that failed earlier in the pass, and a feature whose input
sketch is one of them fails with "sketch `Sketch1` failed: <its error>".
That result is never cached, since the cache key holds neither the
sketch's name nor its error: a second failing value or a rename shows
up at once.
A page window (Preferences) filled its frame with
dialog_body.gamma_multiply(0.97), which also scales the alpha, so the
ribbon's labels showed through its title bar, search field and heading.
It now uses the opaque dialog_body, as every other window does; the
navigation bar keeps its own translucent resting fill.
* i18n(uk): add pending translations for missing strings
* i18n(uk): add pending translations for new strings from upstream
* i18n(uk): improve pending translations
* i18n(uk): professional translation of all pending strings
* i18n(uk): finish the pending translations for 29 October
Every value in uk-sync-29oct.json is now Ukrainian except the seven symbols
and formulas that must stay unchanged: the modifier keys, HDR and the three
inertia tensors.
Terminology follows uk.json: spot light is "прожектор", directional light is
"спрямоване", body is "тіло", render is "рендеринг". Camera orientation keeps
тангаж, рискання and крен.
Removes history-origin-bodies.json, which was empty after its content moved
into this file.
- worst_status searched the history and the results for each feature of a folded folder, the square of the history's length every frame
- A folder of 16 features or more now finds places and results in tables built once, first of an id winning as feature_index and Model::result do
- Frame on a 4,880-body design: 20.4 ms -> 7.9 ms (M4 Max, release, headless)
- Test a_folders_mark_is_the_worst_of_its_features_own
Co-authored-by: P'tite Tête <asso@ptitetete.com>
A new user guide page, Equation curves, under Sketching: the first curve step by step, Explicit and
Parametric explained with tables of rules to type (a line, a parabola, a wave, a circle, a spiral),
radians, parameters with the involute of a base circle, building a part (a closed flower curve
extruded), editing, and common mistakes. The technical details stay in a short section for scripts
and agents.
Seven pictures, rendered by `cargo xtask book` from new scripts in docs/book/scripts/equation-*.json
(gitignored output, attributed in attribution/pending/docs-equation-curves.md). The Sketching page
keeps a short summary and links to the new page.
* claim: #9184
* Control channel: a request's own param_check level, applied and confirmed by the app (#9184)
`engine.execute`, `engine.batch` and `engine.script` take an optional
`param_check: "off"|"warn"|"strict"` in their params. The app runs the
request at that level through the same `run_checked` path and batch
preflight a headless session uses, puts its own level back, and every
reply says the level it ran at as `param_check` beside `ok`. A command
that switches designs keeps the app's level; any other word is refused.
Readings and exports answered on a copy off the window thread keep the
level too.
`control.capabilities`, answered at once on the control-server thread,
lists what the channel does beyond the methods every version has:
`{"param_check": ["off", "warn", "strict"]}`. An older app answers
"unknown method".
The MCP bridge asks it once per connection. When the app lists the
level, every call and batch goes with it and the app checks against its
own commands; otherwise the bridge checks before sending, as #9073 does.
At strict, a reply that doesn't say the app checked the call is an error,
and the next calls are checked by the bridge. Also: `add_param_warnings`
takes the reply by `&mut`, and `with_param_check` sits beside
`Remote::new`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control channel: a design-switching command is checked at the request's level
Review fixes for #9184. `component.edit_in_place`, `component.finish_edit`,
`view.next_tab` and `view.previous_tab` ran at the app's own level and
echoed `off`, so a misspelt call went through at strict. They now have
their parameters checked at the request's level before they run (and
still run at the app's own level, as each design keeps its own), and the
reply echoes the requested level. The MCP bridge at strict now requires
the echo to match its level, not just to be there. `param_check` is read
only by `engine.execute`, `engine.batch` and `engine.script`; other
methods ignore it. The off-thread test now shows the design change
computing off the window thread before it is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch corners: a round that would use up a whole arc is refused
A quarter arc of radius 5 running into a line along it, rounded with
radius 5, put the round's tangent point at the arc's other end and left
an arc of no length. round_corner now skips a round whose tangent point
falls on either curve's other end; with none left it says that radius
does not fit, as for two lines (found by a local trim fuzzer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch corners: a round where the curves meet smoothly is refused
A line running on from an arc's end along its tangent (off only by rounding)
has no corner, but the round's tangent points landed within 1e-7 mm of it and
the round came out as an arc of no length (found by a local trim fuzzer). The
corner test's line met its arc this way too and was checking that output; it
now meets the arc at a corner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9179
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FAWPuFoh9qxn2uS2j53QZ
* sketch.inspect: each profile loop names a curve once, not once per segment (#9179)
A profile records one curve id per piece its loop walks, and a conic or
spline is walked as many short pieces, so sketch.inspect listed a single
Bezier curve 50 times in a profile's `curves` and as often in a hole loop.
The reply now names each curve of a loop once, in the order the loop walks
them (solidcraft_sketch::distinct_curves, as delete and profile references
already do). The solved profiles keep their per-piece lists.
The lists are now a profile pick as they stand: a whole_boundaries pick
refuses a curve named twice, and the regression test extrudes the wall
from the reply's lists.
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>
An SVG import leaves a sketch with regions and no body. The marquee (box select) took only the bodies, so the imported geometry was unreachable with the selection tools: a marquee over it selected nothing, and a click on a region did not take it either.
The marquee now takes the regions of the shown sketches when the design has no body. A design that already has a body keeps the old exclusive behaviour: the marquee takes the bodies alone, not the regions of the sketches that are shown. A dialog that asks for edges or faces (the Patch dialog) never takes the regions: they are neither.
* claim: #2829 (construct.* contracts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Typed contracts for the construction axes, points and planes
The 19 construct.* commands get input contracts and FeatureResult as
their output, with shared types for references, planes, a plane or
face base, an axis line and a construction plane's name, each calling
the reader its handler uses (split out of geo, plane_param, the face
capture and named_axis_line).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9188
* Preference: keep the view when a sketch opens, Look At faces it on request (#9188)
Preferences > Sketch > "Turn to face a sketch when it opens" (on by default,
so nothing changes unless it is turned off). Off: creating or editing a
sketch leaves the view as it is, a sketch started on a face no longer
re-centres on the picked point, and Look At (the palette button, Ctrl+8)
still turns to face the sketch. The control channel sets it as
`ui.prefs {"sketch_open_look_at": false}`.
Changes a default: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9087
* fix(#9087): ui.home refuses a sample index that names no sample
The control channel passed the sample index straight to open_sample, which drops an index that names no sample and says nothing. The request then replied ok, so an agent could not tell a typo from a sample that had opened.
The index is checked before it reaches open_sample: an index that names no sample is refused with the valid range in the error. A sample that exists still opens, as before.
* claim: #9086 (edge lengths)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Measure: an exact circle's or arc's length agrees with its circumference (#9086)
Measure gave a circle's or an arc's length_mm as the length of the
polyline drawn for it, which runs inside the curve, while the same reply
gave its circumference or arc_length from the exact circle. When the edge
is known exactly (the kernel's circle, or a sketch's own circle or arc),
length_mm is now its radius times its angle. A circle fitted to a mesh
body's polyline keeps the polyline's length.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #9029
Co-authored-by: Cursor <cursoragent@cursor.com>
* Square sketch axes when looking at a turned plane (#9029)
Look At only yaw-aligned sketches facing world up/down, so a sketch on a rotated component stayed tilted on screen. Roll (or yaw at the poles) so the sketch's x runs right and y is upright.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* claim: #9108
Co-authored-by: Cursor <cursoragent@cursor.com>
* Canvas calibrate: pick Point A and B with viewport clicks (#9108).
The Calibrate card only had typed coordinates, so users could not mark the image. Clicks on the canvas now fill A then B, with on-image markers and a short hint.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Calibrate canvas: ja/zh/ar/he/vi strings for the click hints
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Persistent naming: a proposal from a second implementation
A design proposal for the persistent-naming failures still open (#4341,
#5198, #5308, #5857, #5858, #5860, #5861), from building persistent naming
in Form Studio: fall back by lineage, never by geometry alone; a suppressed
maker is not a lost reference; names saved as structured one-level values;
ordinals from the feature, not the kernel's face order; split picks resolve
to all pieces; body identity apart from the display name. Documentation
only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Persistent naming proposal: body identity follows the #5308 plan
Change 6 now agrees with bluecmd's plan on #5308: an id minted from
next_id and stored per feature slot, better than the derived identity
the first version proposed. It adds that a stored id is only as stable
as the slot assignment, which today follows each lump's least face
name, and gives the lineage signature from the #5308 discussion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4515
Co-authored-by: Cursor <cursoragent@cursor.com>
* Inspect: keep analysis legends clear of a left History (#4515).
Draft, Accessibility and Curvature legends were painted at the viewport's left edge under a left History sidebar. Place them in the free workspace content instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
offscreen_device() asked wgpu for its default adapter and ignored the
SOLIDCRAFT_GPU policy that headless pictures follow, so with
SOLIDCRAFT_GPU=software the ui-egui offscreen tests still opened the
hardware GPU. Under hardware or software the adapter now comes from
solidcraft_gpu::select (new pick_among, which pick uses too); under cpu
there is none; unset (auto) keeps wgpu's default adapter as before.
Part of #9040.
Claude-Session: https://claude.ai/code/session_013WUFq6QFBqmZjxwaKy2J1M
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9147
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: the Sketch Grid box in the Sketch panel turns the sketch's grid off (#9147)
The box sets the open sketch's hide_grid, but the viewport drew the grid
whenever a sketch was open, so hide_grid only reached the ground grid and
the box did nothing. An open sketch now shows its grid whatever the Grid
switch says, unless its own Sketch Grid option is off; the window guides
that renders and the control channel use follow the same rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Release 0.27.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render job test: wait for the cancelled job's reservation to be released
The worker drops a job's reservation just after the job turns terminal, so
checking admitted bytes right after wait_timeout raced (10 of 40 local runs
failed). Poll with a deadline, as dielectric_tests does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7521
* fix: headless pictures return their draw counts with the picture
`the_renderer_counts_what_it_draws` drew with `Capturer::rgba` and then
read `Capturer::last_stats()`. The capturer is shared with the binary's
other tests, which draw on it from parallel threads, so another picture
could land between the two calls and the test read its counts (e.g. 10224
triangles for the empty design).
`Capturer::rgba_with_stats` returns the picture and its counts, read while
the call still holds the device. `last_stats()` is removed; this test was
its only caller (the performance overlay reads the viewport renderer's own
stats handle).
New test `pictures_drawn_at_once_each_count_their_own` draws empty and box
designs from eight threads on the shared capturer. Against the old pair it
failed 25 of 40 full runs of the `capture` binary; it passes with the new
call.
* fix: a headless picture waits for its read-back's answer
A picture maps its read-back buffer, waits for its submission with
`device.poll(PollType::Wait)`, and then checked once with `try_recv`
whether the map callback had run. wgpu calls a finished mapping's
callback from whichever thread next maintains the device, in a poll or
in any `queue.submit`, after it lets go of the device. When another
thread got there first, the wait returned with the mapping done but its
callback not yet called, and the picture failed with "not supported yet:
reading a picture back without waiting for the browser", the error meant
for a browser.
On the shared capturer, the other thread is another picture's device
check (`probe_lost` submits nothing before it takes the capturer's lock),
so the `capture` tests failed now and then: `bad_picture_sizes_are_errors`
(its last 8 x 8 picture), `pictures_show_a_plain_cut_without_the_hatch`,
`pictures_blend_a_line_that_is_not_opaque`. In the app, the window's
frames are submitted on the device its thumbnails and `ui.render`
pictures are drawn on.
A native picture now waits for the answer, up to the picture timeout; a
browser still answers later, on its own.
New test `pictures_read_back_while_another_thread_uses_the_device`: 2,000
small pictures while another thread polls their device. Without the fix
it failed 19 of 20 runs (1 to 7 pictures each); with it, 150 of 150 runs
pass. `bad_picture_sizes_are_errors` now prints the error it gets.
* Sketch: snap where two recent centre and midpoint guides meet (#8694)
The alignment guide remembered one reference and did not move the cursor. Keep the previous centre or midpoint as well, and snap a drawing point to where their guides cross.
* Sketch: keep the recent-guide crossing snap off until asked (#8694)
The crossing snap was on whenever alignment guides were. Give it its own Sketch preference, off by default, so the guides stay visual until someone turns the snap on.
* Sketch recent snap: ar/he/vi strings and clippy borrow fixes
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: #9072
* MCP: --param-check checks calls to the running app (#9072)
`mcp --connect` (and `mcp` once it bridges to an app) took
`--param-check` and forwarded every call unchecked, while the app's own
session is always off. `Remote` now checks each `engine.execute` and
`engine.batch` against this server's command contracts before sending:
`strict` refuses a call, or a batch with such a step, without sending
anything, and `warn` adds `param_warnings` to the app's reply (each
step's result in a batch). A command with no contract here goes to the
app unchecked. `Auto` carries the headless session's level over when it
bridges to a late app. `batch_preflight::contracts` is the contract half
of the existing preflight, for a runner in another process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fold the pending design format changes (sketch-equation-curves, timeline-joint-folders) into format 44.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
theme::apply always sets egui's theme to light or dark, and egui's own window
sync then told the native window to be light or dark. A macOS window told so
keeps that appearance and no longer reports the system appearance changing,
so with Theme set to System the app stayed light after macOS turned dark. The
app now syncs the window itself: the system's appearance while the theme
follows it, the theme's own otherwise (for the title bar).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9025
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Sketch: an old edge-on projection keeps its line when it is re-resolved (#9025)
A face seen edge-on was saved as two lines on the same two points, one each way, and is one line now. Re-resolving the link dropped both lines and built a new one under a new id, so an offset that names the first line as its source failed with "an offset source vanished or changed shape". Keep the first line and drop only the second.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
The check for a constraint that shrinks a curve to a point took under 1e-6
for a point. One solve could squeeze a 49 mm side to about 1.2e-6 mm and
the next take it the rest of the way, so neither solve tripped it (two
rectangles' corners made coincident, found by a local sketch fuzzer). Under
1e-5 now counts: in 4000 random sketches the constraints that still
collapsed a line went from 9 to 0, and every refusal rate stayed the same.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9015
Co-authored-by: Cursor <cursoragent@cursor.com>
* Evaluate bare length offsets in the design's units (#9015)
Feature expressions like baseLength - 1 were subtracting millimetres because val() parsed with mm defaults and only applied design units to a final unit-less result.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* claim: #6921
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CLI: run refuses unknown options and takes --param-check (#6921)
`solidcraft-cli run` now checks its options through `known_args`, so a
mistyped option fails instead of being ignored, and takes
`--param-check off|warn|strict` with the MCP server's words and default
`off`. `warn` prints each unknown key with a suggestion on stderr and
runs the step; `strict` stops at that step and writes nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CLI: run's option check takes --param-check; warn on timed runs as steps complete
`run` keeps #6919's (#6918) unknown-option check, with `--param-check`
among the options that take a value. `Session::run_script_timed` now
takes the same per-step callback as `run_script_each`, so `run --timings`
or `--profile` at the warn level reports a step's warnings even when a
later step fails, instead of after the script (and not at all on a
failure).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8644
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Web: open tabs share settings changes, so one tab no longer undoes another's (#8644)
Every open tab of the web app saved its whole copy of the settings to the
same browser storage, every 30 s and on focus loss, so a tab still holding
old settings put them back over a toolbar change made in another tab.
A tab now writes its settings in the frame they change, and the other open
tabs load them from the browser's storage event, so their own saves keep
them. Settings loaded that way aren't written back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #9016
Co-authored-by: Cursor <cursoragent@cursor.com>
* UI: keep Distance entry under Preferences and Report a bug (#9016)
Canvas value boxes used Foreground order, so they covered system windows. Draw them as Background chrome instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Sketch Trim: a cut at a curve's own end leaves no piece of no length
A curve that ends where another one ends crosses it there only within the
intersection's tolerance (1e-6), but bracket dropped cuts at the curve's
ends only within 1e-9 of its parameter range. A line ending on an arc's end,
trimmed from its other end, was cut a hair inside its own end and kept a
piece about 1e-7 mm long (found by a local trim fuzzer: 78 such trims in
4000 sketches, and 8 breaks). bracket now also drops a cut within that
tolerance of the curve's ends, as add_cut merges cuts that close together.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Extend: a touch at an arc's growing end is that end, not a stop
An arc whose end is where a line ends that touches the arc's circle there
found the touch a hair beyond the end (within the intersection's tolerance),
stopped on it, and added an arc from that point to itself. A hit within that
tolerance of the growing end (1e-6, as for the far end) is now the end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A constraint whose only solution squeezes curves to a point (a rectangle's
corner made the midpoint of its opposite side, or two opposite corners made
coincident) collapsed the whole rectangle while the sketch said solved. A
solve that shrinks a line or circle from over 1e-3 to under 1e-6 now counts
as one that fails, as one that throws points far away does: a strict edit is
refused, another keeps the sketch from before its solve, and the free solve
after the held ones in apply_in_order refuses the constraint.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Skip drawing and interaction for history chips outside the strip's visible part, after their slot is recorded
- Keep a dragged or focused chip, and every chip while a popup is open
- Frame on a 4,880-body design: 20.3 ms -> 14.6 ms (M4 Max, release, headless)
- Test a_long_timeline_paints_only_the_chips_in_view
Co-authored-by: P'tite Tête <asso@ptitetete.com>
On a Windows account whose %TEMP% is a short (8.3) path, such as
C:\Users\MATHEU~1\AppData\Local\Temp, three tests failed because they built
their folders from std::env::temp_dir(), and the agent file scope refuses short
names ("not allowed for this agent: short (8.3) names such as `PROGRA~1`"):
- engine: cmd::render::settings_tests::embedded_hdr_capture_rejects_parameters_scope_and_cpu_primaries_without_mutation
- ui-egui: agent_chat::tests::file_actions_wait_for_the_user
- ui-egui: agent_chat::tests::permission_modes_decide_what_asks
They now use vfs::temp_dir(), which spells the folder with long names, as
libraries_tests and motion_tests already do (#1385, #1211).
Checked on Windows 11 x64 with the 8.3 TEMP: the agent_chat and
render::settings tests (113) pass, and clippy -D warnings is clean for both
crates.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7287
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Command search: show a command's place in the ribbon, also inside section menus (#7287)
Hovering a command result in Search commands switches the ribbon to the command's
tab and marks its button with its tooltip; a command that has no button there
opens its section's drop-down on its row instead. Leaving the results (or closing
the box) goes back to the tab the user was on, so pressing S, typing and Enter
works as before.
Starting a command from the box makes its button or section label blink twice,
and a command started from a section drop-down marks that section's label while
it runs, like a pressed button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Command search: the ribbon reveal is a preference, off by default (#7287)
Preferences › Interface › Show search results in the ribbon (`ribbon_reveal`) turns on the hover
preview, the blink after a start and the running mark on a drop-down's panel label. Off, the
ribbon behaves as on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pending locale: Arabic, Hebrew, Japanese, Vietnamese and Chinese for the ribbon reveal setting
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: #7912
* Sketch: dimension a sketch entity against a model edge
A dimension took sketch entities only, so measuring a sheet-metal body's edge
was refused with "cannot dimension that combination of entities". The
entities now take a model reference too, an {edge: [x,y,z], body?} or a
{vertex: [x,y,z], body?}, which is projected into the sketch first, as the
constraint commands' operands are since #6348, and measured there.
The Dimension tool picks a body's edge or vertex when the sketch has nothing
under the cursor, and reads the pick back into the reference the command
takes.
* style: rustfmt the sketch dimension command
* Cap the entities one call may project
A dimension projects the model references among its entities into the
sketch, one link and one resolve each, and the loop had no cap: a single
request could make the engine resolve thousands of model edges before
the dimension refused the list. The count is now refused before anything
is projected, and the projection loop stops at the same limit the
Project command uses.
* Sketch dimension on a model edge: use the sketch store after the store refactor
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 cursor correction tries the constraints' free directions a whole step at
a time and took a step as soon as the cursor's distance fell, by any amount.
Along freedom that can't reach the cursor (the far end of a line held only
by its direction), retracting onto the constraints still moves the cursor by
rounding, so such steps were taken: the end walked away a step at a time, and
a line measured by an angle could shrink to nothing, as in #7635. A step now
has to move the cursor itself by more than the solve's tolerance.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6237
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Settings profiles: save, switch, export and import input and interface settings (#6237)
A profile keeps the Interface, Navigation and Sketch preferences (mouse mode,
radial menu and gestures with every studio's lists, theme, scale, font, ...),
the language, the keyboard shortcuts, the toolbar, the S box favourites and
the imported theme it uses under a name. Machine-specific settings stay out
(3D mouse, graphics, units, AI, recent files).
- Preferences > General > Settings profiles: choose the active profile to
apply it, Save, Rename, Delete, Export... and Import...; a note shows when
the settings differ from the saved profile; Cancel takes a switch back.
- settings.profile.{list,save,apply,rename,delete,export,import} commands for
the control channel, palette and scripts; the engine checks the parameters,
the app does the rest.
- One `solidcraft-profile/1` file; import checks the format and name, drops
unknown keys and unreadable values, caps sizes, never switches, and never
panics. Each preference is applied through prefs::set, so a value this
computer refuses keeps its current one and is reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Glossary: settings profile, and "profile" among the words with more than one meaning (#6237)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Settings profiles: new strings in a pending locale file, not the catalogs (#6237)
New UI strings now go to assets/locales/pending/ (CONTRIBUTING.md), so
this pull request no longer edits the shared catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Preferences snapshot: compare the toolbar, favourites and profile too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Settings profiles: keep a command's several keys (#6237)
Since a command can have several keys (#6288), the keymap saves a list where it has more than
one. A profile's checks only took a single key, so saving or importing a profile dropped such a
command's shortcuts. They now read the keys as the keymap does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Settings profiles: Arabic, Hebrew and Vietnamese strings (complete catalogs) (#6237)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Tripper <tripper@localhost>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #8927
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: a pre-selected sketch line lying on a body edge goes in as that edge (#8927)
With a finished sketch shown, a click on a body edge that one of its lines
lies on picks the sketch line. Fillet and Chamfer take edges and no sketch
curves, so the pre-selection found no input to fill: the tool opened with
Edges empty and the selection was dropped, and only a second click with the
tool open (which picks the edge) made it work.
When nothing in the dialog takes a pre-selected sketch curve but an input
takes edges, look for the body edge the curve lies on from end to end
(display polylines, with room for a curved edge's chord sag) and take that
edge instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Preferences > Graphics > Viewport gets a Field of view row under Perspective view: the UI kit's
slider in degrees, 15 to 90, dragged or typed and kept to whole degrees, enabled only while
Perspective view is on. The preference is perspective_fov, in radians (default 0.6, about 34°,
so the default view draws exactly as before), and Restore Defaults puts it back.
One place decides the angle the view is drawn with: SolveApp::view_fov(), the preference with
Perspective view on and 0 with it off or while a sketch is open. A saved value outside 15°-90°
reads as the default. projection and fit_bounds read it (fit_bounds' second hard-coded copy of
the 0.6, tan(0.3), now goes through Camera::half_height), and so does the headless fit.
perspective_view() stays, as view_fov() > 0, so #4291 needs no change.
When the angle changes from one perspective angle to another, the camera's distance scales by
tan(old/2) / tan(new/2), with a running view animation's ends, so the target keeps its size and
the slider changes the depth, not the zoom. prefs::follow_view_fov does it once per frame and
after ui.prefs, Cancel and Restore Defaults; a parallel view keeps the last perspective angle,
and while Render is open the camera is Render's own, so the change waits for the design view.
The Render workspace's Perspective check box turns perspective on at the preference's angle,
not π/4; a saved render setup keeps its own. ui.prefs takes perspective_fov and refuses values
outside the range; ui.camera reports the angle the window draws with, and checks a replayed
camera against it, so its reply still replays. Field of view is in en, ar, de, he, ja, ko, vi and zh;
the ui.prefs settings list in docs/control-protocol.md and the guide's tour describe it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8865
* parameters.add: a parameter with no unit takes the design's units (#8865)
A new parameter given no unit took "mm" for any length or bare number, so
in an inch design `parameters.add {name: v, expression: "40/2"}` made a
20 mm parameter where the Parameters dialog makes 20 in. The unit a new
parameter infers is now a length in the design's units (mm designs are
unchanged); parameters.change on a new name and parameters.import share
the same inference. Angles and unit-less values keep their units.
A definition typed into a value of no fixed kind (a coupling's pitch,
a joint's value) still reads its bare number as millimetres, as that
field does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8912
Co-authored-by: Cursor <cursoragent@cursor.com>
* View cube: rapid clicks add full turns from the in-flight target.
Match arrow-key retargeting so a second cube click turns from the glide destination instead of the mid-animation camera (#8912).
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Problem: a design saved before body names were pinned (#6403) is pinned by its first command
that changes it, as part of that command. Undoing that command went back to the unpinned design,
whose fingerprints no evaluated result matched any more, so the first undo after opening
evaluated the whole timeline again: 63-72 s in the SELENE rover (5,484 features) when opened
from the command line or headless (`Session::opened_at`), 0.08 s with this change. `doc.open`
pinned at the end of the command instead, which kept undo fast but left the design with unsaved
changes on opening.
Solution: `Session::opened_at` pins the names as it opens the design, with the names the file's
bodies have (no result changes, so nothing is evaluated), and takes the pinned design as the
saved one: opening is no change.
Claude-Session: https://claude.ai/code/session_013WUFq6QFBqmZjxwaKy2J1M
Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Favorites, User and Model sections stay lined up when only some lists hold a long name (follow-up to #8551).
Co-authored-by: Cursor <cursoragent@cursor.com>
home.settings and ai.connected_toast follow the English change from Settings to Preferences (التفضيلات, as ui.preferences). Hover is no longer rendered as scrolling in the browser and navigation bar options, the Project options dialog label is fully in Arabic, and the trackpad zoom key help says to hold the key.
* claim: #8875
* Text fields: a right-click menu with Cut, Copy, Paste and Select All (#8875)
Every kit text field (dialog and sketch value fields, the sketch dimension
box, canvas value boxes, names, text areas, passwords, read-only text) now
calls one shared ui_kit::text_menu::menu after drawing its editor. Rows are
disabled when nothing is selected, the text is read-only or a password, or
the clipboard holds no text. The edit marks the response changed and gives
the focus back, so owners that commit on lost focus keep the field; a right
press inside the selection keeps it; Esc closes only the menu; expression
suggestions wait while it is open. New message ui.select_all in a pending
locale file; Cut, Copy and Paste reuse ui.cut, ui.copy and ui.paste.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8860
* Timeline: delete after the marker counts and deletes the shown features (#8860)
With the bottom timeline filtered to one component, "Delete N Features After
History Marker" took the marker as a position among the shown features but
sliced the design's feature list with it. N counted features of other
components, and confirming deleted them too, including features before the
marker.
The entry now gets the feature's design position, the design's marker and
the shown positions, and deletes the shown features after the marker; the
label counts the same set. The history beside the browser shows every
feature and passes no filter, so it behaves as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Timeline: look up shown positions by binary search, test the entry before the marker (#8860)
The shown positions are ascending, so a binary search finds each one without
walking the list for every feature after the marker. The test now also checks
that a shown feature before the marker does not offer the entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A segmented control measured its options translated but painted them as
the caller passed them, so the many callers that pass English labels
(the Coil's Circular | Square and Inside | Center | Outside, Revolve's
One Side | Two Sides | Symmetric, Shell's direction, ...) showed English
in every language. Only where the row was too narrow and the control
fell back to its dropdown, which translates, did they read right.
Segments now paint, size, describe and tip their options translated;
the handles keep the option as passed.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7435
* Viewport: the 3D mouse, the bridge and drag-zoom stop at the model, like the wheel (#7435)
The wheel and pinch hold the model point under the cursor, so the view
comes up to the surface and stops there. Every other zoom path passed no
anchor, so the 3D mouse cap, the navigation bridge and drag-zoom zoomed
straight through the surface into the body.
A device has no pointer, so the cap and the bridge now hold the model
point in the middle of the view; drag-zoom holds the point under the
pointer, like the wheel. With "Zoom to the cursor" off the old
zoom-through-the-model behaviour stays, as it does for the wheel.
The orbit half of the issue is a design decision and is untouched.
* Viewport: drag-zoom holds the point where the drag started (#7435)
The zoom anchor followed the moving pointer, so the zoom slid away from
what was pressed on as the drag went on. It now holds the press point,
the way the wheel holds the point under the cursor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 3D mouse: turn around the model in the middle of the view (#7435)
The cap and the navigation bridge turned the view around the view's
centre, which after a pan or from far away sits behind the face, so the
part swung away while it turned. They now turn around the model point in
the middle of the view (the middle of the bodies in view over empty
space), and keep it still on screen. Preferences > Navigation > "Turn
around the model in the middle of the view" switches back to the old
behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 3D mouse: keep the turning point until the cap is back at rest (#7435)
The point the cap turns around was picked again every frame, so it slid
to whatever came into the middle as the view moved. It's now chosen when
the cap leaves rest and kept until the cap comes back, the way a mouse
orbit keeps the point it started on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 3D mouse: show the point the view turns around (#7435)
While the cap turns the view, a faint ball in the accent colour marks the
model point it turns around, so it's clear what the turn holds still. The
point is now picked when the cap starts turning, not when it only pans or
zooms, so a pan shows no ball.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 3D mouse: orbit is a choice of Traced or Classic (#7435)
The checkbox becomes a two-way choice on the 3D mouse section's form:
Traced turns around the model in the middle of the view when the turn
begins and marks it with a faint ball; Classic turns around the view's
centre as before. Traced stays the default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: the 3D mouse's Traced and Classic orbit (#7435)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Make model zoom and traced orbit opt-in (#7435)
---------
Co-authored-by: nomadsgalaxy <16734355+nomadsgalaxy@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8899
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
* Sketch: keep Diameter box when placing a second concentric circle (#8899)
A centre-point circle's press starts drag-to-draw; releasing on the same
centre (or close to the press) must keep the stage-1 Diameter field instead
of resetting it and risking a second on-click that finishes the shape.
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
* claim: #8352
* Sketch: equation-driven curve, a fit point spline from x(t) and y(t) formulas (#8352)
sketch.equation_curve draws a curve from formulas in the expression
language and the design's parameters: x(t) and y(t) over a range of t, or
y = f(x) over a range of x. The curve is a cubic fit spline through fixed
points sampled from the formulas, as many as keep it within a tolerance
(0.01 mm by default, at most 500 points); a curve that ends where it starts
closes smoothly. The sketch keeps the formulas (one new design-format
file), places the points again before every solve so they follow the
parameters, and sketch.equation_curve.edit changes them, keeping the joined
end points. Both commands have input and output schemas.
* Sketch: Equation Curve window, its Edit menu item, labels and guide (#8352)
The ribbon's Equation Curve opens a window for the form (parametric or
explicit), the formulas, the range and the tolerance; OK runs
sketch.equation_curve. Right-clicking an equation curve offers Edit
Equation Curve, which reopens the window on its formulas and runs
sketch.equation_curve.edit. New labels and help in a pending locale file
(with ar, de, he, ja, ko, vi and zh), the icon, the glossary term and the
sketching guide.
* Equation curve: one if for closing the sampled points (clippy collapsible_if)
A held solve applying a constraint, or the edit's own solve, could find a far
solution of the constraints and move points millions of millimetres while
the sketch said solved. Such a result now counts as a failed one: the next
held solve is tried, or the edit is refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7828
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: hold horizontal/vertical and line up with other points at once while drawing (#7828)
While drawing, the pointer lines up horizontally with the nearest sketch
point level with it and vertically with the nearest one plumb with it, at
the same time, as a dragged point already does; holding H/V with the
previous point still lines up across with another point. Dotted guides run
from the points lined up with (H, V or H+V), and the click adds a
Horizontal or Vertical constraint with each, in the shape's undo step,
unless the sketch already has it.
A click takes the tool out of the app while it runs, so refine_snap
treated every tool as the Line tool then: a rectangle's click could take a
line-extension inference its hover never showed. The tool's points and
kind are kept from the last hover, as the previous point was.
Preferences > Sketch > "Line up with other sketch points while drawing"
(sketch_align_points) turns it off for the old behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: lining up with other points is off unless turned on
Review on #8474: sketch_align_points defaults to off, so nothing changes for anyone who
doesn't turn it on. With it off, a click also treats the tool as before (the Line tool's
inferences), instead of keeping the tool's kind from the hover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8570
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Design: pick one of the other open designs, or browse for a file (#8570)
With other designs open in tabs, Insert Design first lists them under the
button: one click inserts a saved one as a linked component; one with
unsaved changes inserts its saved file and the status line says so; one
never saved is greyed out, as there is no file to link. Browse... opens
the file picker, as the button did before, and it still goes straight
there with no other design open.
Preferences > Design > Linked designs > "Insert Design lists the designs
open in other tabs first" (insert_lists_open_designs) turns the list off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Design list: sit with the other menus' items, so run_item keeps its doc comment
Review on #8571: insert_design_items had landed between run_item and its comment. The
rebased menu-walk list keeps main's Target::HistoryControls beside Target::InsertDesign.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8100
* History: say why an edited feature behind the marker changes nothing (#8100)
Editing a feature that sits after the history marker (double-clicking its
greyed-out item) rolls back to it, previews the change, and on OK puts the
marker back where it was: the feature stays rolled back, so nothing in the
view changes and nothing says why. That is the silent "pattern of a cut
does nothing" in #8100 (the reporter's design has its pattern behind the
marker; on main the pattern itself builds fine once rolled forward).
- timeline.redefine replies `rolled_back` and, when true, a `note`; it also
carries the feature's `warning` (for a rolled-back feature, the one its
constructor met building it at its place).
- The window shows that note in the status line after such an edit.
- A rolled-back item's tooltip in both History layouts says it is after
the history marker and not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pending locale: Arabic, Hebrew, Japanese, Vietnamese and Chinese for the rolled-back notes
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: #8639
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch profiles: close the joints near-coincident curves leave open (#8639)
Two free-form curves that lie on each other along a stretch (a DXF that
carries an outline twice, with the same straight sides and different
corners) are cut into sampled pieces whose ends the graph joins into one
node within 1e-4 mm, but the pieces keep their own ends, a few millionths
apart. Only analytic edges were closed at those joints, so the sliver
loops between the two curves stayed open by up to 1e-5 mm, more than the
1e-6 a solid needs. The region around them took those loops as holes:
extruding it failed with "profile loop is not closed" and it drew no
fill when picked, so it looked unselectable.
Close a line's joint in each finished loop: line to line at their middle,
a line onto any other segment's end. On the reporter's DXF the band
between the outer two rings now extrudes (110.5 mm2 by 2 mm = 220.9 mm3),
and the zero-area slivers no longer form profiles (221 -> 216).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8863
* Sketch: the status line after a dimension is in the interface language (#8863)
The Dimension tool built its "Dimension applied" status sentences (new
parameter, renamed parameter, plain value) and the driven-dimension note
with English format! strings, so they stayed English under a translated UI.
They now come from the catalog (one pending locale file: en, ar, de, he,
ja, ko, pl, vi, zh), with the typed parameter name passed in as is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8861
* History: Delete on a selected feature deletes the selection in both timelines (#8861)
With several features selected, Delete in a selected feature's menu deleted
the whole selection in the bottom timeline and only that feature in the
history beside the browser, which offered the selection as a separate
Delete Selected Features… entry.
Both menus now use one Delete entry: the selected features when the
right-clicked one is among them, labelled "Delete N Selected Features",
else that feature alone. The history beside the browser no longer adds its
own Delete Selected Features… to a selected feature's menu, since the entry
above covers it; folder menus keep it. The single-feature delete helper had
no callers left and is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* History: a feature's menu leaves out Delete Selected Features… by a flag (#8861)
A right-click on a feature outside the selection selects that feature
first, so in a feature's menu the clicked feature is always selected. A
flag that says the menu is a feature's says the same without the lookup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8006
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Parts library: button head cap screws, ISO 7380-1 (#8006)
Insert Part offered the socket head cap screw but no button head, so a
design using button heads had to stand in a cap screw and accept the
wrong head. A button_head_cap_screw family now sits beside it, taking
the same parameters from the ISO 7380-1 table over M3 to M16.
The head is a new Head::Dome: the shank and the head revolve as one
outline, with the rim at dk, a short cylindrical flank, the arc of the
crown, and a flat top around the socket, so the hex socket stays a plain
cut on a plane. The standard draws the flank and the flat but dimensions
neither; they are 0.15 k and 0.7 s, and the comment on those constants
records what that costs against the standard's crown radius r1 and why
neither other fractions nor r1 taken as given does better. dk, k, s and
t are exact, each checked against three reproductions of the table.
Insert Part, Change Part Size and the bill of materials need no change:
they read the families and their table rows from parts.library.
The wider fastener request is issue 7692; this is only the one family
from it, and leaves the rest of that list open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Parts: format after the rebase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pending locale: the Button head cap screw family name
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>
* Components: copy and paste bodies and components between designs and components (#3390)
Squashed for the rebase onto main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #3511
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Paste New: copy from a design other than the one pasted into
`component_clone::paste` read the design it copies from through the session it pastes into.
`build` now takes the source (document, evaluated model, unapplied moves) apart from the
target session; Paste New within one design passes the session as both, as before. Names are
numbered against the target's names.
From another design (`Source::copied`, a `ForeignCopy` snapshot taken at copy time):
- names are kept where the target doesn't have them, else numbered;
- the user parameters the copy uses come along, `w_2` where the target has a `w`, with the
copied expressions following;
- a copy whose features use what isn't in it (bodies, sketches, faces, parameters outside it)
is refused with a list of what; `FeatureKind::input_bodies` lists the bodies a feature uses;
- configurations stay with their design, and the target's timeline marker must be at the end;
- a body moved in from outside comes as an imported base named `BodyImport1`.
Body transfers are pointed at the copy's components before they are added: `add_feature`
checks that the components they name exist, which another design's don't.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Engine: paste bodies and components copied in another design
`component.paste_from_design` pastes what `Session::foreign_copy` holds, at the same world
coordinates as in its design, into the active component or `component` (placed by
`component_path`; a component placed more than once gets it in every instance), as one step
to undo:
- bodies as imported bodies, without their history, in one Import feature (named as any
import is, "Import1"); a body seen in another placement ("Cube (B:2)") pastes as "Cube";
names the target has are numbered;
- components as independent copies (Paste New from the snapshot): with their history,
appearances, materials, joints and motion inside them; joints to the outside stay behind.
Tests: bodies (position, naming, target component, undo; a body of another placement) and
components (history, appearances, a part placed twice, editing independently, save and reopen,
undo; user parameters renamed; refused when using a body outside it; bodies moved in).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Clipboard: copy in one design, paste in another
Copying or cutting bodies or components also keeps a snapshot of them (`ForeignCopy`); another
tab pastes it with `component.paste_from_design`: bodies as imported bodies, components as
independent copies (Paste and Paste New alike), folded and selected. Cut pastes as a copy
there, the original staying; Esc on the cut leaves nothing to paste anywhere.
The last copy in any design is what pastes: each copy or cut is numbered, and a tab whose own
copy is older empties its clipboard when it is switched to (`drop_stale`). A failed copy
anywhere leaves nothing to paste in any tab.
Tests: bodies and components pasted in another tab, the last copy winning across tabs, cut
as a copy. `copied_bodies_stay_with_their_design` gave way to the cross-design test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Clipboard: what is selected wins over a design row picked earlier
Copy copied the whole design as a linked component whenever the design's
browser row was still picked, even after a component, body or feature had
been selected in the view or another tab, and failed in an unsaved design.
The design row now gives way to anything selected, and switching tabs
drops the picked components of the design left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Put the Paste from Another Design label in the pending locale file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: unground the placement before moving it (a design's first part is grounded)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop locale keys main already folded into en.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pending locale: Paste from Another Design in the complete catalogs
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: #7409
* Keyboard Shortcuts: the read-only list says so and leads to the editable page (#7409)
Home › Keyboard shortcuts and Help › Keyboard Shortcuts open the
Shortcuts page read-only, in the Preferences window with its sidebar and
Cancel/Apply/OK, so it looked exactly like Settings › Shortcuts with
Import…, Reset All and recording missing. The read-only page now says
"Read-only list: shortcuts can't be changed here." and has an "Edit
shortcuts" button that turns the same page into the editable one.
Strings in assets/locales/pending/shortcuts-read-only-note.json (en, ja,
zh). Harness test opens it both ways, checks the note, clicks Edit
shortcuts, and finds Import…, Reset All and a recordable binding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pending locale: Arabic, Hebrew and Vietnamese for the read-only shortcuts note
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: #8864
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport: with Orbit, Pan or Zoom lit, a press held still keeps the highlight under it (#8864)
A mode lit in the navigation bar, or a held Space, owns drags, not a still
press (#8438), but the hover pick counted any left press under such a mode
as navigation and dropped the highlight, so the line under a Select Other
hold went dark until the list opened. Only a preset's left-button binding
or Ctrl+left orbit navigates from the press on now; a lit mode's drag
still drops the highlight once it starts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Sketch Intersect: add Faces/Bodies and Projection Link options (#1110)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Sketch Project: add Geometry/Bodies and Projection Link options (#1110)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Sketch Project/Intersect: keep exact lines and arcs of whole bodies, no noisy rebuild warning (#1110)
Whole-body silhouettes and sections fit lines and arcs tightly and snap arcs
tangent to their neighbours. A rebuilt projection warns only when it removed
the user's own constraints or curves. Pending locales cover ar, he and vi, and
the preselection tests use the direct-pick workflow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Select Other: label curves from Intersect as intersected (#1110)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* claim: #8692
* Browser: remember the folded card across runs, optional reveal on hover (#8692)
The browser card's fold (the chevron in its header) is now a preference,
saved with the other layout state, so the next run starts the way the user
left it. Preferences > Interface > Layout adds Shown / Reveal on hover: the
browser then stays out of sight until the pointer comes over its place or
the thin tab at its left edge, fading in like the navigation bar's Hide
until hovered, and stays while the pointer is inside or a menu, drag or text
edit started from it is open. It never folds in that mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: move the reveal-on-hover messages to a pending locale file (#8692)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8306
* Timeline: joints in feature folders (#8306)
timeline.group and timeline.move_to_group take joints as well as features, so
hundreds of joints fold into one folder tile. A closed folder shows one tile
whatever it holds; opening it shows its features and joints in both the bottom
and left History. Suppress, delete and ungroup on a folder reach its joints too
(one undo step), deleting a joint removes it from its folder, and an unknown
joint id or name is an error. Saved with the design; designs without joint
folders read and write as before.
* claim: #8862
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: Enter after `pi mm` keeps the unit, not a longer name (#8862)
A unit after a name or constant multiplies it, as after a number (`pi mm`
is pi millimetres), so it is a complete word there: the longer names are
listed unarmed and Enter submits what was typed. A unit at the start, or
after an operator or bracket, stays a prefix as #3664 chose.
Whether Enter completes is now worked out again every frame, unless a key
armed the list. It was kept from when the list first appeared for a word
and caret position, so it went stale when the text before the word was
replaced, or when the box took the focus before the names were gathered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #7023
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Status bar: switch the mouse mapping from a menu (#7023)
The status bar gets a mouse-mapping button beside the units. It shows the
current mapping, and a click opens a menu of the mappings above the bar.
Picking one sets what Preferences > Navigation sets for it (bindings, wheel
direction, middle double-click, right drag orbits) through
choose_nav_preset. Nothing changes while the status bar is off (the
default).
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: #4176
* Preferences: mouse-wheel zoom speed (#4176)
A 0.25x to 4x multiplier for each wheel zoom step, applied in
navigation::handle so every canvas uses it. The 3D mouse keeps its own
speed. Values outside the range are refused when set and reset to 1x when
loaded.
* Control protocol: wheel_zoom_speed in the sorted ui.prefs list (#4176)
* Wheel zoom speed: its label in the complete catalogs (#4176)
* Arabic: fix the Resize Part hint (tanween style, part list, command name)
* Arabic: navigation bar Edge reads as position, tanween spelling in the geometry budget help
* claim: #8040
* wip: 3D line/spline/point tools (#8040)
* Take sketch.move3d off the toolbar help backlog now that it is agent-only (#8040)
---------
Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
* claim: #8794
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FAWPuFoh9qxn2uS2j53QZ
* Control token: a closing window removes only its own token and discovery files (#8794)
Under Flatpak each instance has its own loopback, so a second window
binds the same port number as the first and writes the same
<port>.token, <port>.pid and current.port in the shared data folder.
At exit the first window deleted them all, unconditionally, leaving the
second window running with no token file a client could read.
remove_token_file now takes this launch's token and removes the token
and process files only while the file still holds it; the process id
can't tell instances apart, as every Flatpak instance has the same one.
remove_current_port also takes the token and keeps the discovery file
while another launch's token file holds that port, and removes
current.pid with current.port (it was left behind).
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>
* claim: #8835
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a selected text moves when dragged from anywhere inside its frame (#8835)
A drag had to start on one of the text's letters to move it. From the
space between letters, or inside a letter's outline, it found no geometry
and drew a selection box instead.
Off the sketch's own geometry, a drag from inside the selected text's
frame now grabs the text, as one from a letter does. Handles, dimensions
and geometry under the pointer still come first, and outside the frame a
drag from empty space still draws a selection box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Trackpad: zoom with two-finger scroll while holding a key, chosen in Preferences (#8322)
Holding the zoom key turns a vertical two-finger scroll (or a wheel) into zoom at
the pointer, with the wheel zoom's speed, direction and anchor, so a laptop user
zooms without pinching. Preferences > Navigation > "Zoom with two-finger scroll
while holding": Cmd on macOS and Ctrl elsewhere (default), Option/Alt, Ctrl, or
Off. That key with the scroll did nothing before, so no default changes; Off
keeps it that way. Shift keeps its orbit.
* Trackpad zoom modifier: off by default (#8322)
Scroll keeps its usual meaning until a key is chosen in Navigation.
* Trackpad zoom modifier tests: Off is the default; tests that zoom choose Cmd (#8322)
* Trackpad zoom modifier: zoom once alongside the Ctrl+wheel pinch from #8539 (#8322)
A wheel the chosen key claims is no longer also read as a pinch, and a key that is not
chosen keeps main's pinch handling unchanged.
Align gets an Object Type row (Bodies | Components). In Components mode it runs the new
occurrence.align command, which moves only the chosen occurrence placements (their
transforms) instead of adding an Align feature to the shared component, so other instances
stay where they are.
- occurrence.align: from/to points, snaps, planar faces and planes in world coordinates,
flip, capture/pending, grounded and rigid-group refusals as occurrence.move.
- A viewport or browser-body pick takes the placement directly under the active component.
- A placement inside a subassembly placed more than once still moves (as occurrence.move
does) but the dialog warns, and the reply lists it under shared.
corner_free looked only in a fixed column at the view's right edge and took
the largest free rect there by area. Next to a tall dialog docked at the
right edge, or the Assistant in the Floating layout, the narrow strip beside
the pane won: the toasts were squeezed into it, their text broke inside
words, and with a wider pane they were not shown at all.
It now tries that column first and then columns ending at each obstacle's
left edge, nearest the corner first, and takes the first free rect that is
TOAST_WIDTH wide and tall enough for a toast, as the frame-rate readout
moves beside a right pane (#6755); failing that, a rect tall enough beats a
wider one too short to show any toast. Without a pane the stack stays
exactly where it was.
* claim: #7688
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Graphics: a background colour of your choice for the 3D view (#7688)
Preferences › Interface › Theme gets a Background row: the theme's
gradient (the default, unchanged) or one colour for the whole application,
filling the view in either theme. The window keeps its own colours, a
picture of the view (`ui.render`) takes the colour too, and `ui.prefs`
sets it as `viewport_background` ([r, g, b] or null).
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>
* feat: add joint-aware assembly Move handles and live motion readouts
* test: improve render job failure diagnostics
* fix: preserve assembly Move compatibility and viewport theme colors
* ci: skip default label sync for fork PRs
* fix: keep Windows CI checks portable after main rebase
* test: cover RTL assembly strings and stable refraction job
* i18n: cover assembly Move in Vietnamese
* i18n: cover assembly Move in French
* fix: complete CGA palette for assembly motion handles
* fix: complete Assembly Move palettes and Turkish strings
* fix: retain assembly move cache answers and toolbar help
* style: format merged assembly Move navigation guard
* fix: align Assembly Move preferences and tests with main
* Fix offline update fixture synchronization and clipped prism intersections
Wait for all v5 manifest requests before deploying v6. Intersect hole-free prism sections over their common height, retaining the established B-rep path for newly clipped perforated sections. Preserve the failing property seed and add closed-form and cap-boundary regressions.
* claim: #8592
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* History: error and warning marks in the browser and on folded folders (#8592)
In the History "In browser" layout, a feature whose build failed gets the
error icon at the end of its row, one that built with a warning the
warning icon, with the message in the tooltip: the marks and text of the
History on the left. A folded History or Features folder carries the
worst mark of what it holds, and so does a folded folder in the History
on the left. Suppressed and rolled-back features are not built and get
none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- active_body: take every body's owner in one pass (model_body_components) instead of searching the timeline body by body
- Timeline strip: find each chip's result in a table built once per frame (result_index) instead of a linear search per chip
- 4,880-body design: UI frame 31-32 ms -> 7 ms of CPU; tests pin the active body and the result lookup
Co-authored-by: P'tite Tête <asso@ptitetete.com>
* claim: #8095
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Properties: moment of inertia, principal axes and first moments of a part or assembly (#8095)
component.properties and occurrence.properties report each closed body's
inertia tensor about its centre of mass, and in total the tensor about the
total centre of mass (world axes, g·mm²), its principal moments and axes,
the first moments, and with `about: [x, y, z]` the tensor about any point.
The tensor comes from each body's welded export mesh, tessellated once per
body, scaled to the measured mass and turned by each placement; bodies add
by parallel axes. The maths is a new crates/geom inertia module.
The Properties window shows the first moments, the tensor, the principal
moments and axes, and the view marks the centre of mass with the principal
axes while it is open. Inspect → Measure → Properties now opens that window
(it ran the command and showed nothing).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Properties: Copy matrix, and the whole design's card titled just Properties (#8095)
Copy matrix, under the tensor, puts it on the clipboard as three lines of
tab-separated values at full precision, in the units shown. The card for
the whole design is titled Properties; one opened on a component,
placement or bodies keeps its name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Properties: measure beside the window, so a large assembly doesn't freeze it (#8095)
Properties ran on the window thread: 18 s on a 144-body design, with the
window answering nothing meanwhile (#5203). In the desktop app the card now
measures on a copy of the session on a worker, the way #4614 made agents'
measures work, says "Measuring…" until the reply is in, and Close drops it.
A quick measure still shows at once; headless, tests and the web measure in
place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The wasm compile ran only by hand, so #8227 (a native-only DroppedFile::bytes() call in
crates/ui-egui) passed PR CI and the merge queue and broke the web build on main (#8534).
A merge group that touches the crates below the apps, the web app, the vendored crates, the
manifests, the toolchain or this workflow now runs `cargo xtask wasm`, and `ci` requires it.
Dependency advisories ran only nightly. A pull request or merge group that changes Cargo.lock
or a Cargo.toml now runs `cargo deny --locked check --warn unmaintained advisories`, and `ci`
requires it then. The nightly keeps the full advisories, bans and sources scan.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4127
* Render preview: geometry budget from the device, readable refusals (#4127)
The Render preview refused every scene over a fixed 128 MiB of geometry. On
desktop the budget now follows the device's buffer limit (eight of its
largest buffers, at most 2 GiB; 2 GiB with the usual 256 MiB buffers). The
web keeps 128 MiB.
The product upload runs under its own GPU error scopes: an allocation the
device refuses is reported in the preview instead of reaching the device's
uncaptured-error handler, and the half-uploaded batches are dropped. Buffer
uploads map the buffer themselves so a refused buffer fails the upload
rather than panicking in create_buffer_init.
Refusals name the geometry size and the limit and say how to get under it,
on a theme panel card that stays readable on any preview background.
* Render preview: keep the previous geometry budget behind a preference (#4127)
* Render preview: adapt the device budget to main's GPU upload errors (#4127)
* claim: #5717
* ui(feat): let users choose visible toolbar tabs
Keep Sheet Metal and Weldments visible by default while adding persisted tab visibility controls and restoring specialty tabs for designs that use them.
Signed-off-by: Karl Von Richter<karl@embeint.com>
* ui(fix): respect hidden toolbar tabs in every design
Remove feature-based visibility overrides for Sheet Metal and Weldments. Keep all tabs visible by default and preserve the user choice across feature creation, opening designs, selection and undo.
Signed-off-by: Karl Von Richter<karl@embeint.com>
---------
Signed-off-by: Karl Von Richter<karl@embeint.com>
The nightly failed every day from 2026-10-16 to 2026-10-28 without anyone noticing. A new
nightly-alert job (scheduled runs only, the only job with issues: write) opens or comments on
one issue, "Nightly CI is failing" (label ci), when a nightly job fails, and closes it when a
nightly passes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7724
* Update now installs the newest release, checked when pressed (#7724)
* Update: close the unmanaged-install test after the rebase (#7724)
* Update: the refresh messages in Arabic, Hebrew and Vietnamese too (#7724)
* claim: #8533
Co-authored-by: Cursor <cursoragent@cursor.com>
* Parameters: show full names in the table and formula popup.
Hover tips carry the full identifier when Name cells or completion rows clip, and the completion list widens for long suggestions (#8533).
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
A new arch job repackages each release tarball as
solidcraft-<version>-linux-<arch>.pkg.tar.zst with makepkg, through the Craft apps' shared
action (storytold/craft-common-actions/actions/arch-package, pinned by commit). The package
is io.tangencylabs.solidcraft, named after the tarball's .desktop file, provides solidcraft
and conflicts with solidcraft-bin/-git. Each package also gets a version-less copy, and
docs/downloads.md links them.
* claim: #8183
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Navigation bar: can sit at the top of the view (#8183)
Preferences > Navigation bar gets an Edge choice, Bottom (default) or Top.
On top the bar sits under the ribbon, its menus open downwards and its
tooltips show below it; the OK/Cancel pair and the axis view move down
below it when they would overlap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Navigation bar: the edge's labels in the complete catalogs (#8183)
ar, he, ja, vi and zh carry every English message; the Edge row's three
labels were missing from them, so `xtask i18n --check` and
`every_complete_catalog_carries_every_english_message` failed.
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>
body_appearance_key hashed only the name of a body's assigned material, so
after material.edit changed that material's colour (or an undo or redo
changed it back) the key stayed the same and the viewport kept the cached
old look. The key now also hashes the material's resolved look; with an
unchanged material the cache is reused as before.
* Keyboard shortcuts: a command can have several keys
Click a key on the Keyboard Shortcuts page to replace it, or the + after it
to add another; every key runs the command and menus show the first. One
key is still saved as a string, several as a list. Taking a key over from
another command leaves that command its other keys. ui.shortcut takes a
slot and reports every key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keyboard shortcuts: follow main's search layout; fix prefs test for key lists
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>
Chamfer at a corner of two lines lying on each other cut both back and
added a line of no length between their ends; lines in line with each
other have no corner either. chamfer now refuses a corner angle of 0 or
180 degrees with Fillet's message, "the lines are parallel" (found by a
local trim fuzzer).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Resize Part: say what it works on instead of a parameter error; Parts opens the library (#8735)
Resize Part (parts.resize) started with no standard part selected, as the
ribbon starts it, ran the command with no parameters and showed
"`component` must be an id or a name". It now says to select an inserted
standard part, and that Scale resizes a body. Parts (parts.library) only
listed the library, so its button did nothing: it opens the Insert Part
dialog. The German and Dutch button labels said "scale" ("Teil skalieren",
"Onderdeel schalen"), like the Scale command: they now say change size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Resize Part: the German label matches the command, and the test checks that nothing ran (#8735)
The German ribbon label reads "Bauteilgröße ändern", the name the command,
its dialog and its messages already use. The hint for a missing standard
part is now one function shared by the ribbon start and the Browser's
Change Part Size. The regression test also checks that the design and its
history stay as they were and no message carries the command's own error,
that a scripted parts.library still only lists the library, and that with a
standard part picked Change Part Size opens on it.
---------
Co-authored-by: semmelrinde <124386289+semmelrinde@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A placement row's menu starts from its component's menu, which takes
Turn Up Axis's enabled state from the component's first placement.
Move/Copy was already set again for the placement right-clicked; Turn Up
Axis and its two turns now are too, so a free second placement can turn
while the first is grounded, and a grounded one can't.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A trimmed circle ended on a line exactly where an arc started on that
line, held there by a point-on-curve tie. Trimming the line away dropped
the tie and left the two ends on one spot, untied. remove_curves now ties
each point held on a removed curve at one of its ends to that end before
the curve goes (found by a local trim fuzzer, 15 -> 12 cases in 2000
seeds).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8480
* Pinch to zoom on touchpad: native Ctrl+wheel pinch zooms the view (#8480)
The viewport's pinch path read only `egui::Event::Zoom`; `immediate_wheel` leaves
wheel events carrying the zoom modifier to egui. Native Linux and Windows
touchpads report a pinch as Ctrl+wheel in points, so the gesture reached neither
path and the view did not move. `pinch_factor` now reads those wheels too, in
every wheel unit and scaled as egui scales them, and `ui.scroll` can send the
same gesture (`ctrl: true`) so scripts and scenarios can replay a pinch.
* fix: finish Ctrl+wheel pinch handling and replay validation (#8480)
* claim: #8561
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: disable Apply until something changed (#8561)
The dialog footer gets a disabled state for Apply (Footer::nothing_to_apply,
Window::apply_enabled): drawn with the theme's dim ink, not clickable, not
reached by Tab and reported disabled to accessibility. Preferences enables
Apply only while something differs from its snapshot (what Cancel would put
back), so it is disabled when the dialog opens and again after Apply.
ui.prefs replies with apply_enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8746
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move/Copy: changing Move Type keeps the move made so far, and picks follow the moved body (#8746)
Switching the Move Type reset the values, so the move made under the
earlier type was lost. The dialog now keeps it (`chain`) and the new type
moves the bodies on from there; params() composes the two into one
solid.move (one turn about the origin and a translation, as before).
While the new type's picks are missing, the kept move alone is made.
The new type measures from where the bodies now stand (Free Move's pivot,
Rotate's ring, round-edge centres and axes on a moved body). The picker,
the edge/face highlights, the centre marker and Rotate's axis line follow
the moving body, mid-drag too; a pick on it is taken back by the current
type's own move so it doesn't move with the move it sets. While the
triad is dragged the moved bodies' highlights are hidden and rebuilt once
on release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8382
* Browser: Show/Hide context menu on origin axis rows (#8382)
Right-clicking an origin axis row (X, Y, Z) now opens its own menu with
Show/Hide (V), through ui.originPart and browser.visibility like the
origin planes' entry. An axis takes no sketch, so Create Sketch is not
offered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Display menu's "Perspective while orbiting, ortho on faces" ran past
the menu's edge: a menu row painted its label at full length. It is now
cut before the row's detail and check, and shown whole on hover, as a
long name in a list is.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): keep selected sketch points highlighted
Add persistent point selection rings while retaining constraint-state markers, with regressions for pointer selection, dialog inputs, aliases and visibility. Fixes#1069.
* Move the sketch point selection tests into the viewport overlay tests (#1069)
Run them through a frame that clears its texture uploads, as the current egui
requires, next to the other overlay tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>
Co-authored-by: Colin Faulkingham <colinfaulkingham@Colins-Mac-mini.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Render lights: uniform light list, solo and mute, and the Light editor
* Render lights: Lighting and Light editors to the approved design
* Render lights: the scale-test frame carries the product light list
* Restore the stable Render light mute target on the shared eye control
* fix: validate the viewport BRDF probe with current shader dependencies
* fix: compile legacy lighting controls only for tests
* fix: scope the legacy lighting preset import to tests
* fix: reuse the merged Add Light label and translations
* test: enable the implemented Add Light radial action
* fix: restrict the legacy CPU vector editor to tests
* test: reveal Output before checking Lights browser focus
* test: use the published Browser card rectangle for Output scrolling
* test: reveal Output children before exercising editor navigation
* claim: #8267
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Create menu: family flyouts open beside their row, on hover (#8267)
A family row (Line, Rectangle, Arc, ...) opened its variants as a dropdown
below the row, covering the rows under it. The flyout now opens to the
right of its row, level with it, and on the left only when the right has
no room. Hovering a row opens it, as the context menu's Appearance
flyout does; resting on another row closes it after the shared delay.
The context menu and the Create menu share the flyout timing
(SUBMENU_CLOSE_DELAY, flyout_hover, flyout_leaving) and placement
(flyout_at) in ui_kit::menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A vertex pick found its named edges and then picked each again by a point
on it, among the edges of every body. At a corner where another body's
edge runs past (a wall against a side wall), all three became that edge,
and the vertex its end 3 mm away. Take each named edge's ends from the
body that has it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
menu_tests::linked_component_opens_its_part_in_a_tab_and_reports_a_missing_source
gave the background open of the linked part 600 frames. Headless frames
are quick, so on a loaded machine (the whole ui-egui suite at once) the
read can take longer, and the test then found the assembly still shown.
It now waits until the part is open, up to 120 s as Harness::settle and
the edit-in-place tests wait for a load.
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #4160
* Sketch Fillet: 3D sketch lines are not offered, and say they can't be filleted (#4160)
The Fillet tool offered 3D sketch lines as picks, and sketch.fillet then
refused them with "`a` must be a line". The tool no longer offers them, and
the command refuses them with "not supported yet: filleting 3D sketch lines".
* Sketch Fillet and Chamfer: refuse all spatial sketch curves (#4160)
* Sketch corner test: give the picked curve its point (#4160)
main added a point to sketch-curve hits after this branch was written.
* Render shot editor: camera and frame controls, aspect presets, guides and the frame overlay
* Render shot: Shot editor and Scene overview to the approved design
* Distinguish Render stage variants from native window frames
* fix: restore library inspection after clearing shot selection
* test: follow the merged Output Check navigation
* test: inspect Check without requiring a preflight result
* test: cancel Shot removal through its shared dialog action
* fix: remove obsolete Inspector stage form after editor integration
* fix: preserve Appearance Advanced when clearing Wear focus
* test: expect the dedicated Check editor from Scene overview
Saving gave no sign that it worked, while an export names the file it wrote.
After Save or Save As, the status line now reads "Saved <path>".
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The dense solve updated each matrix element through bounds-checked get/add;
on slow scripted drags it was most of the time. The same operations in the
same order on row slices give bit-identical solutions, about twice as fast.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4359
* Canvas and Decal say where to click after the image is chosen (#4359)
After picking an image the placement tool started silently, so it looked
as if nothing had happened. Show the tool's placement hint as a toast while
it waits for the click (translated), and end it once the image is placed.
* Canvas: include placement hint in viewport obstacles (#4359)
* claim: #8662
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Preferences: typing in a number field edits it instead of searching (#8662)
Type-to-search takes printable keys unless the focused field notes the key
sink. number_field (Autosave every, the Largest file to open limits) never
did, so its digits went into the search.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4193
* Sketch Mirror: an origin axis can be the mirror line (#4193)
Mirror accepted only a straight curve of the sketch. It now also takes an
origin axis that lies in the sketch plane ({axis: X|Y|Z}), projected into
the sketch as a fixed linked line, so the copies stay symmetric to it and
follow the source when it moves.
* Sketch Mirror: use current sketch store (#4193)
In Preferences, letters typed in a drop-down list's own Search box (the
Font list, a 3D mouse's button assignments) went to the Preferences search
instead, which then filtered the page, usually to "No matching
preferences", and closed the list. The Preferences search takes typing only
when no text field held the keyboard on the previous frame, and the
drop-down's Search box never said it did. On the full Interface page the
number fields' focus check covered for it; once a search hid them, as when
the Font row is found by searching for "font", nothing did.
The Search box in searchable_dropdown_with now reports that it has the
keyboard, as the Preferences search box does.
Co-authored-by: Claude <noreply@anthropic.com>
* Render output: Effects and Check sections, Size presets panel and the Check tool chip
* Render output: Sampling, Effects and Check editors as child rows under Output
* fix: restrict legacy Output controls to test builds
* fix: cover status controls and reveal Effects test targets
* fix: include shared quality reviews in copied check reports
* fix: remove the obsolete Effects GPU guard after Wear integration
* Render appearance: click, drag and paint to assign, Pick Up, copy and paste, and Remove
* Exclude the workspace Shots strip from dialog width checks
* Exclude the collapsed Shots control from dialog width checks
* Retain assembly cache listings and measure actual dialog bounds
* fix: handle toast button rectangles in assignment status
* fix: restrict gallery note measurement helper to tests
* test: measure assignment help at the catalogue footer
* fix: share the command revision snapshot after restacking
* claim: #7445
* Color picker: add shared Hex input mode (#7445)
Accept strict RGB/RGBA Hex input while preserving exact opacity,
opaque/PBR restrictions, invalid drafts and existing U8/F behavior.
Keep explicit English fallback entries for the three new Hex messages
in Arabic and Hebrew, which final integration main now requires as
complete catalogs. No Arabic or Hebrew translations are claimed.
The user authorized this feature-only commit after full cargo xtask ci
stopped at the pre-existing geometry nonminimal_bool lint. The same
failure was reproduced without this patch at the published claim and
main. No unrelated lint fix or suppression is included.
* i18n: cover complete catalogs for Hex picker (#7445)
Add the three Hex message keys to Vietnamese in the feature's pending
locale file. Keep the unchanged English source values as explicit
fallbacks; no Vietnamese translations are claimed.
Audit the complete-catalog set (ar, he, ja, vi, zh) rather than only
the first catalog reported by CI. Preserve the existing completeness
test and all picker/Appearance code.
The completeness regression changes from red to green. Full local CI
and wasm checks remain blocked by failures independently reproduced
in clean upstream main; no unrelated fixes or suppressions are added.
* Appearance: use the shared opacity-aware color picker in Render (#7445)
Route the merged Render Appearance editor through the UI-kit picker,
passing exact opacity only for non-PBR looks. Keep a non-editable mixed
label and treat explicitly accepting the first color as a mixed edit.
Remove the external Hex field and duplicate parser, replace its gallery
entry with the shared mixed-aware picker, and update the control contract.
Preserve existing regression assertions and add actual Render UI tests
for alpha edits, precision, invalid drafts, PBR rejection and mixed edits.
Full local CI and wasm were run before this commit and remain blocked
by the separately reproduced unchanged upstream geometry lints and
prefs.bytes wasm error. No unrelated fixes or suppressions are included.
---------
Co-authored-by: mynoc <298906611+mynoc-96@users.noreply.github.com>
* claim: #8086
* Configuration table: a filter in the Add Column menu (#8086)
The + menu listed every parameter, feature suppression and body
material/appearance with no way to narrow it. A filter field now sits at
the top, takes focus when the menu opens, narrows the entries and the Face
Appearance submenu as typed, picks a lone match on Enter and clears when
the menu closes. A click into the field keeps the menu open (plus_header
closes on outside clicks; entries close it themselves), and the list keeps
its full height after the filter has emptied it once.
New kit element ui_kit::menu_filter (glyph, frameless editor, rule) so the
field does not read as an entry; gallery, i18n guard and docs/ui-kit.md
updated. Regression test in manage_dialogs_tests.
* claim: #6756
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Windows: never load a dxcompiler.dll found on PATH; FXC unless DXC ships beside the app (#6756)
wgpu's Dx12Compiler::Auto loads dxcompiler.dll by name, so Windows takes the
first one on PATH. SolidCraft ships none, and DXC 1.8.2505 from other programs'
folders (Obsidian, NVIDIA SDK Manager) crashed every start with
EXCEPTION_ILLEGAL_INSTRUCTION during the viewport shader compile.
solidcraft_gpu::dx12_compiler() now chooses: WGPU_DX12_COMPILER as wgpu reads
it when it names a compiler; otherwise DXC from the dxcompiler.dll beside the
program, by its full path, else FXC. The window, the headless/CLI instance and
the test helpers start with it, and dx12_compiles_with_fxc follows it.
WGPU_DX12_COMPILER=dxc keeps the old lookup.
Fixes#6756
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: points tied only through a removed curve's end stay tied
Removing a curve removed its orphaned end points with their constraints, so
two points made coincident with such an end lost their tie although they
still met: two rectangles' corners on one arc end came apart when the arc
was trimmed. They are now tied to each other before the point goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: keep the tie test with the other trim tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Naming a face swept from a sketch flattened every curve of the sketch to
a polyline and measured its distance to each, for every face. On a sketch
with thousands of curves (a sheet with many cut-outs) that grew with
curves times faces. The curves are now flattened once per feature, each
with its box, and a curve whose box is farther than the best distance so
far is not measured.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Spanish (es-ES): catch up with en.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Navigation preferences: name the Shift key through the catalog
Spanish now translates the key name, so the page painted it in English.
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: #7356
* Orbit about the cursor: on an edge or sketch line, turn about the point under the cursor (#7356)
With Orbit around: point under the cursor, a drag that starts on a body
edge turned about the edge's middle, which on a long edge is far from
the cursor, and one that starts on a sketch curve turned about the
centre of the bodies in view. Edge and sketch curve hits now carry the
point under the cursor, which the pick already measured, and
pivot_under uses it. An edge hit's `mid` is unchanged, so selections
still record the edge's middle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8139
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FAWPuFoh9qxn2uS2j53QZ
* 3MF export: a component's bodies as one object with parts (#8139)
file.export and file.save_mesh wrote every body as its own 3MF object and
build item, so a slicer loaded a multi-body part as separate objects and
could arrange them apart on the bed. With objects: "component", each placed
component that holds two or more bodies is written as one object whose
<components> are its bodies' meshes, built once, so PrusaSlicer, OrcaSlicer,
Bambu Studio and Cura load it as one object with a part per body, each of
which can take its own filament. The design's own bodies, and a component
with a single body, stay objects of their own. The default (objects: "body")
writes the same file as before.
- io: model_xml_parts and write_3mf_parts write the parts; export_with_parts
maps body names to them. model_xml_with and export_with_quality give the
same output as before.
- engine: mesh_parts reads `objects` (component is refused for formats
other than 3MF); component_parts groups the placed bodies by placement,
each part named after its occurrence (Bracket:1, Bracket:2).
- file.save_mesh passes the parts on when it also sends the file to a slicer;
file.send_to_slicer's own folder export is unchanged.
Fixes#8139.
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>
* Render wear: a Wear row and editor for the aging layers
* test: read Wear header text from accessibility label values
* test: restore body picks for Wear header recovery assertions
* Render environment editor: rotation, intensity, backdrop colour and ground through render.environment.set
* Render environment editor: rotation ring in the view with one undo step per drag
* Render environment: Source, orientation, backdrop, ground and atmosphere sections
* Keep segmented controls inside their allocated rows
* fix: compile legacy environment controls only for tests
Where several curves cut at one place, trim pinned the new end onto the
first of them. At the touch point of two circles that was a rectangle side
passing through, and once a later trim removed the side the arc ends were
loose: a trimmed dog bone kept two untied ends.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Coincident: refuse a line's two ends
Making a line's two ends coincident shrinks it to a point; a Parallel or
Perpendicular on it afterwards has no direction to hold and a later solve
could throw points far away while reporting solved, as with a line's own end
as its midpoint (#8562).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Coincident: keep the own-ends test with the other Coincident tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #3693
* Fix timeline playback to show each rebuilt feature
* Move History playback into the Inspect ribbon
* Refuse Play History up front when playback couldn't run
Starting Play and the per-frame check that stops it were looking at
different app state, so Play could be accepted during an apply, a delete
check or queued history work and then stop silently on the next frame.
Both now use one playback_blocked check.
The UI also reads the engine's PlaybackAction as the typed enum instead of
matching strings, so a bad reply is an error rather than a quiet toggle.
* Put Play History in the History controls
History now shows five controls in transport order: start, previous,
play, next, end. Play runs timeline.play, so it turns into Stop while
playback runs, same as the ribbon button and the shortcut.
It's on by default (#3688, #3740, #4216). Anyone who wants the old four
controls back can right-click them and pick Hide Play Button, or clear
history_play_button under Preferences > Interface. In a named vertical
History too narrow for five buttons beside its grip, Play is left to the
ribbon rather than overlapping the grip.
* Draw the History Play button as a plain play/pause glyph
The Play button in the History controls now uses the same drawn glyphs as
its neighbours: a filled triangle, and two bars while History plays. The
triangle is bigger than the step arrows so it doesn't read as Forward.
The ribbon command keeps its timeline_play and timeline_stop icons.
* Keep the playback changes clear of PRs queued ahead
Several PRs in the merge queue touch the same lines, so this moves ours
aside without changing behaviour:
- history_play_button and its reset-test row sit with the other History
preferences, not beside view_confirm_corner (#7248 adds status_bar there).
- History keeps main's history_transport_width() for four controls (#6430
and #7459 call it); the Play button's count uses history_controls_width.
- A new or opened document stops playback after dim_view::cancel(), clear
of the line #7819 adds after reset_chat.
- The Play/Stop label swap moves from interactive_command_info, which #7906
rewrites, into the ribbon's command_info.
* Stop playback after the command completes, clear of #6433#6433 rewrites the session.execute line in execute_command, and our playback check sat right under it, so the queue couldn't merge the two. Checking the revision after command_completed does the same job a couple of lines later.
---------
Co-authored-by: cfaulkingham <colin.faulkingham@gmail.com>
* claim: #7834
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport: vertex markers where edges are drawn (#7834)
Each body's vertices (the ends of its pickable edges, once each) show as
small screen-space squares in the edge colour in the styles that draw
edges: shaded with edges, wireframe, and shaded with hidden edges, where
hidden vertices show faintly like hidden edges. Plain shaded draws none.
A marker is a lifted line from the vertex to itself in the body's block,
so it is instanced with the edges, cached with them, and depth-tested
like a highlight band. The existing vertex hover ring stays on top.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport tests: count edge segments apart from vertex markers (#7834)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vertex markers: an option, off by default (#7834)
Adds the Vertex markers preference (Preferences > Graphics, off by default) and draws the dots only when it is on.
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>
Open refused a saved model whose text records (a feature's warning, a body or
sketch name) held more than 65,536 bytes, although Save writes them, so such a
design was evaluated again on every open.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport: cut dashed lines to the view, so a huge distance can't fill the memory
epaint makes one shape per dash and steps along a line in f32. The
viewport's dashed lines (the Extrude taper handle, Measure's legs, the
fillet radius guide, the inference and snap guides, a sketch text's
frame) were dashed over their whole projected length. With an Extrude
distance of 1e9 mm the taper line was billions of points long: the
frame never finished and filled the memory within a second.
`viewport::dashed_line` dashes a line that lies in view exactly as
before, and cuts one that reaches far off screen to the view (grown by
two dash periods) first, keeping its dashes in phase with its start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Viewport: compute a cut dashed line's ends in f64
A line a trillion points long lost its cut ends in f32: they collapsed to one point, and the part in view drew nothing. The new test caught it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Two new commands, view.next_tab and view.previous_tab, with Ctrl+Tab and
Ctrl+Shift+Tab as their default keys: they appear on the Keyboard Shortcuts
page and can be rebound or cleared like any other. The window steps along the
tab strip and goes round at either end; one design alone stays as it is, and
a "save changes?" prompt keeps its tab shown. The engine only echoes the
step (no design edit, no undo step); engine.batch refuses them like the
other steps that go to another design.
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5066
* Pattern counts: name a length-unit parameter, refuse fractional counts (#5066)
A count parameter created with a length or angle unit now gets an error
that names it and says to set its Unit to none. A count that evaluates to
a fraction is refused instead of being rounded silently.
A mesh body's row (an imported STL, 3MF, OBJ, ...) now shows a new
mesh_body icon, a triangulated cube in the mesh accent, instead of the
solid body's grey cube. Hovering a body row says Mesh body, Surface body
or Solid body. Body::is_mesh() decides; nothing new is saved.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7744
* Vendor winit 0.30.13 with Wayland touchpad pinch and rotation gestures
The 0.30 Wayland backend never binds zwp_pointer_gestures_v1, and
WindowEvent::PinchGesture / WindowEvent::RotationGesture only come from the
macOS and iOS backends, so a trackpad pinch produces no input at all on
Linux and Wayland (two-finger scroll still works, since X11/XWayland
reports it as a wheel). egui-winit already maps the events to
egui::Event::Zoom and navigation::handle already zooms the camera from
that, so the Wayland backend was the only missing piece.
Upstream the gesture support is part of winit 0.31 (rust-windowing/winit#4338),
which eframe 0.36 cannot use while it still requires winit 0.30, and the
0.30 backport rust-windowing/winit#4778 was closed unmerged. So ship the
same fix locally, upstream-style-applied and stripped of example and test
targets, with its unit tests for the gesture state machine kept under
cargo test -p winit --lib -- pointer_gesture:
- src/platform_impl/linux/wayland/seat/pointer/pointer_gesture.rs: bind
the global (versions 1..=3), one pinch gesture object per pointer,
destroyed before the pointer is released; begin/update/end become the
two events for the window the gesture began over. Two fingers only;
the protocol's scale is relative to the gesture's beginning, so the
event carries the change since the previous update; rotation is
negated to winit's counterclockwise degrees; a non-finite scale is
dropped, and so are updates of a gesture whose begin we never saw.
- seat state wiring: bind the global, track and drop the gesture object.
- wayland-protocols' unstable feature, platform notes on both events,
changelog, patch-section entry in Cargo.toml, vendor/README, and
Linux/Wayland notes in the troubleshooting guide and the UI tour.
Retire this vendoring once an eframe release consumes winit 0.31 or
later; the cleaned branch is kept as
https://github.com/purp0w/winit/tree/wayland-pinch-gestures.
* ci: retrigger after flaky occurrence-cache test
---------
Co-authored-by: Francisco das Chagas Danta Filho <dev@dantas-filho.me>
Parallel, collinear, equal or concentric of a curve with itself was added
as a constraint that always holds; perpendicular or tangent to itself was
refused as geometry that can't move. These six now refuse one curve
picked twice, as Coincident already does for a point, and leave the
sketch as it was. Constraints already in a design are untouched.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7919
* Pick by the body's own size, not the camera's distance (#7919)
The occlusion test that decides whether an edge or vertex is hidden by the
face in front allowed a slack of `scene_radius(app) * 0.02`, and
`scene_radius` ends with `.max(app.cam.half_height() * 2.0)`: in an
orthographic view that is `0.016 x camera distance`. Zooming out grew the
slack without bound until a whole body fitted inside it and every edge behind
the front face counted as visible, and edges and vertices are offered before
faces, so they won the pick. Cutting the slack from the scene also gave a
small part in a large assembly a tolerance many times its own size.
`pick_slack` now takes a size and returns the mesh's own chord tolerance with
room to spare, `DisplayQuality::Coarse.chord(size) * 4.0`: a facet sits at
most a chord tolerance inside its surface, and that tolerance is already a
fraction of the body's size, so the slack no longer moves when the camera
does. Each body is tested with its own bounding box, which the loop already
holds. `view.pick` does the same, so an agent's pick still matches a click on
the same pixel. Sketch profiles and origin planes belong to no body and keep
a scene-level value, `scene_pick_slack`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A line's own end held at its middle shrinks the line to a point. The
solve accepted that, and a later angle, tangent or parallel on the line
had no direction to hold: its points went millions of millimetres away
while the sketch still reported solved. MidPoint now refuses the pick,
saying why, and leaves the sketch as it was. Constraints already in a
design are untouched (only the command refuses).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4750
Co-Authored-By: Claude <noreply@anthropic.com>
* Extend: pick two curves to extend both to where they meet (#4750)
`sketch.extend` gains a second form: `curves: [a, b]` names two lines or
arcs, with an optional `at: [[x,y],[x,y]]` naming the end of each to grow
(default: the end nearest the meeting point). Both grow along their own
line or circle to where they cross and the two ends are joined with one
Coincident, in one undo step. `moved` is the joined end's id.
- Where two crossings are possible (line and arc, two arcs) the one with
the least total growth is used; a tie is refused.
- A curve that already reaches the point does not grow. An end held by
other geometry or a constraint stays and gets a new line or arc.
- Parallel or collinear lines, curves that never meet, a point behind the
chosen end, the same curve twice, splines, ellipses, infinite lines,
offset geometry and malformed `curves`/`at` are refused with no change.
- The single-curve form is unchanged; its code is shared through
`grow_line` / `grow_arc`.
Canvas: in the Extend tool, Shift-click two curves. Shift goes through
the tool modifier registry as the new `extend_pair` action, and Extend
turns off Shift's midpoint snap. Escape drops a pending first pick. The
sketching guide describes the gesture.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #7893
* Toasts: one stack in the view's bottom-right corner, one width, buttons as in dialogs (#7893)
* Control protocol: toast_place in the sorted ui.prefs list (#7893)
* Toasts: a sketch test clears the status notice before clicking the view's corner (#7893)
The test's line end lands in the bottom-right corner, where notices now stack
and take clicks as they did at the bottom centre; the test is about the circle.
* Toasts: the pattern scenario's circle sits clear of the view's corner (#7893)
After the refit, a circle at y 0 landed in the bottom-right corner, where the
tool's prompt now stacks and takes clicks. The circle moves 40 mm up; the
pattern it checks is the same.
* claim: #8358
* Components: a component named like the design is reachable by name (#8358)
Document::find_component resolved the design's own name to the root component (id 0) before
searching the component list, so a component that shares the design's name was unreachable by
name: appearance.assign reported `no component `CurtainRailPlug`` (the same lookup serves every
command that takes a component by name).
An exact component name (or component id) match now wins over the design-name fallback; "0" and
"root" still always mean the design.
Regression test: a_component_named_like_the_design_takes_its_appearance_by_name (fails on main
with the reported error, passes with the fix).
* claim: #8521
* feat: give History feature folders their own symbol
A folder of features in the History on the left drew the same folder icon
as the Browser's folders. It now draws feature_group, a pile of history
chips with the front one in blue, and so do Add to New Folder and Remove
from Folder in the History's menu. Other uses of folder keep it.
* claim: sheet.* typed parameter schemas
* Sheet metal feature commands: typed parameter contracts
sheet.flange, sheet.perforate, sheet.hem, sheet.fold, sheet.unfold, sheet.refold and
sheet.convert read their parameters by hand, so a misspelt key or a value of the wrong
shape was ignored or failed late, and describe_command had only prose for them.
Each now has a contract in schema/sheet.rs, checked in front of the unchanged handler,
and returns FeatureResult. The values no shared type fits (sheet-edge points, an edge
flange's edges, a fold's line and fixed point, the flange type, the positions, the
relief shape and the perforation pattern) get types that call the handlers' own
readers, split out of cmd/sheet.rs, and give their messages. The rule's values
(thickness, K-factor, bend radius, reliefs) stay out of the contracts: the handlers
refuse them, and strict refuses them as unknown keys.
A test passed a nested `base` object whose `sketch` the flange never reads; it now
names the sketch at the top level.
Part of #2829 (#6581).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sheet metal contracts: a further bend's angle stays as lenient as before
The flange handler reads a `then` bend's `angle` that is no expression
(`true`, a list) as left out and bends it 90 degrees. The rebased
contract had moved that check into the shared reader, so the handler
refused such a call even with the check off.
The reader is back to the handler's behaviour, and `ThenSteps` is the
stricter one, as the schema guide asks: warn reports such an angle and
strict refuses it. Tests cover the reader's leniency, the type's
refusal and the three check levels on one flange.
Part of #2829 (#6581).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: the history in the browser, as a History layout (compact mode)
Settings > Interface > History gains "In browser" (view.history_layout dock: browser):
neither timeline is shown, and each component's History folder lists its sketches and
features in history order with the marker. Dragging the marker rolls the history back
through timeline.roll_to; Find in Timeline reveals the feature in the browser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser history: sketches listed under their feature as in the vertical History
With Sketches: Under their feature, the history in the browser (compact mode) lists each
sketch under the first feature built from it, through the shared history_rows (#6494)
without reference rows: same rule, same fold key, so switching the History layout keeps
what is open. A drop above a row rolls back its listed sketches with it. Selecting a feature
lights the rows of its sketches, and selecting a sketch lights the features built from it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: with the history in the browser, the Sketches folder starts closed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser history: sketch rows have the eye of their Sketches folder row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* History in the browser: Arabic, Hebrew and Vietnamese for the layout's name
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: scroll a long refusal into view with its Create Issue (#8454)
A dialog's refusal comes last in its fields. When the fields fill the
dialog, the refusal sits under the fold: an Extrude Cut that the kernel
refused showed only its first line, "boolean Cut: the operation failed: no",
and no Create Issue. The dialog now scrolls a new refusal into view once:
a refusal on OK at once, a preview's once no field is being typed in, so a
field never moves away while it is typed in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: decide the reveal in one function and test it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Lists many PRs add to: no written lengths, one file per theme (#7747)
* xtask lists: the fixed-table allowlist in order, each with its own reason
* Lists: the wide-character blocks and the drawing scale presets as slices too (#7747)
* Lists: the drawing units test reads UNITS as a slice (#7747)
A copied body transfer was pasted like any feature and given the target
component as its owner. A transfer belongs to the component it moves bodies
to, so the saved design was refused on open ("body transfer owner must match
its destination"), and a pasted transfer had nothing to move anyway: the
copies go into the component they are pasted into. Paste now leaves body
transfers out and says so in its notices; a clipboard of only transfers is
refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8410
* Preferences: reverse trackpad orbit direction (#8410)
Add "Reverse trackpad orbit direction" under the pan settings in
Preferences > Navigation. orbit_reverse defaults off, so older settings
files load unchanged, and turns the Shift + two-finger orbit the other
way on both axes, as natural scrolling flips both. Pan, zoom and mouse
orbiting are untouched. Labels in en, ar, de, he, ja, ko, vi and zh as
a pending locale file; orbit_reverse in the control protocol's sorted
settings list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #8542
* CI: report missing translations for complete catalogs before review (#8542)
`cargo xtask i18n --check` already reads the pending locale files and fails
when a complete catalog lacks an English message, but no workflow ran it, so
the gap surfaced only in the full test run after approved-for-ci. It now runs
in Format and repository checks, and CONTRIBUTING names the complete catalogs.
# Cargo.lock or a Cargo.toml changed on a pull request or in a merge group.
if [ "$DEPS_RUN" = true ]; then
test "$ADVISORIES" = success
fi
# A merge group that reaches the wasm build.
if [ "$WASM_RUN" = true ]; then
test "$WASM" = success
fi
- name:Note a carried result
if:needs.changes.outputs.carried != ''
env:
@@ -1033,9 +1090,13 @@ jobs:
run:|
echo "This push only merges main into a change that already passed; the heavy checks were not repeated. Passing run: $CARRIED" >> "$GITHUB_STEP_SUMMARY"
# Every crate below the apps and the web app must build for the browser (`cargo xtask wasm`, a
# cargo check, no tests). In the merge queue for a group that reaches the wasm build (Select
# checks' `wasm`), where `ci` requires it; by hand with cross_compile.
@@ -84,6 +84,8 @@ Editable drawing code, native projects and shipped exports are collected under `
| docs/book/src/generated/tool-preview.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/tool-preview.json`; gitignored) | MIT OR Apache-2.0 |
| assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
| assets/fonts/craft-ui-sc/CraftUI-SC.otf | Adobe / Google Inc. (Noto Sans SC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/SC Regular, glyph-subset for assets/locales/zh.json and renamed Craft UI SC (OFL reserved-name); licence text in assets/fonts/craft-ui-sc/OFL.txt; web UI Simplified Chinese | SIL OFL 1.1 |
| assets/fonts/craft-ui-tc/CraftUI-TC.otf | Adobe / Google Inc. (Noto Sans TC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/TC Regular, glyph-subset for assets/locales/zh-TW.json and renamed Craft UI TC (OFL reserved-name); licence text in assets/fonts/craft-ui-tc/OFL.txt; web UI Traditional Chinese | SIL OFL 1.1 |
| tests/step-conformance/cone_point.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
| tests/step-conformance/cone_point_vertex.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
| tests/step-conformance/cylinder.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
@@ -125,6 +127,8 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/appearances/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/appearances/opaque_finishes.rs | SolidCraft contributors | original illustrative opaque finish values; no external dataset, textures or measured engineering data | MIT OR Apache-2.0 |
| assets/fonts/biz-udp-gothic/OFL.txt | The BIZ UDGothic Project Authors | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/craft-ui-sc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-SC.otf | SIL OFL 1.1 |
| assets/fonts/craft-ui-tc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-TC.otf | SIL OFL 1.1 |
| 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 |
@@ -603,6 +607,33 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
| assets/icons/ui/design/svg/model/finish_edit.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| `assets/locales/pending/sketch-tracking-guides.json` | SolidCraft contributors | Original alignment-guide preference labels and help text | MIT OR Apache-2.0 |
| assets/locales/pending/point-clouds.json | SolidCraft contributors | Original point-cloud UI strings and translations | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/feature_group.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/mesh_body.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/timeline_play.svg | SolidCraft contributors | Original history playback icon; canonical grey_cube base and original action cue | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/timeline_stop.svg | SolidCraft contributors | Original history playback icon; canonical grey_cube base and original action cue | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/status_pin.svg | SolidCraft contributors | Original analytic pin icon for persistent viewport results | MIT OR Apache-2.0 |
| assets/locales/pending/render-wear.json | SolidCraft contributors | Original UI labels and translations | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/equation_curve.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/locales/pending/sketch-recent-snap.json | SolidCraft contributors | Original preference messages for the recent centre and midpoint snap | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_horizontal.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_vertical.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_angle.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_parallel.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_perpendicular.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_tangent_external.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_tangent_at.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_tangent_two.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/construction_line_bisector.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-circle.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-circle.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-flower.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-flower.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-involute.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-involute.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-line.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-line.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-parabola.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-parabola.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-spiral.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-spiral.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-wave.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-wave.json`; gitignored) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/print_face.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/locales/fi.json | SolidCraft contributors | Original Finnish translation of SolidCraft's English catalog, AI-assisted (Claude Code) clean-room from the English and not yet reviewed line by line by a native speaker; no commercial CAD text used | MIT OR Apache-2.0 |
## Code adapted from sibling projects
@@ -662,6 +693,7 @@ copied into SolidCraft.
| Directory | Source | Licence | Changes |
|---|---|---|---|
| vendor/accesskit_macos | accesskit_macos 0.26.3, https://github.com/AccessKit/accesskit (The AccessKit contributors) | MIT OR Apache-2.0 | the subclassing adapter and its tests from 0.27.2: dropping the adapter keeps AppKit's key-value observers (vendor/README.md) |
| vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; degenerate intersection-curve rejection; face and edge history of booleans; trace macro; corner choice at vertices several intersection curves end on; reuse of an operand's meshes (vendor/README.md) |

@@ -98,7 +98,7 @@ More commands, the Windows cross-build and the browser build are in [docs/buildi
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`. 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:
`"C:\\Program Files\\SolidCraft\\solidcraft-cli.exe"`). Tools, access rules and an example session:
[docs/mcp.md](docs/mcp.md).
## Scripts
@@ -150,3 +150,4 @@ Tinkercad are trademarks of their respective owners, named only to describe comp
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";
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 [--param-check off|warn|strict]\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 (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\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.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";
"The graphics device was reset (the graphics driver stopped responding), so SolidCraft closed and started again. Unsaved changes were kept for recovery.",
);
ifsoftware{
note.push_str(" This start uses software rendering, which doesn't depend on the graphics driver.");
}
ifletSome(reason)=self::reason(reason){
note.push_str(&format!(" The graphics driver reported: {reason}"));
}
note
}
/// Start SolidCraft again with the same arguments, the loss stamped at `lost_at`, on the software
/// renderer when `software`.
pubfnrestart(software: bool,lost_at: u64){
letOk(exe)=std::env::current_exe()else{return};
/// The command that starts `exe` again with `args`, the loss stamped at `lost_at` and its
/// `reason`, on the software renderer when `software`.
"A Single hole snaps to the middle of its face and to the centres of its round edges":"A Single hole snaps to the middle of its face and to the centres of its round edges",
"Base Center":"Base Centre",
"Center X":"Centre X",
"Center Y":"Centre Y",
"Center of mass":"Centre of mass",
"Centre to centre":"Centre to centre",
"Circle center":"Circle centre",
"Colour":"Colour",
"Copy all licences":"Copy all licences",
"Customize Toolbar":"Customise Toolbar",
"Customize…":"Customise…",
"Face center":"Face centre",
"Favorites":"Favourites",
"Favourite":"Favourite",
"Mass Center":"Mass Centre",
"Modeled":"Modelled",
"Select the centre the pattern turns around first":"Select the centre the pattern turns around first",
"Snap to face and edge centres":"Snap to face and edge centres",
"cmd.sketch_slot_arc_center":"Centre Point Arc Slot",
"cmd.sketch_slot_center_point":"Centre Point Slot",
"cmd.sketch_slot_center_to_center":"Centre to Centre Slot",
"color.copy":"Click to copy colour values",
"color.edit":"Click to edit colour",
"color.gamma":"Showing colour values in 0-255 gamma space",
"color.linear":"Showing colour values in 0-1 linear space",
"color.selected":"Selected colour",
"dxf.cancelled":"DXF export cancelled",
"dxf.cancelling":"Cancelling DXF export",
"help.apache":"Apache licence 2.0",
"help.appearance.assign":"Gives the selected bodies, faces or components a colour or a material look, without changing their weight. Use it to tell parts apart or to preview a finish.",
"help.face.delete":"Removes the selected faces and lets the neighbouring faces close the gap, or leaves the gap open as a surface. Use it to take off a rounded edge, a hole or a boss that is in the way.",
"help.font_details":"The interface uses egui's built-in fonts (Ubuntu Light and Hack, under the Ubuntu Font Licence and the MIT/Bitstream Vera licence; Noto Emoji and the emoji icon font, under the OFL and MIT). Sketch text uses Liberation Sans (SIL OFL 1.1). The Crafting Apps' optional font pack, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), is not bundled in SolidCraft builds.",
"help.licences":"Licences",
"help.material.assign":"Sets what the selected bodies are made of, such as steel, aluminium or ABS. Use it so that the mass and weight match the real part.",
"help.mit":"MIT licence",
"help.solid.draft":"Tilts the selected faces by the angle you set, measured from a neutral plane. Use it on moulded or cast parts so that they slide out of the mould.",
"help.solid.revolve":"Spins a sketch profile around an axis to make a round solid. Use it for shafts, knobs, bowls and any part that is round about a centre line.",
"help.solid.split_body":"Cuts a body into separate bodies with a plane, the face of another body or a sketch line. Use it to make parts that are printed, machined or coloured on their own.",
"help.solid.split_face":"Divides the selected faces along a plane, the face of another body or sketch curves, without cutting the body. Use it to give part of a face its own draft, colour or offset.",
"help.third_party":"Third-party licences",
"home.new_mm_hint":"Millimetres · opens in Design",
"navbar.description.separator":"A thin line that groups neighbouring slots.",
"pattern.alignment_hint":"Counts include the original. Centre balances copies; Flip chooses the extra side for even totals.",
"prefs.bottom_centre":"Bottom centre",
"prefs.logo_theme":"Theme's colours",
"prefs.max_file_size":"Largest file to open",
"prefs.max_file_size_design":"Design files",
"prefs.max_file_size_flag":"This run started with --max-file-size {size}, which applies until SolidCraft closes. These settings apply when it starts without it.",
@@ -24,6 +76,11 @@
"prefs.max_file_size_step":"STEP and import files",
"prefs.max_file_size_web":"The web app opens STEP and import files up to 512 MB and designs up to 256 MB: a browser tab can't hold larger files. The desktop app can raise these limits.",
"prefs.megabytes":"MB",
"prefs.sketch_conflict_colour":"Conflicting geometry in the conflict colour",
"prefs.sketch_conflict_colour_note":"While a sketch's constraints conflict, the curves and points a failing constraint holds are drawn in the conflict colour too, not only its glyph or dimension. Off: they keep their usual colours.",
"prefs.sketch_construction_lines":"Extended construction line tools",
"prefs.sketch_recent_snap":"Auto snap to recent centres and midpoints",
"prefs.sketch_recent_snap_note":"While drawing, snap the cursor where the guides of the two most recent circle centres or straight-edge midpoints cross. Off: those guides stay visual and the cursor does not move.",
"prefs.sketch_state_icons":"Sketch icons in the colour of their state",
"prefs.sketch_state_icons_note":"In History and the browser, a sketch's icon takes the colour of its state (under constrained, fully constrained or conflicting) and its tooltip names the state. Off: every sketch has the same icon.",
"prefs.sketch_tracking_guides":"Centre and midpoint alignment guides",
@@ -35,6 +92,9 @@
"prefs.theme_follow_note":"Reloaded whenever another tool rewrites it. Choosing a theme stops following it.",
"prefs.theme_follow_off":"Off",
"prefs.theme_follow_stop":"Stop following",
"prefs.trackpad_zoom_modifier.help":"Hold only this key to zoom at the pointer with vertical scroll. Off keeps the existing scroll behaviour.",
"render.aging_airborne_help":"Airborne dust is a separate finite homogeneous scattering volume. It changes transport and raw EXR, not the surface-aging stack. Existing volume/glass/denoising restrictions remain explicit; no atmosphere is added automatically.",
@@ -62,6 +122,9 @@
"render.aging_target_help":"A named target must still match its captured index and name. Global masks are world-anchored; they do not follow moving parts. This is neither cavity detection nor true edge wear. Filter the list to reach additional bodies.",
"render.background_hdr_separate":"The current HDR resources illuminate and reflect on surfaces; they are not automatically displayed as a panorama behind the subject. Changing a gradient does not rotate or recolour the lighting.",
"render.background_lighting_section":"HDR illumination · separate from camera background",
"render.background_no_alpha_override":"A camera background is not added to transparent output silently. Raw-master regrading uses captured coverage and does not reapply denoising; the source EXR remains unchanged.",
"render.background_not_light":"Background colour is presentation, not an additional lighting source.",
"render.background_regrade":"Apply to retained raw master",
"render.background_stage_occlusion":"This shot includes physical stage geometry. A large floor or wall can fill the frame and hide a camera-only background. Clear or resize the stage deliberately, or change its physical reflectance. Regrading cannot remove captured geometry.",
"render.background_workflow":"Choose the background you intend: a camera-only colour/gradient, a physical stage that receives light, or HDR illumination. They are independent. Live Preview is a modelling view; render a new image or regrade a retained master to see camera finishing.",
"render.base_color":"Base colour · sRGB",
"render.bloom_help":"Creative output-only glow from positive linear highlights, spread with three normalised box passes. Radius is in output pixels. Added in linear display light before sRGB; not a lens, volumetric effect or convergence improvement. The raw EXR is unchanged.",
"render.cpu_area_editor_help":"Full side vectors in millimeters; U cross V points towards the emitting side. This differs from GPU half-edge vectors.",
"render.cpu_light_help":"Up to eight directional lights. An empty custom list turns them off; selecting a preset restores its lights. Colours are linear RGB and intensity is relative, not watts.",
"render.effects_gpu_only":"These effects require GPU path tracing. Changing the page never switches the renderer automatically.",
"render.effects_help":"Combine ordered colour looks and image-space lens effects. Rendering and raw-master regrading use the same saved settings. Raw EXR stays unchanged. These are original artistic controls, not calibrated lens or branded-film emulations.",
"render.effects_reset_lens":"Reset lens effects",
"render.effects_seed":"Pattern seed",
"render.env_colour":"Colour",
"render.environment.radial_only":"Centre and radius apply to Radial mode.",
"render.error_finish_gesture":"finish the modelling gesture before previewing",
"render.error_finish_operation":"finish the current modelling operation before entering Render",
"render.hdr_primaries_undeclared":"The source does not declare primaries; review the renderer colour requirements.",
"render.image_cancelled":"Render cancelled",
"render.image_cancelling":"Cancelling",
"render.image_scene_stale":"Finish the modelling gesture or refresh the scene before checking or starting an image.",
"render.inspector_colour":"Colour",
"render.layer_down":"Move down",
"render.layer_enabled":"Layer enabled",
"render.layer_remove":"Remove layer",
@@ -90,6 +171,18 @@
"render.layer_up":"Move up",
"render.lens_effects_help":"Flare uses up to four separated bright sources in the actual linear image. Ghosts, halo, streaks, chromatic offsets and radial distortion are artistic image-space approximations. Spatial effects need opaque output; outside samples extend the image edge. Grain is deterministic. No physics, EXR or geometry is altered.",
"render.library_inspect_only":"Select to inspect; nothing is applied automatically. Favourites and recent history belong to this open document session.",
"render.library_rig_help":"Fit once at an explicit centre and physical extent. Complete rigs include passive cards; calibration blockers still refuse. Conservative emitter/card clearance is checked before saving.",
"render.library_source_preview_help":"Authored base-colour source map. This is not a rendered or measured material preview.",
"render.library_unfavorite":"Remove favourite",
"render.light_behaviour":"BEHAVIOUR",
"render.light_preview_help":"Live view is unshadowed; Area lights use a centre-sample approximation. Render uses the complete light model.",
"render.material_color_replace":"Base map replaces body colour",
"render.material_map_required":"Choose a colour image or a normal image before using maps.",
"render.material_maps":"Planar colour and normal maps",
"render.material_maps_help":"PNG/JPEG up to 1024×1024; 16 MiB encoded total. U and V are orthogonal world-mm repeat vectors (maximum 128 repeats). Colour is sRGB; normal data uses OpenGL +Y. Files are captured at Check/Start.",
"render.opaque_preview_help":"Opaque PBR and simple colours; blended transparency. Refraction, HDRI and final rendering are not available yet.",
"render.page_aging":"Aging & contamination",
"render.page_background":"Backgrounds",
@@ -108,22 +206,45 @@
"render.radial_distortion":"Radial distortion",
"render.ribbon_aging":"Aging",
"render.ribbon_effects":"Effects",
"render.ribbon_studios_help":"Browse implemented studio compositions and review their replacements before applying. Concept-only scenes remain labelled.",
"render.sharpening":"Detail sharpening",
"render.soft_focus":"Soft focus",
"render.stage_panels.centre":"Centre",
"render.stage_panels.source_note":"Authored base-colour source map. Not a measured material.",
"render.studio_area_help":"Up to four one-sided rectangles. Orthogonal half-edges U and V define size; U × V faces the lit side. Coordinates are world-space millimetres.",
"render.studio_center":"Centre X, Y, Z (mm)",
"render.studio_hdr_help":"Radiance .hdr, 2:1 panorama, up to 2048 × 1024. EXR is unavailable. The colour assumption applies only when primaries are unspecified.",
"sketch_text.align_center":"Centre",
"ui.3d_mouse_kind_cap":"Return to Centre",
"ui.3d_mouse_looks_like_cap":"The reading stays put while you hold it, the way a Return to Centre device does.",
"ui.3d_mouse_orbit_tip":"Traced: the 3D mouse turns the view around the part of the model in the middle of the view when the turn begins, marked by a faint ball. Classic: around the view's centre",
"ui.3d_mouse_step_pull":"Pull the cap towards you, hold it, then let go.",
"ui.3d_mouse_switch_cap":"Calibrate as Return to Centre",
"ui.agent_chat_minimize":"Minimise to a strip",
"ui.ai_mode_edits_note":"Modelling commands run without asking; saving, opening, exporting and other file or destructive actions still ask.",
"ui.center":"Centre",
"ui.center_arc_follows_cursor":"Centre point arc follows the cursor's direction",
"ui.center_arc_follows_cursor_tip":"A centre point arc runs clockwise or counter-clockwise the way the cursor goes round its centre. Off: always counter-clockwise from the start.",
"ui.centerline":"Centreline",
"ui.colour_vision":"Colour vision",
"ui.colour_vision_note":"Changes the colours that carry a meaning: sketch states, axes, bends, analyses and icons. If you use Windows colour filters, keep this Off.",
"ui.colour_vision_off_tip":"The theme's own colours",
"cmd.sketch.construction_line.vertical":"Reta de construção vertical",
"cmd.sketch_arc_center_point":"Arco por ponto central",
"cmd.sketch_arc_tangent":"Arco tangente",
"cmd.sketch_arc_three_point":"Arco por 3 pontos",
@@ -1282,6 +1293,8 @@
"prefs.orientation":"Orientação",
"prefs.overall_speed":"Velocidade geral",
"prefs.preview_choices":"Prévia das suas escolhas",
"prefs.print_face_highlight":"Destaque da face de impressão",
"prefs.print_face_highlight_tip":"Intensidade da cor e do contorno da face de impressão de um corpo na vista. 0 % não mostra destaque.",
"prefs.quality_default":"padrão",
"prefs.quality_fastest":"mais rápido",
"prefs.quality_slowest":"mais lento",
@@ -1561,6 +1574,8 @@
"ui.3d_mouse_step_twist_counterclockwise":"Gire a tampa no sentido anti-horário, segure e solte.",
"ui.3d_mouse_switch_cap":"Calibrar como retorno ao centro",
"ui.3d_mouse_switch_trackball":"Calibrar como trackball",
"ui.3d_mouse_use_direct":"Usar driver direto",
"ui.3d_mouse_use_direct_tip":"Ler o mouse 3D diretamente (hidraw) em vez de pelo spacenavd.",
"ui.3d_mouse_view_axis":"Eixo da vista",
"ui.3d_mouse_view_off":"Desligado",
"ui.3d_mouse_view_off_tip":"O perfil do driver do mouse 3D desliga este eixo, então o movimento dele não move a vista. Escolher aqui o movimento de outro eixo troca os dois, e esse movimento deixa de mover a vista.",
"ui.constraints_conflict":"Restrições em conflito",
"ui.construction":"Construção",
"ui.construction.ribbon_label":"Linhas aux.",
"ui.context_menu":"Menu de contexto",
"ui.continuity_g0":"G0 vinco",
"ui.continuity_g1":"G1 tangente",
@@ -2161,6 +2178,7 @@
"ui.pan_zoom_orbit":"Deslocar, zoom, orbitar",
"ui.parallel":"Paralelo",
"ui.parameters":"Parâmetros",
"ui.part_size_needs_a_part":"Selecione uma peça padrão inserida (um parafuso, uma porca, uma arruela ou um pino) para alterar o tamanho. Para aumentar ou diminuir um corpo, use Escala.",
"ui.part_size_shared":"Reconstrói a peça, substituindo quaisquer recursos adicionados. Todas as ocorrências deste componente são atualizadas.",
"ui.paste":"Colar",
"ui.path":"Caminho",
@@ -2186,6 +2204,11 @@
"ui.polygon":"Polígono",
"ui.preferences":"Preferências",
"ui.press_a_key":"pressione uma tecla…",
"ui.print_face_cleared":"{body}: face de impressão removida",
"ui.print_face_input":"Face de impressão",
"ui.print_face_one_body":"Uma face de impressão pertence a um único corpo: envie esse corpo sozinho para alterá-la",
"ui.print_face_pick":"Selecione uma face plana de um corpo sólido para torná-la a face de impressão",
"ui.print_face_set":"{body}: face de impressão definida; Enviar ao fatiador apoia o corpo sobre ela",
"update.check_unavailable":"A verificação de atualizações não está disponível para esta instalação.",
"update.checking":"Verificando…",
"update.copy":"Copiar",
"update.dmg_opened":"A imagem de disco está aberta e verificada: arraste o SolidCraft para Aplicativos, substituindo a cópia antiga, e reinicie o SolidCraft.",
"provenance":"Original SolidCraft mapping of Gruvbox (MIT palette)",
"format":"solidcraft-theme/1",
"name":"Gruvbox Dark Soft",
"base":"dark",
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.