Sketch Dimension refused a dimension the sketch couldn't take, with
"that dimension would over-constrain the sketch; add it as a driven
(reference) dimension with driven: true", which the tool gave no way to
do. It now places it as a driven dimension instead: while placing, the
preview shows it in parentheses in the driven colour, and the click adds
it at once (it has no parameter and no value to edit), with a status line
saying why. Other refusals, such as an unsupported pick, are reported as
before, since the driven dimension is refused too.
Refs #320
Co-authored-by: Vitali M. <6042958+vitalibr@users.noreply.github.com>
A panel drop-down taller than the window ran past its bottom edge, so the
last rows (Sketch → Create → Intersect and below) could not be reached. The
rows now scroll inside the window, and a row reached with the arrow keys
scrolls into view. Row ids no longer depend on the enclosing Ui, so the
keyboard focus still finds them inside the scroll area.
Between two points, or on a line, that don't line up with an axis, the
Sketch Dimension tool only offered the distance between them: an auto
dimension ignored where its text went. Now the placement picks what it
measures: across from the two (between the perpendiculars through them)
the distance between them, as before; past that, above or below them the
horizontal distance, beside them the vertical one. With no text, an
explicit type, or two ends already on an axis, nothing changes.
The tool previews the dimension from the cursor, so the choice follows
the pointer while placing it.
Fixes#257
Co-authored-by: Vitali M. <6042958+vitalibr@users.noreply.github.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
fill_triangles bounded the slab triangulator by edges x slabs, as if every
edge crossed every slab. A ring sampled finely at a close zoom has hundreds
of edges and slabs but only four edges in each slab, so the estimate ran a
hundred times over the hover highlight's budget and the fill was dropped,
leaving only the outline. Count the slabs each edge actually spans instead.
Region2::triangulate tested every edge against every slab, so its work was
the overestimate. It now sweeps upward with the edges crossing the current
slab, giving the same triangles in time proportional to them.
Fixes#164
Co-authored-by: Vitali M. <6042958+vitalibr@users.noreply.github.com>
Choosing 8x anti-aliasing in Preferences › Graphics on a device that
only allows 1x and 4x for the window's format made every later start
panic in wgpu's pipeline validation (`Sample count 8 is not supported
by format Bgra8Unorm on this device`), so the app could not be opened
again without editing graphics.json by hand.
- egui-wgpu (vendored): request TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
when the adapter has it (WebGPU only guarantees 1x and 4x), and clamp
the sample count to the largest one the device supports for both the
colour target and the depth/stencil format. The supported counts are
exposed as `RenderState::msaa_supported`.
- At start, a saved count the device can't do goes back to the default
(4x, or the largest supported count below it), is written back, and a
"Settings were changed" notice says what changed.
- The Anti-aliasing list greys out unsupported counts, `ui.prefs`
rejects them, and an invalid saved `msaa` loads as the default.
- New messages translated in en, de, ja and ko.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(sketch): constructive solving ahead of the numeric solver
Part 1 of splitting #251: the sketch crate only. A graph-constructive front
end (Owen 1991; Fudos & Hoffmann 1997) places what it can by ruler-and-compass
loci and cluster decomposition, Levenberg-Marquardt finishes and verifies the
rest, and the rank analysis stays authoritative for degrees of freedom. Solves
can follow a reference (the previous solution), so edits and drags keep their
branch instead of flipping.
New API: solve_with (hold, reference, analysis, budget) and
solve_after_value_edit; solve and solve_holding keep their signatures as
wrappers, so no caller changes here.
When the seed moves an arc's centre, the arc's unconstructed ends start carried
along (the arc translates) instead of left behind for LM to turn; found by the
reference test 20-slot-constrained.
Refs #203
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44
* feat(doc): keep a sketch's solved state across value edits
Part 2 of splitting #251 (builds on part 1, the sketch crate):
- Sketch edits solve for the document's values continuing from the stored
solved state (Document::solve_sketch), so a dimension edit keeps the
sketch's branch instead of re-solving from scratch.
- A value edit (parameters, a configuration, the units) that would make a
sketch that solved before fail or turn over is refused with the
conflicting constraints named; otherwise the solved sketches are written
back into the document (Model::adopt_solved_sketches), which changes what
saved designs store.
- Later features are keyed by a sketch's solved geometry (to a 1e-6 mm
quantum), so setting a value back reuses their earlier results.
- Solved sketches in a model state are shared (Arc) between states.
Refs #203
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44
* feat(sketch): live drag that keeps its solution, ended by sketch.drag_end
Part 3 of splitting #251 (builds on part 2):
- sketch.drag follows a long move in short held steps, so the geometry keeps
its branch, and reports how far it got (reached, at).
- live: true runs one frame of an interactive drag: the sketch is solved
once and shown, later features keep their last result (marked stale)
until sketch.drag_end or any command that reads the model evaluates it
(CommandSpec::live, reads_model, doc_only).
- sketch.drag_end adds the constraint the drag let go on and evaluates the
model once.
- Undo and redo evaluate a new document once (execute does) instead of
twice.
- The viewport drives drags through live frames and drag_end; dimension
text drags start only off sketch geometry.
Refs #203
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Loop blends accept straight wall seams that are not lines
Filleting or chamfering the top outline of two overlapping cylinders whose
concave edges were rounded failed with "the wall seams must be straight":
the round's contact lines with the cylinders are straight NURBS curves, and
the loop blend only accepted seams stored as lines. A seam now counts as
straight when every sample lies on its chord; it is trimmed as a line as
before.
* Fillet and chamfer arrows point into the body
The radius arrow pointed out of the body along the edge's bisector, so
dragging it away from the edge grew the round. It now points into the
material, like the shell's thickness arrow: dragging toward the body grows
the radius or distance, dragging out of it shrinks it.
* Toolbar: no cap on pinned buttons (Fixes#323)
Every pinned command gets a toolbar button; `promoted()` is only the
default layout of a fresh or reset toolbar. A toolbar too narrow for every
button now collapses panels into their drop-down (one button that opens it,
listing every command, pinned ones included) from the right, then moves the
last panels into the » menu, instead of pushing a wide panel and everything
after it into ». Nothing is dropped.
Scenario s48 pins ten commands into SOLID > CREATE: all ten show at a wide
window, a narrow one collapses CREATE into its drop-down (all ten listed),
narrower still puts it in », and Reset Toolbar restores the defaults.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
* Narrow-toolbar test: panels collapse into their drop-down before »
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
* kernel: drop a needless borrow that fails clippy -D warnings on the current stable
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Home page hides the open design, yet Save, Undo, Redo, File › Insert,
Save As and Export, and the design's keyboard shortcuts (Ctrl+S/Z/Y,
Delete, V, F2, F6, Esc, S and tool keys such as E) still acted on it:
Delete removed the selected body unseen, Save wrote a design the user
could not see, and tool keys left Home without being asked to.
On Home the top bar's Save, Undo and Redo are dimmed with a translated
"open a design first" tooltip, the File menu's design items are greyed
out, and only New, Open and Preferences keep their shortcuts. Everything
works as before once Home is closed.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A line whose end lies behind the eye has no screen point: vs_line divided
by a non-positive w, which flipped the segment or dropped it whole. At a
low perspective angle the grid lines running along the view direction
reach behind the camera and vanished. Cut each segment at clip z = 0
before the screen-space expansion.
Fixes#182
Co-authored-by: Vitali M. <6042958+vitalibr@users.noreply.github.com>
The Home page returned from the frame before the Preferences window was
drawn, so the gear, Ctrl+, and Home's Preferences / Keyboard Shortcuts
links only set the flag and the dialog appeared after leaving Home.
Draw the Preferences window on Home too.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Snap 3D bridge drawing to points on visible sketch planes
* Keep cross-sketch point picks out of sketch tools
`pick()` returned every visible sketch's points while 3D drawing was on,
so `sketch_point_at` and the sketch tools could take another sketch's
point as an active-sketch local point plus id (flattening it or making a
bad coincident reference). `pick()` is local again; the bridge snap uses
`pick_3d` explicitly and hover mirrors it for feedback. `sketch_point_at`
also ignores foreign points defensively.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(sketch3d): check local point scope with a sketch tool active
Main's Project preselection fix now offers other sketches' points to idle
selection, so assert the local scope where it still applies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* style: rustfmt
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>
- Remove xtask/data/fusion-catalog.tsv and xtask/data/fusion-ids.tsv.
- Remove the legacy command-id table (crates/engine/src/legacy_ids.rs) and its use in
find_command and in the shortcut, recents and pinned-command loaders. Only SolidCraft's
dotted ids name commands; any other id is an unknown command.
- Boundary Fill's id becomes solid.boundary_fill.
- Replace the parity report with SolidCraft's own feature checklist: `cargo xtask features`
writes docs/features.md from the command registry.
- docs/oracle.md, docs/step-import.md (corpus section) and docs/iges-import.md: reference
results are being rebuilt on analytic and open-source (FreeCAD) values; see #292.
`cargo xtask oracle` and `cargo xtask step-corpus [--iges]` write these placeholders.
- AGENTS.md, CLAUDE.md, CONTRIBUTING.md, README.md, ROADMAP.md and the book describe
SolidCraft as independent, with reference tests on analytic values and open-source tools,
and agents do not drive or extract data from commercial CAD products.
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Create ko.json
* Update ko.json
* Update i18n.rs
* Fonts: add a Hangul fallback so Korean doesn't render as boxes
Only the first CJK font found was loaded, and on Windows and macOS that is
a Japanese font without Hangul (Meiryo, Hiragino). Load the first readable
font of each group: the existing CJK list, then Malgun Gothic (Windows),
Apple SD Gothic Neo (macOS) or Nanum Gothic (Linux). Also rustfmt the
Korean catalog test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Navigation presets are now a table of bindings (button, required modifier
keys, camera mode) instead of string matches. The Alias preset binds
Shift+Alt+left to orbit, Shift+Alt+middle to pan and Shift+Alt+right to
zoom. A left press with a binding's keys is latched until release, so that
drag neither box-selects nor drags sketch geometry; plain left drags behave
as before. Shift only requests snapping when the binding without Shift
still orbits.
The scenario harness sends Alt with ui.drag/ui.click, and camera checks can
assert panned/zoomed since the last camera step. The user guide notes Alt+drag
grabs by KDE and GNOME and how to free them.
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Toolbar drop-downs highlight the row under the pointer and the keyboard focus (#288)
A panel drop-down's command rows are full-width rows that take the theme's hover background
(icon, label and shortcut on it) when hovered or focused. Up/Down move the focus through the
enabled rows, Enter starts the command, Escape closes the drop-down. Rows publish
`dropdown:<id>` and the highlighted one `dropdown_hot:<id>`; scenario s46 checks pointer and
keyboard highlight.
Fixes#288
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
* Toolbar: drag commands from drop-downs onto the toolbar; pin icon; Customize Toolbar… (#289)
- A panel drop-down row drags onto the toolbar (a button or a panel's free space, with the
drop line) and is pinned there; the dragged command's icon follows the pointer.
- A toolbar button dragged off the toolbar, or back into the open drop-down, leaves it (its
icon shows a cross while releasing would remove it).
- A hovered drop-down row shows a pin at its end: click to pin or unpin.
- Right-click on the toolbar's empty space: Customize Toolbar… (a window listing the tab's
commands with check boxes, and Reset Toolbar) and Reset Toolbar; command menus have
Customize Toolbar… too.
- Still per workspace and tab, kept with the preferences. Toolbar buttons also publish
`button:<id>`; scenario s47 drags a row to the toolbar, unpins by dragging off and into the
drop-down, uses the pin icon and Customize Toolbar…, and checks it survives a restart.
Fixes#289
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
* Toolbar drop-down pin: a push pin drawn in code (upright and filled when pinned); control protocol lists the toolbar handles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Expose interactive editing and placement of sketch text
* Respect document units when editing sketch text
* Route Edit Text through the shared feature dialog
The sketch text editor is now a Kind::Text dialog opened through
Dialog::for_command (frame.rs docking, standard OK/Cancel, Enter/Esc).
Its fields are ordinary dialog fields: they take expressions, parameters
and units, and bare lengths are in the design's units. Untouched fields
keep their exact saved values. The dialog stays open across unrelated
design changes and closes only when its text is gone, the sketch ends,
or another command starts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Honor design units in sketch dimensions and measurements
* fix: apply document units to specialized length controls
* Accept explicit units in length fields; document design-unit dimension values
Length fields in a design-unit control now evaluate what is typed as a
length expression: a bare number stays in the design's units, while an
explicit unit ("10 mm", "0.5 in", "1 in + 5 mm") is converted.
Document that a bare numeric sketch.dimension value from scripts, the
control channel or MCP is read in the design's units.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- The dialog draws every page into a fixed, clipped 744x400 area, so no page
(or translation) changes the window width; descriptions wrap and tall pages
scroll inside it.
- OK / Cancel / Apply sit flush right in the bottom bar (right-to-left layout
within the fixed width); Restore Defaults stays left.
- The "settings" icon is a gear: a solid ring with six square-ish teeth and a
see-through centre hole, instead of the sun-like rays.
- Scenario s46 visits every page and checks the window keeps its width and
that Apply ends at the bottom bar's right edge (new `same_width` and
`right_edge` scenario checks).
Fixes#290
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Web: a failed CJK font fetch shows English for this session only; the
saved language preference is kept (CjkFontLoader, persisted on save).
- Web fetches the CJK font only when a CJK language (ja) is selected.
- fonts.rs: directory scans match CJK-specific names only (no "noto" or
"gothic"); CJK stays the last fallback after the Latin UI font.
- Revert unrelated clippy rewrites in component_clone.rs, coplanar.rs,
canvas.rs and issue_report.rs to match main.
- Tests: default English, fallback order, CJK-only matching, fetch only
for CJK, failed fetch keeps the saved language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
With Object Type = Bodies, the Mirror, Rectangular Pattern and Circular
Pattern dialogs sent the feature that created each picked body
({"features": ["Extrude1"]}), so the copies replayed only that feature and
lost everything done to the body afterwards (a fillet, a chamfer, a cut).
They now send the bodies themselves ({"bodies": [...]}), which the commands
already support and the Path Pattern dialog already did. Picking Features
is unchanged.
Editing a body Mirror or Pattern from the timeline now reopens the dialog
with its bodies selected.
Copies made this way take the body-copy names ("Body1 (2)"), as the
commands already give; pattern_direction_edge now looks up "Body1 (3)".
Tests: UI scenarios mirror_body_keeps_fillets and pattern_body_keeps_fillets
(filleted cube, copy and edit through the dialogs); both fail without the
fix with the copies' sharp 8000 mm3 volume.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
construct.plane.offset now takes a planar face as its base
({"face": [x,y,z], body?, index?, path?}), captured with the same exact
face attachment a sketch on that face uses. The Offset Plane dialog accepts
planar faces as well as origin and construction planes.
A construction plane on a face is resolved through its face anchor when the
timeline is evaluated, so it follows the face when upstream features change
(and after reopening), instead of keeping the plane as it was when picked.
Tests: engine (top and side faces, follows a box resize, survives reopen,
hostile picks are refused without adding a feature) and a UI scenario that
picks a face in the dialog and builds a pad on the new plane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>