Selecting a whole drawing was unusable on a 186 468-entity file. Four costs
scaled with the selection where they did not have to.
Every selected handle triggered a walk of the entire document to find LEADERs
pointing at it. On the file this came from, selecting 471 583 entities in a
585 786-entity drawing did not finish inside eight minutes. A reverse index
built once per geometry epoch answers the same question; a test asserts it
returns exactly what the walk returned, including the case of two leaders on
one annotation.
Group expansion asked the selection about every group: for each selected
handle it scanned each group's own member list. Asking each group once
whether it holds anything selected turns that around.
Selecting and deselecting went one entity at a time, and `deselect_entity`
rebuilt the ordered list with a `retain` per handle, so removing N of M
walked the order list N times. Both now take a slice.
Grips were built for every selected entity. AutoCAD stops at GRIPOBJLIMIT,
and the DWG header does not carry the variable, so its default of 100 stands
in for it.
The remaining two commits are measurement: `select-commit` reports the phases
of a selection separately, which is what showed the bulk paths had been added
to the press-drag branch while a click-move-click window goes through a
different one entirely.
- Add 2px top padding for breathing room above buttons
- Buttons now start above the resize slider area
- Improves visual spacing at top of command history panel
Co-authored-by: Copilot <[email protected]>
- Remove top padding from panel container (0 instead of 4)
- Buttons now start flush at top edge above scrollbar
- Improves visual alignment and space usage
Co-authored-by: Copilot <[email protected]>
- Increase header container right padding from 6 to 8 pixels
- Ensures Copy and Clear buttons align consistently with top alignment
- Improves visual spacing on the right side for cleaner UI appearance
Co-authored-by: Copilot <[email protected]>
- Modify COPYCLIP command to write entities as JSON to system clipboard
- Modify CUTCLIP command to write cut entities as JSON to system clipboard
- Modify COPYBASE_AT command to write copied entities as JSON to system clipboard
- Entities are serialized as JSON with base point coordinates for cross-platform support
- Clipboard data format: OCS_CLIPBOARD_JSON header + base point + entity data
- Works in web (WASM) build via Clipboard API and native builds via iced clipboard
This fixes the issue where copy command didn't persist to system clipboard in web browser,
allowing proper paste operations and clipboard sharing between application instances.
Co-authored-by: Copilot <[email protected]>
- Modify COPYCLIP command to write entities as JSON to system clipboard
- Modify CUTCLIP command to write cut entities as JSON to system clipboard
- Modify COPYBASE_AT command to write copied entities as JSON to system clipboard
- Entities are serialized as JSON with base point coordinates for cross-platform support
- Clipboard data format: OCS_CLIPBOARD_JSON header + base point + entity data
- Works in web (WASM) build via Clipboard API and native builds via iced clipboard
This fixes the issue where copy command didn't persist to system clipboard in web browser,
allowing proper paste operations and clipboard sharing between application instances.
Co-authored-by: Copilot <[email protected]>
- Changed button padding from [5, 10] to [4, 8] to match layout tabs
- Changed container left padding from 6.0 to 2.0 for consistent spacing
- Plus button now has same distance from Start tab as from Layout tabs
Co-authored-by: Copilot <[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]>
Carry the loader-built resident wire layout into the installed scene so the first edit can use the incremental patch path. Add a regression test that exercises the full prepare/install/edit handoff.
- Fall back to WireModel::SELECTED when selected_tint is None (classic mode) so selected circles, arcs, and ellipses are not omitted from the highlight pass.
- Explicitly rebind uniform_bind_group before drawing analytical curves in selection_xray pass.
- Skip invisible wires (display_visible == false) during selection and hover wire extraction.
The first drawn point costs 946 ms against 17-39 ms for every one after it,
and `build=587.67ms` is most of that — a full wire assembly where the
following points patch in 1.2 ms.
`try_resident_patch` refuses any resident set whose `layout` is `None`, and
`install_prepared_open_geometry` inserted the loader's set with exactly
that. So the set the file open hands over could never be patched, and the
first edit rebuilt all 236 954 wires to add one.
The layout is a pure function of the wire order, and the loader already
built it for those very wires — `resident_wires_for` stores it in the
temporary scene's resident set. It now travels in `PreparedOpenGeometry`
like the tessellation memo and the draw-order map, taken by `Arc::ptr_eq`
identity so it cannot be paired with a different assembly.
`try_resident_patch` also reports why it declines. Each refusal costs a
full assembly, and until now the only one that said anything was the shared
`Arc`; `no-layout`, `no-entry` and `journal-too-old` were silent.
**Not verified.** The headless harness opens through the synchronous path,
so `install_prepared_open_geometry` never runs there, and `ZOOM E` does not
reach `try_resident_patch` at all — no skip line is emitted for it. This
needs a GUI run on the reproducer: the first drawn point should drop well
below 946 ms, and if it does not, `resident-patch-skip reason=` now says
what is still refusing.
Co-Authored-By: Claude Opus 5 <[email protected]>
The skip branch printed `circles=0 ellipses=0`, which reads as "there are
no circles" when it means "not recomputed this frame" — and those counts
were the evidence that confirmed the skip works, so the wording nearly
cost a misreading of it.
The skip now prints its own line with what it retained.
Measured effect of the skip itself, on the reproducer: a drawn point goes
from 98 ms to 17-39 ms, `prepare` from 89 ms to 11-23 ms. Frames that draw
a circle still re-partition, and the instance counts show it — 40868,
40869 … 40874, one per circle, with the line frames skipped in between.
Co-Authored-By: Claude Opus 5 <[email protected]>
`forget_cached_keys` is the one place that means "this slot holds nothing
you can trust", and it left the partition record behind. A reused slot
rebuilds regardless — its content id changed, so the arena takes the build
path and the record is overwritten — but that made the safety an accident
of another code path rather than something this function guarantees.
Co-Authored-By: Claude Opus 5 <[email protected]>
`skipped=false` on every frame of a real session — the skip added in the
previous commit never fired once.
The membership test used the same cheap structural filter that
`partition_wires` uses before attempting extraction: a tangent geom, no
fill, pick or text triangles. That filter is a pre-check, not a decision.
An ordinary drawn line carries a tangent geom for snapping and no
triangles, so it passed, and every drawn line was counted as feeding the
analytical uploads. Nothing was ever skippable.
It now decides the way the walk decides, by attempting the extraction.
`draw_depth` only rides along in the extracted instance, so the value
passed for the test cannot change the answer, and it runs only for the
handful of wires a patch changed.
Drawing a circle or an ellipse still forces the walk, correctly — those
are contributors, and the session that exposed this contained both. What
changes is that the lines drawn afterwards no longer do.
Tested on real tessellated geometry: a line must not force the walk, a
circle must.
Co-Authored-By: Claude Opus 5 <[email protected]>
`arena-post partition=55.2ms blocks=5.9ms curves=2.5ms` — the partition is
almost the whole of the 73 ms an arena patch spends after patching, and it
walks all 236 956 resident wires to serve a one-entity change.
Its three consumers here — the instanced, circle and ellipse uploads —
depend only on wires that are block references or analytical curves. A
patch that touches none of those cannot change any of them, so the walk is
skipped and the uploads already on the slot are kept.
Two things make that sound, and both are checked rather than assumed:
**Depths hold still.** The instances bake a draw depth.
`inserted_draw_depth_label` allocates into a reserved gap instead of
renumbering, so adding an entity leaves every other label alone — the
existing doc comment says so and there is now a test. A rebuild from
scratch *can* move them, so `draw_depth_generation` bumps only there, and
a slot reuses its uploads only while that generation is unchanged.
**Contributors are remembered.** `partition_wires` records which handles
fed those three outputs, and the skip requires that no changed handle is
among them and that no changed handle's new wires would join them. That
catches an entity entering the set and an entity leaving it. The
membership test is shared with the walk it decides to skip, so the two
cannot drift, and it is deliberately conservative: a wire that looks
analytical but fails extraction forces a needless re-partition, never a
stale upload.
`arena-post` gains `skipped=` so a run says how often this actually fires
rather than leaving it to be inferred from the timing.
Not verified on a device yet — the headless harness never reaches
`prepare`, so this needs a GUI run on the reproducer to confirm both the
saving and that nothing goes missing on screen.
Co-Authored-By: Claude Opus 5 <[email protected]>
`arena-post partition=55.2ms blocks=5.9ms curves=2.5ms` — the partition is
the whole of the 73 ms an arena patch spends after patching, and it runs
on every drawn point over the entire resident set.
Inside it, `wire_draw_depth` is a hash lookup whose result feeds only the
analytical circle and ellipse extraction, and it was computed before the
test that decides whether either will run. So every patch paid 236 956
lookups to serve the ~56 000 wires that are circles or ellipses.
Moving it inside that branch is behaviour-preserving: the value was
unused on every path that skipped it.
This does not make the partition incremental — it still walks the resident
set for a one-entity change — but that walk is memory-bound and a
structural fix needs the cached partition to survive a patch, which the
borrows in `PartitionedWires` make its own piece of work.
Co-Authored-By: Claude Opus 5 <[email protected]>