The operations docs/automation/API-SPEC.md (PR #1383) describes, brought
over from huaninstratech/OpenCADStudio so the specification no longer runs
ahead of the code:
- entity CRUD: entities_create, entities_delete, entities_transform,
entities_copy_to, and `where` filters on query
- blocks and export: block_define, block_delete, a normalised wblock
- publishing: layout_create, page_setup_set, plot (per_page included)
- metadata: sysvar, xdata_set / xdata_get, file_identity
- collections: group_create, selection_set_save / load
- control plane: batch, events, view_focus, close
- the REST transport: `--http <port>`, the /api/v1 routes and their
OpenAPI 3 description
- person-in-the-loop picks: user_select and getpoint, hosted by the GUI
over the same REST routes, with on-screen pending signals
Taken from the fork's commits b7a7ed80, 69396b42, 64f1cf6a, c3aafc8e,
bb5a7bfa, 52317a5c, 50d293ba, b90e194f, 517c6397, 53a5c4c9, a49ac045,
37de5a46, a78b1822, 6d9aed2a, 33f522da, 5b5cafe2, 02a7e78d, 94df40dc,
fd5bc14d, 485fac61 and 2c5fc994.
Left out on purpose: 37de5a46 also removed the donation surface (the
Patreon fetch, the donation prompt, the Start-page supporters rail, the
DONATE command and its locale strings) and replaced the About dialog's call
to action; that is the fork's own product decision and stays out. The
fork's version bump and its unrelated IFC/BIM work are not part of this.
Adapted while porting: the REST server and the GUI bridge are desktop-only
and a template start is refused in the web build, so the wasm target still
compiles; the two plot tests read the PDF through the shared stream helper,
because exported streams are Flate-compressed; the benches set the new
crosshair field; comments and docs describe each operation in its own
terms rather than by another product's API names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Placements (entity lookups, intersections, String labels, projections)
recomputed per view() and per mouse-move hit-test, with overlay re-clones
and 2-3x offset recomputes. New constraints_epoch bumped on every set
mutation/visibility/undo/DWG-load (plus new set_constraint_enabled API);
single-slot Arc<[GlyphEntry]> memo on an 11-field key (hover/selection
excluded); overlay de-cloned/de-duped to the single geometry source;
hit paths reuse the cache.
Tests:
- cached_glyph_placements_hit_returns_same_arc: same-key Arc identity
- cached_glyph_placements_invalidated_by_metadata_edit: edit + selection change yield new Arc
- glyph_hit_test_entries_matches_precomputed_boxes: shared hit-test helper
Bench (cargo bench glyph, 60 constraints): 8.18us -> 0.05-0.15us (~55x).
A/B reference deleted.
The dynamic dimension of a dimensional constraint is now linked in the
drawing file (AcDbAssocDependency + AcDbAssocDimDependencyBody on the
constraint node), reread on load, hidden by DCHIDE/DYNCONSTRAINTDISPLAY
and never plotted; it carries a lock mark before its text. New
CONSTRAINTNAMEFORMAT and DYNCONSTRAINTDISPLAY variables and the
DIMCONSTRAINT option front end. A transformed entity whose own end points
a driving dimension measures keeps only the first constraint point, so a
scaled line returns to its dimension value.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
An OTRACK crossing is the meeting of two tracking vectors, but
`OtrackHit` carried only the one a typed distance is measured along, so
the overlay had a single guide to draw. Snapping to the crossing of two
vectors worked and looked like an unexplained point on one line.
Carry the crossed ray out of `otrack_snap` as `OtrackHit::cross`, keep it
on the app beside `otrack_active`, and let the overlay take a list of
guides rather than one. `otrack_active` still drives typed-distance
entry on its own, so distance entry is unchanged.
FixesHakanSeven12/OpenCADStudio#1313
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TQzYZsXBwPZ5HwBaGitEUV
(cherry picked from commit 2f6caacece)
1) VCONSTRAINT (and the new GCVERTICAL name) uses the axis constraint flow:
"Select an object or [2Points] <2Points>:", 2Points mode with the
reference's re-prompts and messages, and the same object types as
Horizontal — line, polyline segment, text, MText, ellipse axis.
2) Object mode turns the object about its first vertex; a two-point relation
across two objects slides the second object rigidly onto the axis.
3) Re-applying an axis constraint reports that the constraint already
exists; Vertical follows the UCS Y direction.
4) The constraint bar draws Vertical as the reference's axis mark, with the
point marker for a two-point relation.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
7) The constraint bar draws Fix as a padlock instead of the letter F: a
white lock with a small orange point marker when one point is held, a
red lock when a whole curve or polyline segment is held.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The Snap and Grid tab showed static grid values with dead checkboxes, and every grid line stroked identically.
Grid X/Y spacing and Major-line-every are now editable inputs (validated 2-100 for majors, dirty-tracked, applied with an error message on bad input); Adaptive-grid and Display-beyond-Limits checkboxes are wired to live state. Every Nth line partitions into major_segments and strokes subtly brighter (1.5x alpha, 0.75px) for the reference hierarchy. GRIDMAJOR state persists to UserSettings. Covers partition, invalidation, and parse tests.
The viewport grid ignored the configured spacing and forced square grids: compute_grid_step hardcoded a 1.0 base, a single shared step drove both line families (including the tangent derivative used for gap measurement), and the display always clipped to LIMITS despite the beyond-Limits option.
Grid spacing now scales per-axis from GRIDUNIT via compute_grid_steps (adaptive 5x growth, fixed passthrough), GridParams carries step_x/step_y, snap tolerances follow the visible steps, and the LIMITS clip is skipped when beyond-Limits is set. Also fixes a per-axis overflow fallback that returned the X base for Y. Spacing state (X/Y, adaptive, beyond-Limits) is stored on the app and persisted to UserSettings at defaults; dialog wiring follows separately.
Grid snap locks to SNAPUNIT instead of magnetizing; snapped 3D points keep elevation instead of flattening to z=0. New SnapTypes Vertex/EdgeMidpoint/FaceCenter/Knot/FacePerpendicular/NearestFace with their own F4 master toggle and mode set, tiers, and markers, fully independent of the 2D master. Solid B-rep corners/edge-midpoints/face-centers from mesh edges and kernel faces, spline knot points, and live perpendicular/nearest-to-face on mesh triangles merged priority-aware in move/click paths. Drafting Settings 3D Object Snap tab is fully live (was dead placeholders); Nearest-to-face off by default. TDD: behavioral LINE-to-solid tests plus engine/scene unit tests.
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 <[email protected]>
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 <[email protected]>
- 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