* claim: #12069
* wip: Sketch preference: a quick double Esc can be kept from finishing the sketch (#12069)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: profile key count includes the new sketch preference (#12069)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11978
* wip: Parameters filter and completion match the starts of a name's words (#11978)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: describe the Parameters filter's word-start matching in the guide (#11978)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameters autocomplete: list word-start matches after substring matches (#11978)
A name matched only by the starts of its words now comes after every name that contains the typed word, so it never pushes an earlier suggestion out of the list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12208
* wip: Center Point Arc three-click regression test (#12208)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Center Point Arc: keep the Radius box off the cursor so a click places the start (#12208)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arc click test: is_dimension is on the constraint's kind
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch value boxes: a box that would sit on the pointer moves beside it (#12208)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12543
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: Insert Mesh origin option (#12543)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Mesh: name the Move to origin checkbox's handle mesh_units:origin (#12543)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Mesh origin: measure in the active component's frame, first placement when it has no path (#12543)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12494
* German: call Look At "Senkrecht auf" instead of "Ansehen" (#12494)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* German: the navigation bar's Look At is "Senkrecht auf" too (#12494)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11057
* Sketch Dimension: the value box stays clear of the pointer when tall or in a small view (#11057)
Place the type-first value box by its drawn size and try the four corners around
the pointer; where none fits, keep the 30 px gap on one axis and slide along the
other, instead of clamping both axes back over the pointer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Mirror: a curve clicked while the mirror line is active joins the entities (#11700)
With entities preselected, the Mirror window opens on the empty Mirror line field.
A circle clicked then was refused without a word, so it was left out of the mirror.
The commands whose first input is the shared Entities list now take such a pick
there and stay on the input they were on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch modify: candidate stays the active input's pick; clicks fall back to Entities
sketch_modify_ui::candidate again answers only for the active input, so the
Mirror line still refuses curved and external references. The Entities fallback
for a click moved to pick_candidate, which Dialog::candidate_on uses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4850
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: test Symmetry of two corners of one rectangle edge about the origin (#4850)
Already fixed on main by #4626; this guards it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(es): catch-up con en.json del 2026-11-07 (507 nuevas, 23 cambiadas)
Catch-up with `en.json` at `871014f95`.
* i18n(es): el título REST es Apoyo, como la orden y la ayuda
---------
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
* docs: drop CollabCraft and craft-cloud from the browser runtime design
SolidCraft's cloud Open/Save is its own design. Removes the CollabCraft,
craft.quasarke.net, craft-cloud and craft-nextcloud references added in #1258.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: remove the browser runtime design note
docs/web-runtime.md described crates that do not exist in SolidCraft
(craft-runtime, craft-tier-store, craft-worker-host, craft-gpu) as its plan.
Removes the note and the links to it, and the craft-libs section in plugins.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* web: share previews point at app.solidcraft.io
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11306
* Preferences: save and restore panel layouts by workspace (#11306)
* Preferences: keep layout keys out of the viewport and fix validation checks
* Preferences: save other settings when panel layout conversion is refused
* Preferences: keep layout key handling scoped and deduplicate web warnings
* fix: retain imported panel column widths
* fix: use shared panel preference controls and tokens
* test: cover custom-height rows in the UI gallery
* Render hotfix: speed up mapped GPU path setup on lavapipe (#12664)
* Render: show GPU shader preparation while a job waits
* Render: share emitter lookup and show shader preparation in its own window
* Render: preserve preparation progress and dialog keys
* Render: target the displayed job from shader preparation Cancel
* CI: bound the progressive GPU UI test process
* Add Solarized Dark theme
* Add attribution
* Make Solarized Dark pass the theme checks and register it in the new theme order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Theme filter test: count the new Solarized Dark theme
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: #8969
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: Divide Curve places points at equal lengths along curves (#8969)
A new PointAlong constraint holds a point at its share of a curve's
length (measured along the exact curve), so the points follow edits.
sketch.point.divide places them on lines, arcs, circles, ellipses,
splines and conics; trim and break loosen them to point-on-curve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Divide Curve: use the solidcore_record segment and vector types after the layer split
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Divide Curve: exempt sketch.point.divide from kernel cases and show PointAlong glyphs
PointAlong constraints now get a glyph on their point (the midpoint icon),
light their point and curve, and hold an end against Extend like
PointOnCurve does.
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 #9901
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: the helical-chain and face-classification scaling tests count work, not time (#9901)
Both timed a small run and one four times larger and compared the times,
which load arriving between the two runs can fail. They now count the
work that would grow quadratically, through test-only counters: the edges
Mesh::tangent_chain looks at to extend a chain, and the triangles
SurfaceIndex::contains measures a point against. Four times the input
must cost at most eight times the count, as the time ratio allowed. The
counts are the same on every run (2248 -> 8998 edges, 80250 -> 421776
triangles). scales_linearly and its test go from both files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #12288
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render camera: input and output schemas for Set, Inspect and Frame Render Camera and Apply Render Camera (#12288)
The four commands that set, read, fit and apply the render camera get
typed contracts and replies, in a new `schema/render_camera.rs`, and
leave `schema/unconverted.txt`.
- `RenderCamera` is a saved render setup's camera: its keys, and the
bounds `RenderSetup::validate` keeps (1e9 mm, 1e6 radians, a positive
distance, 0 <= fov < pi). The reader decodes it as the handler does,
through a setup holding only that camera, so each refusal keeps the
handler's message. The bounds are in the input schema only: a reply
gives back the camera in the view, which Frame Render Subject can move
beyond them, so the output schema has the camera's shape alone.
- `camera` and `name` are optional in the types and required in the
schemas (a `require` transform), so a call without them still gets
the handler's own message; `#[schemars(required)]` doesn't do this
next to `serde(default)`.
- The handlers build their replies from the reply types through a small
`typed_reply`; the JSON is unchanged.
Tests check each camera and aspect case against both the schema and the
handler, keep today's messages in strict, and match every reply against
its schema in warn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Sketch: a tangent to an ellipse or a conic touches it and never crosses it (#11535)
A tangent between a free-form curve (ellipse, conic, spline) and another
curve, with no shared end point, brought the sample gap nearest zero to
zero. Curves that cross have a zero gap too, so a line or circle could
cut through an ellipse while the Tangent still showed as held. The solver
now brings the extremum of the gap nearest zero to zero (refined between
samples by a parabola), which only a touch satisfies. Where the gap has
no extremum, as on an open curve running away, it keeps the old gap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tangent: a monotone gap takes the end nearer zero, not a crossing's zero (#11535)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tangent: an open free-form curve touching at its end has no residual (#11535)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #11663
* Fix: a box selection with dimensions in it is fixed at once (#11663)
A box drawn over a sketch also takes its dimensions and constraint icons.
Fix started with that selection ignored all of it and fell back to the
pick tool, so nothing was fixed. It now passes over the dimensions and
constraint icons and fixes the boxed points and curves, up to the 10,000
names the command takes (the shared constraint limit of 200 no longer
applies to Fix).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix constraint test: a selection of only points gives each its own Fix (#11663)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Fix from a selection: a curve of another sketch goes to the pick tool (#11663)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Three ways a flat pattern whose edges come within 50 nm of a bend zone's edge failed:
- An outline with a vertex a hair from where a bend zone ends (a collinear vertex, as a DXF
split at its bend lines has) is split there, and the panel's loop keeps an edge that short.
SMLib's face builder refuses it ("SMLib failed (status 1001)"), so unfold fails. Such hair
edges are now merged into the line they run along.
- A cut-out whose edge stops a hair short of a bend line, or past it, or whose rounded corner
ends tangent a hair beside it, leaves a sliver face along the line, which the sew cannot tell
apart from its neighbour ("ambiguous edge contact"), so refold fails. Corners that close are
now moved onto the bend zone's edge (an arc moves with its centre); the cut moves under 0.05 µm.
- The planar booleans close a loop within 1 nm, but hand back free-form loops whose pieces are
up to tens of nm apart, which a following extrude or boolean refused. Gaps under 50 nm are now
welded before a loop is extruded or combined.
A cut-out that encloses no area is skipped.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6404
* Joint symbols: a square for rigid joints, sized to the joined parts, faint where they pile up (#6404)
In an assembly with many joints the symbols covered the model: rigid
joints drew the same motion axis and ring as moving ones, and every
symbol was a fixed 5 % of the view height, so zoomed out a cluster of
joints swamped the parts.
- A rigid joint draws a small square, with no axis or ring. This covers
every rigid joint, including those joint.rigid_group saves.
- A moving joint's axis is at most 5 % of the view height, as before,
and at most a quarter of the smaller joined part's size (its bodies'
box diagonal, found once per design revision), so zoomed out the
symbols shrink with the parts.
- Symbols are planned picked first, then moving, then rigid joints; one
whose centre falls inside a symbol planned before it is drawn faint,
unless it is picked.
The planning is joint_glyph_plan, tested without painting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
* Joint symbols: hovering one names the joint and its type (#6404)
A tooltip on a joint symbol in the 3D view shows the joint's name and
type, e.g. "Glue · Rigid", so the square, ring and axis explain
themselves, as the timeline and browser entries already do. The pointer
picks the nearest symbol within 8 points; of symbols piled on one spot,
the one drawn on top. No tooltip while a mouse button is held.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMbqRqYimvY26QzcvrojSC
* Joint symbols: the tooltip names the type only where the name doesn't (#6404)
Review on #6742: "Rigid 7 · Rigid" said the type twice. A name that
already contains the type's label is shown alone ("Rigid 7"); any other
name is followed by the type in brackets ("Glue (Rigid)"). The type is the
Type box's label (kind_label, "Pin-slot" rather than "PinSlot"), translated
like the rest of the interface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMbqRqYimvY26QzcvrojSC
* Fix compile after the layer split
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the current egui id calls
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Format the joint tooltip call
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #4109
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a preference to turn off scaling the sketch to its first dimension (#4109)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test the first-dimension preference at small, typical and large values
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Engine: the scale_sketch doc names the Sketch preference
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Translate the first-dimension preference into the complete catalogs (ja, zh)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add the Arabic, Hebrew and Vietnamese texts for the first-dimension preference
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>
Add saved optical models, reciprocal rough interface transport, hard-light
connections, and bounded photon caustics for surfaces and selected media.
Keep inactive Glass sampling behavior and document admission and model limits.
Expose native controls and provide original CPU/GPU diagnostic captures.
* Orbit drags keep turning at the edge of the view
An orbit drag that reached the edge of the screen stopped turning: the cursor
stops there, so the pointer reports no more motion. Now, when an orbit drag
reaches the edge of the view, the cursor moves to the opposite edge and the
frame that reports the jump turns the view only by the hand's motion, so the
drag can go round the model as far as the user likes. Synthetic drags
(control channel, scripts) never move the real cursor. Where the cursor can't
be moved (Wayland, the browser) nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Orbit cursor wrap: a Navigation preference keeps the old behaviour
Wrapping the cursor during an orbit changes what an orbit drag does out of the
box, so Preferences › Navigation gets "Wrap the cursor while orbiting" (on by
default, `orbit_cursor_wrap` in ui.prefs). Off, wrap_cursor never moves the
cursor and the drag stops at the edge of the screen as before. The new strings
are a pending locale file, with Arabic, Hebrew, Japanese, Vietnamese and Chinese
for the complete catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the current egui id calls
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: rootarchitect <7781866+rootarchitect@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Hole: read the faces nearest its position, not every face of the design
`hole_direction_at` classified the surface of every face of every body
(`faces_through` with an infinite reach) and then used the nearest one,
nearly always the first. In an assembly of hundreds of imported parts that
was most of a hole's time, on every preview and apply, and the viewport's
hole manipulator calls it too.
- Faces are now sorted by their mesh distance first (cheap) and each surface
is classified only when the direction rules reach it, in the same order, so
the direction is the same.
- `hole_on_surface` skips meshes whose box the drilling line does not meet
(it only casts along that line), and `faces_through` skips meshes farther
than its reach.
A cost test counts the surfaces classified for one hole among forty parts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hole: fix the new test for the references field
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Rename the faces_through line in mesh-decisions to faces_near
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: #12293
* Test: a parameter read only by a sketch point lists the sketch in used_by (#12293)
* Parameters: used_by names the feature that reads a parameter itself, as the graph does (#12293)
parameters.list built used_by from named parameter expressions only, so a
parameter read by a sketch point coordinate, a sketch pattern or a sheet
metal or plastic rule listed no user. The names a feature reads through its
own expressions now come from one helper that both parameters.graph and the
parameter table use.
---------
Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
* claim: #12191
* Test: a hole picked at both ends gets one fastener at its open end (#12191)
* Insert Fastener: picks on one hole seat one part, at the hole's open end (#12191)
Picks were merged only when they seated at the same point, so a hole picked
at both ends (a blind hole's rim and floor edge, a through hole's two rims)
got a second fastener, turned over. Picks on one axis line of one body now
seat one part. When one of them is a blind hole's floor edge, the part is
seated at the rim instead: hole_wall now says whether the picked end of the
wall opens onto air, by classifying a point on the axis just past it.
* Insert Fastener: coaxial blind holes from opposite faces stay two holes, a fastener each (#12191)
---------
Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
* Point clouds: remember the source file and reload it (#8818)
A cloud keeps the absolute path it was inserted from. pointcloud.reload
reads it again, keeping the cloud's name, placement, units and colouring,
and the filters on properties the file still has; the Edit Point Cloud card
has a Reload from file button.
The file is read beside the window through the file loader, with progress
and Cancel, as an insert is. A thinned cloud stays thinned to as many points,
which reads an unchanged file as the same points; any other is read as a new
insert would be, so a file that grew comes in whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload asks before it reaches a network location (#8818)
A design names the cloud's file, and a design may come from anyone. On
Windows, only asking about `\\host\share\x` connects to that computer and
may send it the user's sign-in. So `pointcloud.reload` now reads through
`vfs::read_design_path`, as linked designs do, and Reload from file asks
with the same prompt as Update Links.
The button's label uses its stable message id. The strings gain Korean,
which a new label needs. `pointcloud.reload` gets its line in
tests/kernel-cases/EXEMPT, which the "Every command has a case" job needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload reads under the limit the cloud was read under (#8818)
The limit was inferred from the counts, as if a cloud with fewer points than its
file had been thinned. A point that cannot be read makes the same difference:
such a PLY read as half its points on an unchanged file, since a limit one below
the file's count is a stride of two, and a file that grew was cut to the old count.
Keep the limit itself: a read is a function of the file and the limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload drops a value filter when the file stops naming the values (#8818)
A set of values filters a label, and `pointcloud.edit` refuses one on a property with no names.
A reload kept such a filter while the property stayed, so a file that dropped its names comment
left a cloud every later edit refused. One predicate now says what is refused and what a reload
drops.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload keeps its undo step and ends a play it moved (#8818)
A reload accepted with the Edit Point Cloud card open left the card's undo
boundary before it, so the card's next edit folded the reload into itself and
one Undo took back both. The boundary now moves past the reload.
A play told its filter by place alone. When a filter before it went, by a
reload, an agent's edit or an undo, the next filter took that place and the
play's range, on screen and written when the play ended. A play now names its
filter's property and ends, unwritten, when that place holds another.
`engine.batch` and the control hook compared command ids exactly, while the
engine takes any case: `POINTCLOUD.RELOAD` passed the refusal and read the file
on the window thread. Both now resolve the id as the engine does, for every
command that loads a file. The batch row of the protocol said the refusal held
only with the agent cursor shown; it always holds.
No test was run locally, at the owner's request: compile, clippy and fmt only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a file load keeps the request's own param_check (#8818)
`file_load::control` takes a command the file loader runs before
`control::handle` does, so that command skipped the request's `param_check`:
`pointcloud.reload` accepted a word that is no level, a `strict` call with an
unknown parameter read the file anyway, and the reply did not say the level.
`doc.open`, the imports and the DXF export went the same way.
The hook now takes the level itself and checks the call before the file is
touched, as a command that switches designs does: the worker's session goes on
to be the design's, which keeps its own level. Both share the same check, and
the reply says the level and any warnings.
No test was run locally, at the owner's request: compile, clippy and fmt only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Rebased onto main as one change: the joints group into one button in the timeline, and the History bar folds into component runs, feature folders and STEP inserts. Both views are Preferences > Layout settings and off by default.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: #11741
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render bindings: input and output schemas for the surface and optical bind and unbind commands (#11741)
describe_command now gives the exact parameters and reply of the four
commands that bind a library surface or an optical profile to bodies in
a saved render setup, and unbind them, and the warn and strict
parameter checks use them. The handlers already read their parameters
with strict serde types; those types move to the new
schema/render_bindings.rs, gain descriptions and JsonSchema, and become
the contracts. The doc crate's target and setup types have no
JsonSchema, so their schemas are written by hand. The replies are built
from typed values instead of json! literals, with the same JSON, and
the saved setup is serialized before the document changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render bindings schema: `set` names the setup's fields, and `origin`'s default is the body's centre
`patched_settings` reads a `set` patch onto the saved setup as a
RenderSetup, which refuses unknown fields, so the schema now names the
setup's 14 fields with additionalProperties false (still at most 13 at
once). Without `surface.origin`, render.library.surface.prepare starts
the maps at the centre of the body's bounding box, not at the origin;
the description now says so. A new test covers the field names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render bindings: keep main's CPU and GPU wording and the OpticalMaterial import after the rebase
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Agents and scripts: the view navigation and layout commands describe their exact parameters and reply (#12278)
describe_command now gives the exact parameters and reply of view.navigate,
view.layout, view.history_layout, view.graphics_preset and
view.ambient_occlusion, and the warn and strict parameter checks use them.
The handlers still read their parameters from the raw JSON, so the new types
read each key as its handler does and give its message. Keys the handlers
refuse when left out (mode, preset, enabled, camera) are required in the
schema but left to the handlers' messages. The replies are built from typed
values instead of json! literals, with the same JSON as before.
* view.navigate: the contract reads only the keys of the form the handler takes (#12278)
The handler reads a motion's keys when motion is given (each motion its own), else direction, else degrees and action, and never the keys of another form. The contract is now read by hand in the same order, so a key of another form is not checked in warn or strict either, and a call that works with the check off works in both.
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #8923
* Sketch palette: drag it by its header to move it (#8923)
The palette docks at the right edge as before; its status header now drags it like a
command dialog's title, and it keeps its place for the rest of the session. It stays on
screen and never goes so low that Finish scrolls out of view. A wide dialog docked at
the left no longer shrinks to the sliver left of a palette that was moved there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: drop the token the palette's room no longer needs (#8923)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch palette: keep the token binding the dialog still uses after the rebase
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Design tabs: double-click a tab to rename the design
A double-click on the selected design's tab opens a name field in the tab
with the name selected, so typing replaces it. Enter or a click anywhere
else keeps the new name; Escape keeps the old one. The tab grows with the
name while it is typed (up to the tab's usual 200 pt). The rename goes
through component.rename with component 0, so it is undoable, marks the
design unsaved and is what Save As offers next. A name left as it was or
left blank changes nothing, and names are cut to the 128 characters the
command takes.
On desktop the bar is the window's title bar, and a double-click on a tab
used to maximize the window. Double-clicking the bar's empty space still
does; the tabs now rename. The preference tab_double_click_renames
(Preferences > Interface, "Double-click a design tab to rename it") brings
the old behaviour back.
Changes a default: yes - a double-click on a design tab renames the
design, old behaviour (maximize the window) kept behind preference
tab_double_click_renames.
Strings in assets/locales/pending/design-tab-rename.json (en, ar, de, he,
ja, ko, vi, zh). Docs: the UI tour and the preference list in
control-protocol.md (handle tab:rename).
Tests in documents.rs: Enter renames and undo restores; a click elsewhere
keeps the name and the tab grows while typing; Escape, a blank name and
the same name change nothing; an over-long name is cut to fit; with the
preference off a double-click does not rename.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix the tab rename for the current egui and ignore the rename result
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>
Single holes on several faces send one solid.hole command per face, each
carrying the same `name = expression` text. The second command was refused
because the name was already taken. A definition typed again exactly as an
existing user parameter was defined (same expression, same unit) now names
that parameter; a different value is still refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>