When sample points say two bodies are apart or one is inside the other, the facetings are now checked for surfaces passing through each other; if they do, the boolean is computed instead of shortcut. If it still can't be intersected, the sampled answer is kept as a flagged, unverified result, and Interference lists such pairs as "could not be checked" with the reason. Fixes#442 and #443.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every Options row puts its control in one right-aligned column, Look At is a single label with an icon button, Options folds with a disclosure triangle, and the toggles use plain nouns (with dedicated ids for Profile and Points so they don't collide with other labels).
The Linetype buttons now read "Draw as {linetype}" with nothing selected and "Convert the selection to {linetype}" with a selection, in all four locales.
A profile traced over a face sketch's projected boundary left the extrude's caps and sides without an exact planar source, so a sketch or offset plane on the new top face was refused. The drawn curve is now the edge's source where it covers a projection. Clearer fixed-plane message; regression tests in the engine, UI harness and design corpus, plus a corpus-wide flat-face attachment check (known gaps under #491-#495).
Fixes#489
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hold the left button still (long-press on touch) or right-click › Select Other… to list everything under the cursor: sketch points and curves ("Line 3", "Point (arc 2 centre)"), vertices, edges, faces, bodies, profiles, planes and axes, filtered like a click and by the open dialog's active input. Hovering a row lights it up; clicking selects it (Shift/Ctrl add) or feeds the dialog. A repeat click on the same spot with nothing changed selects the next thing there; a double-click keeps its own actions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: name an untitled design after its file on save and open
Design tabs all read "Untitled" because a design's name only ever came
from file.new. Saving a design still under the default name now names it
after the file (bracket.solidcraft -> "bracket"), and opening a file whose
stored name is the default shows its file name. Names chosen by the user
are kept. The rename is part of the save, so it is not an undo step and
does not leave the design marked as changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWLPLy7ug7URSK5WybjRNa
* test: expect the file's name after saving an untitled design
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWLPLy7ug7URSK5WybjRNa
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The live preview also evaluates each command as a new body, to show the tool of a join or cut
(as_new_body sets every `operation` to "new"). A combine has no new-body form: its operation is
between bodies that already exist, and solid.combine rejects "new" ("operation must be join, cut
or intersect"). That error became the preview's error even when the combine itself worked, and
since OK is greyed while the preview has an error, a combine could not be applied from its
dialog at all. as_new_body now leaves solid.combine alone, so its preview shows the result.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): draw sketch constraint glyphs in red
Traditional CAD convention marks constraint glyphs in red. glyph_edge
and glyph_text in both themes now use red values. glyph_bg is
unchanged. The same glyph box switches to the error colour when a
constraint fails. The new reds are chosen to stay distinct from that
error colour. They are also distinct from the blue used for selection
and hover. See the pull request body for the before and after RGB
values and the contrast reasoning.
* fix(ui): match constraint toolbar icons to the red glyph colour
The CONSTRAINTS panel buttons and their drop-down rows drew their accent
strokes in the normal sketch tool green. They now use glyph_text, the same
red the matching constraint glyph box uses in the sketch view. A new
panel_accent helper in toolbar.rs picks the colour: the constraint red for
the CONSTRAINTS panel, the existing sketch green for every other sketch
tool, and the plain accent everywhere else. No other toolbar styling
changed.
* Add a one-surface fill of closed 3D loops to geom
A closed loop of curves that does not lie in a plane has no exact surface;
the usual answer is one smooth face fitted over it. fill_loop does that:
- the loop's best-fit plane (Newell normal), with a frame chosen from the
loop itself so the result does not depend on where the loop starts, its
direction, or where the part sits
- the loop seen along that normal must be simple and must not fold or run
along the normal; otherwise it is refused ("not supported yet"), never
filled with wrong geometry
- one non-rational bicubic B-spline height field over the loop's box,
fitted by least squares to the loop's samples (The NURBS Book 9.4.1,
9.4.3, eq. 9.62/9.65) with a thin-plate smoothness term
lambda*A*int(h_uu^2 + 2 h_uv^2 + h_vv^2), A one grid cell's area, so the
fit is the same at any scale; only planes are reproduced exactly
- span counts per direction (long narrow loops fit), refined until the
loop is within tolerance, else refused with the deviation reached
bspline: bisected span search, basis functions on a stack array, one
clamping helper shared by curves and surfaces, one binomial helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
* Patch fills non-planar loops of edges with one smooth face (#250)
surface.patch with edges refused any loop that is not planar ("not
supported yet: patching a loop that is not planar"). It now fills it:
- planar loops still get one exact planar face, but planarity is certified
conservatively: lines by their ends, B-spline/NURBS edges by their control
points, intersection curves by their samples lying on a support plane
within tolerance; a loop that can't be certified is filled instead
- other loops get one face from geom's fill_loop, bounded by the body's own
chained edges, whole (no split): its history is one generated face whose
edges are the body's, and it stitches back into a closed solid
- intersection-curve edges are sampled by a seeded, guarded double
projection shared by the planarity test and the fill (plane_fit)
- the face tolerance is 10x the planarity tolerance (documented: later
operations take the face to contain its edges within it)
- edge picking runs inside the guard; the geom->truck conversion is shared
with the T-spline sheets (spline_conv); the Newell plane fit is shared with
STEP import
Tests: 3- to 8-sided, concave (dart, arrow, plus), flat-corner, two-edge and
imported rims fill as one face and stitch closed (volumes against an
independent integral); finite mesh normals, same result for any pick order
or placement; non-height-field loops and too-fine detail are refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
* Test the Patch dialog filling a non-planar loop of picked edges
The Patch dialog's only test used a sketch profile. Picking the four
curved rim edges of a box cut by a sideways cylinder now makes one
surface.patch command whose result is a single smooth face over the rim.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A sketch on a flat face of a sheet metal body was refused ("this face has no exact planar
source"), so the only way was a plane frozen in space. When the sheet changes (a flange's
angle, the thickness), the frozen sketch stays behind: a cut from it fails on the slanted
flange or leaves a blind pocket in the thicker sheet.
`sketch.create` with a face of a sheet metal body now attaches the sketch to the panel the
face belongs to. It keeps the face's name, `sheet:<body>:<panel>:<top|bottom>` (the panel is
the base or a flange's feature tag), and takes the panel's exact plane from its flat region
and fold each time the model is evaluated, so the sketch follows the sheet through parameter
changes, unfold and refold. Its coordinates are the flat pattern's (the bottom face's y
mirrored). A face that is gone is reported as a lost attachment, never replaced by the
nearest face. The attachment follows a renamed or copied body, shows as `sheet_face` in the
command result and as its own status in Redefine Sketch Plane. `fixed: true` still freezes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A left press with Ctrl (or Cmd) held, no Shift or Alt, on empty space orbits
for that press, unless the preset binds Ctrl+left itself. It only orbits
once the pointer moves: a Ctrl+click still adds to the selection, and
Ctrl+drag on sketch geometry still drags it without snapping. Works with a
tool active, without adding geometry. Ctrl+box selection from empty space
becomes the orbit; Shift+box selection still adds.
Preferences › Navigation and the UI tour mention it. Test drives every
preset through real pointer events.
Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
With "Orbit around: Point under the cursor", a drag that starts over
empty space used to pivot on the point under the cursor at the target's
depth, so an orbit begun near the edge of the view swung the model
around an off-screen point. It now pivots on the centre of the visible
bodies and sketches (the same bounds Fit frames), or on the view centre
when nothing is visible. A drag that starts on the model is unchanged.
Claude-Session: https://claude.ai/code/session_01HWLPLy7ug7URSK5WybjRNa
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
One edge flange feature gave all its flanges one height and one angle. A
part whose flanges lean differently took one feature per angle, and
flanges of separate features don't close toward each other, so its
corners stayed open.
- sheet.flange (edge): an entry of `edges` is a point [x, y, z] as before,
or {point, angle?, height?} whose angle and height (expressions, checked
like the feature's) replace the feature's for that edge. Bad input is
refused with the edge named. Before, the object form was read for its
point and the rest was ignored without a word.
- The feature stores its edges as FlangeEdge. An edge without values of
its own is written as the plain point it always was, so designs without
them are read and written as before; an unknown key in an edge is
refused, not dropped.
- The edges' values are feature inputs of their own (Edge angle, Edge
height, after the feature's), so parameters, renames and
parameters.change reach them. timeline.edit names the edges again when
it changes them, so each value stays with its edge.
- SheetBody::add_flanges_each trims every edge by its own setback and
adds the flanges. The corners between them close as between equal
flanges: the closing already works from the two flanges it joins (each
one's fold and leg), so the flat pattern follows by hand from the
angles, the heights and the rule (the tests derive it).
- The Flange dialog shows a row per picked edge with an angle and a
height box (empty: the dialog's value) once more than one edge is
picked, and brings them back on Edit Feature.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A panel label too wide for its panel (beside its caret) wraps between words
onto a second line; a single word wider than the panel stays whole. Each line
is centred, and the label's hover area grows to hold both lines. A panel's
buttons are centred in the panel, so a one-button panel such as Finish Sketch
lines its icon up with its label.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* fix: fillet fit review follow-ups
From the review of #388 (merged before these landed):
- The Fillet dialog's Applied Radius showed nothing when editing a fillet
that was already reduced: the preview found the radius by diffing the
states before and after. The engine's preview now keeps the command
replies, and the dialog reads the radius of the feature they name.
- Pattern copies of a reduced fillet try the radius the original was
reduced to first, instead of each searching again (up to 16 fillets).
- fit: reduce with a chord or variable radius is refused instead of being
silently ignored.
- One helper appends a note to a feature's warning; too_large is imported
like unsupported in the loop and curve blends.
Refs #293
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44
* Use the solidcraft_doc crate name after the rename
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWLPLy7ug7URSK5WybjRNa
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
- frame::heading draws a panel's title row (heading_bg) or a section heading
(subheading_bg) on a band darker than the dialog, in both themes. Every
docked command dialog, the Sketch Palette and Redefine Sketch Plane use it.
- Sketch Palette: "Options" and a new "Sketch Status" heading are section
bands; the status line sits under its own heading.
- Sketch Palette: Finish Sketch no longer centres in the leftover height, so
the palette ends one line below it, one line after the status.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
With sketch curves selected, the palette's Normal / Construction / Centerline
buttons now show the line type of the selection (each type of a mixed
selection); with nothing selected they show the drawing mode, as before.
Previously no button was highlighted while curves were selected.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every panic (in a command, in the kernel, anywhere else) and every unclean
exit (abort, stack overflow, kill) leaves a report in a local crash folder:
message, file:line, a trimmed backtrace, version, commit, system and renderer,
the command and recent commands, and when possible the design and replay.json
that reproduce it (solvecraft-cli crashes replay <id>).
- Cost: nothing until the app installs the hook (tests, CLI and web never
do); then one small journal line per top-level command of the design on
screen, taken back with a truncate when the command changed nothing.
- Unclean exits: each run holds a lock on its journal; a journal nobody holds
at the next start belongs to an app that died. Autosaves restart the
journal from the autosaved design.
- Bounded: a crash's signature (place + message, numbers ignored) is counted,
not rewritten; at most 5 new reports per run, 20 reports, 50 MB, 90 days;
recovered kernel panics are pruned first.
- Agents: diagnostics.crash_reports / crash_report / crash_reports.clear
commands, the MCP crash_reports tool, solvecraft-cli crashes, solvecraft
--crash-log; diagnostics.crash_test (with SOLVECRAFT_CRASH_TEST) checks it.
- People: the Crash Reports window opens after a crash (Help > Crash
Reports…) with a reviewed issue draft per report; nothing is sent.
- Release builds keep line tables for file:line in backtraces.
Tests: child processes that really panic in a command, in the kernel, on a
thread, and abort; dedup counts, limits, hostile folders, journal parsing,
the UI window and draft, the MCP tool. docs/crash-reports.md.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* feat(kernel): typed TooLarge refusal and fillet_max search
Blend refusals caused only by the size (larger than the neighbouring faces,
taller than the walls, longer than the next edge, larger than a corner
radius) now return KernelError::TooLarge, displayed as before. fillet_max
finds the largest radius below a request at which the fillet of the edges
works, to three significant digits, by building candidates; it is an explicit
call (up to 16 fillets), not run on every refusal.
A corner edge that doesn't run square to the edge is now reported as such
instead of as a blend larger than the neighbouring faces.
Refs #293
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44
* feat: fillet fit option reduces a radius too large for its edges
solid.fillet takes fit: exact (the default, as before: a radius too large
for the geometry around the edges is refused) or reduce: the radius is
reduced to the largest that works for all the picked edges (kernel
fillet_max, three significant digits, every candidate built). The feature
stores the requested radius, so it refits on every evaluation; when it
reduces, the feature's warning says so and the command returns
radius_applied. Pattern copies of the fillet use the same rule. The file
format writes fit only when it is reduce.
The Fillet dialog gets a Radius Fit row (Exact / Reduce to Fit, constant
radius) and, when the preview reduced the radius, an Applied Radius line;
editing the feature keeps the choice.
One radius for all edges for now; per-edge radii and taper follow.
Refs #293
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>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Two-finger scroll pans the model instead of zooming; pinch-to-zoom
zooms toward the cursor and honors reverse zoom. Trackpad scrolls
arrive bracketed (Start..End, momentum included) while mouse wheels
send bare moves, so phases tell them apart: bracketed gestures pan,
bare vertical input zooms and bare horizontal input pans (thumb wheel).
Mouse line/page input keeps zooming, and ui.scroll now sends line
input so scripts and agents are unaffected. New Navigation preference
'Two-finger scroll pans' (default on) restores the old behaviour
when off.
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>