Constraint delete: a Properties-panel row delete button, viewport
click-to-select on a constraint glyph pill with Delete-key support, and a
right-click "Delete Constraint" menu item — all routed through one
undoable Scene::selected_constraint-aware path.
Fixes two named-parameter bugs found while testing that feature:
- DIMCONSTRAINT: a parameter named like a mode-switch keyword ("r", "d",
"a", "x", "y") could never be referenced, since the keyword check ran
before the parameter-name check.
- PARAMETERS modal Apply rebuilt Scene::named_parameters but never
refreshed the Properties panel, leaving renamed/deleted rows stale
until an unrelated refresh (e.g. a selection change) happened to occur.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ports FreeCAD's planegcs 2D geometric constraint solver to Rust as a new
crates/ocs_gcs workspace member, and wires it into the document/scene model
as a persistent SketchConstraintSet: Coincident, Horizontal/Vertical,
Parallel/Perpendicular, Equal, Tangent, Distance, Angle and Radius
constraints that re-solve live (including mid-drag, not just on release),
render glyphs and a DOF badge, flag conflicts with a one-click resolver
pill, and round-trip through save/load via an XRecord blob.
Also includes docs/command_reference.md (a statically generated reference
of every registered command and its prompt sequence) and
docs/named_parameters_design.md + docs/backlog.md, which hand off the next
queued feature (a named-parameter/expression table) and consolidate every
deliberate scope-down and open gap found along the way, so a fresh session
has a complete starting point.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add canvas_is_light helper based on WCAG luminance (> 0.5)
- Ensure crosshair_background always returns sheet white in paper space
- Update resolve_selection_base_color to resolve user ACI -> light-canvas palette -> theme palette
- Add light_canvas_color with curated deep-green/deep-blue pairs for light surfaces
- Extract selection_fill_alpha with 1.35x contrast compensation on light surfaces
- Thread canvas_bg through draw_marquee and poly selection
- Note deliberate limitation for desk-spanning marquee edge case
- Add test coverage for paper space, ClassicDark, custom light bg, and fill alpha clamping
Model space appearance now follows the active UI theme instead of a
hardcoded dark background. Canvas mode is user-selectable (Match Theme /
Classic CAD Dark / Custom RGB), with matching controls for the paper
sheet and desk surround backgrounds and grid opacity.
Implements industry-standard CAD system variables, all settable via
command name, SETVAR, and the Options dialog:
- COLORTHEME (0/1 dark/light UI scheme, preserves custom palettes)
- COLORSCHEME (interactive named-theme selection)
- BACKGROUND (THEME/CLASSIC/DEFAULT/DESK presets, RGB triplets)
- SELECTIONAREA, SELECTIONAREAOPACITY (translucent marquee fill)
- WINDOWSAREACOLOR/WINDOWAREACOLOR, CROSSINGAREACOLOR (ACI or theme)
- SELECTIONEFFECT, SELECTIONEFFECTCOLOR (GPU highlight pass, driven by
selection_generation invalidation — no retessellation)
- SELECTIONPREVIEW (bit 0 = idle rollover, bit 1 = in-command hover)
- GRIPSIZE, GRIPCOLOR, GRIPHOT, GRIPHOVER (grip overlay styling)
Scene.selection was Rc<RefCell<SelectionState>> (31 fields, ~1.6KB
with poly_points) cloned deep per view_main per pane via
(*sel_ref).clone() at view/mod.rs:760. Replace with
Arc<RefCell<SelectionState>> (Arc::new(RefCell::new(...)) at
scene/mod.rs:1436,1868), SelectionCanvas holds Arc<RefCell> and
selection_overlay takes Arc<RefCell> (overlay.rs:365,415),
view_main does Arc::clone(&tab.scene.selection) (atomic bump)
instead of deep clone. sel_ref stays borrowed for field reads
(vp_size etc.).
Bench (release, n=5000): SelectionState::clone 39-68ns (64pts) /
11-22ns (0pts) vs Arc::clone 7ns — 5-9x on the hot path.
Tests:
- selection_is_arc_not_refcell: asserts type_name contains Arc
- bench_selection_state_clone / bench_selection_arc_clone / bench_selection_state_clone_empty (ignored, release)
Introduce a cache for the grid overlay to prevent redundant geometry
reconstruction on every UI redraw. Extract the pure projection logic
from draw_grid into a renderer-free grid_segments function, and add
GridKey and GridCanvasState to memoize the resulting Geometry.
The cache key includes all per-pane GridParams and the overlay bounds,
preventing stale geometry when camera changes within the same canvas
size. On a params-key miss, the iced cache is cleared before redrawing
to bypass its bounds-only reuse.
Tests: 8 new unit tests cover key invalidation and state handling.
Benchmarks: uncached geometry ~32 us/frame vs cached decision ~90 ns,
a >99% reduction. Verified: 472 passed / 1 pre-existing failure / 4
ignored.
Tests:
ui::overlay::grid_key_tests::grid_key_matches_identical_params
ui::overlay::grid_key_tests::grid_key_invalidates_on_changed_fields
ui::overlay::grid_key_tests::grid_key_invalidates_when_any_pane_changes
ui::overlay::grid_key_tests::should_reuse_empty
ui::overlay::grid_key_tests::should_reuse_equal
ui::overlay::grid_key_tests::should_reuse_changed
ui::overlay::grid_canvas_state_tests::default_state_has_no_cached_key
ui::overlay::grid_canvas_state_tests::stored_key_is_recognized_by_should_reuse
Sync projection controls from the active camera, preserve the UCS icon in perspective views, and use visible or selected 3D geometry for orbit pivots. Dense mesh vertex grips remain hidden.
Pin Iced 0.15 so dialogs can measure content before distributing the resolved frame across child panels. Keep the web plugin notice compact and non-resizable.\n\nCloses #582
Same hazard as the DYN guide in 48a43aa, in the three remaining dashed strokes:
a dash pattern is measured in pixels, so a path costs the tessellator a quad
every few pixels and its price is set by screen length. All three run from a
projected world point — an entity endpoint for the extension guides, the
acquired point for the OTRACK alignment line — which lands millions of pixels
away for a far or very large entity. That is millions of quads, and the process
fills RAM (#406).
The alignment line even said "the canvas clips it to the viewport" as the reason
extending it by 5000 px was safe. It isn't: the canvas clips after tessellating,
so every quad is built first. That assumption is what made these look bounded.
Clip in `clip_seg` (Liang–Barsky) before stroking, padded a dash period so a
guide still visibly leaves the frame. Unit-tested: a guide from 5,000,000 px
away comes back under 2000 px with its direction and its on-screen end intact,
a fully visible segment is untouched, and one that misses is dropped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dynamic-input guides are dotted, and a dash pattern is measured in pixels:
the tessellator emits a quad every few pixels, so a guide costs in proportion
to its SCREEN length, not to what it represents. Entering a large distance
(5000000 in the value box) projects the cursor millions of pixels from the
anchor, so the guide asks for millions of dash quads — gigabytes — and the
process fills RAM and dies while building a frame whose content is off-screen
anyway (#406).
Clamp the drawn length to just past the viewport diagonal. `dx`/`dy` still come
from the true cursor, and the angle and read-outs are computed from the raw
length, so direction and every displayed value are unchanged — only the tail
past the viewport edge, which nobody can see, is dropped. Clamping `cursor`
bounds every guide below it: they all derive from it, including the polar arc
and the perpendicular endpoint.
The reporter's own bisect points here: with DYN off the crash does not happen,
and drawing the same line by command (which builds no interactive guide) never
reproduced it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
STRETCH picks its crossing window as two corner points. After the first
corner set last_point, Ortho/Polar constrained the opposite corner to an axis
through it, collapsing the window to a line and making selection impossible.
Add CadCommand::window_corner_pick(); STRETCH returns true for its two window
steps, and the viewport skips the ortho/polar (and OTRACK) lock for that point
in both the preview and commit paths — the corners are now free.
Also render the window like a normal box selection instead of a bare green
outline: add SelectionState::preview_box (a hit-test-free marquee), draw it
with the shared draw_marquee helper (green crossing fill + stroke), and drive
it from the command's new window_first_corner(). Cleared on command end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge duplicate Type into pattern_type; Type/Associative/Island detection as dropdown/toggle; Angle/Scale/Spacing/Origin X-Y editable; Color via picker. Reorder Pattern group, drop redundant Color/Transparency/Layer override and the Misc section. Wire the pattern origin (per-line base_point) into the render (x0/y0 relative to world_origin) so it affects dashed/offset patterns; add a circle origin grip at the hatch centroid (new GripShape::Circle).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two refinements to Parallel snap. The acquired reference now keeps a
point on the line and draws a ∥ marker there, so the user sees which line
the parallel is measured from. And dwelling on that same reference line
again removes it (toggle), matching how OTRACK tracking points clear;
dwelling on a different line switches the reference. Same-line detection
uses parallel direction plus perpendicular screen distance, so it holds
across zoom.
Refs #277
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>