Highlighting a whole-drawing selection took 299 ms, and it runs when the
selection changes — directly between the click that completes a selection and
seeing it.
Most of that was asking the same question twice. Each wire went through the
circle and ellipse extractors at its draw depth to build the analytic
instances, then through both again at depth 0.0 purely to ask *whether* it is
a circle or an ellipse, allocating a vector each time and dropping it.
Neither extractor's `Some`/`None` answer depends on the depth: every
`return None` in them turns on the geometry — which `TangentGeom` variant,
the radius, the axis lengths, the parameters — and the depth is only written
into the instance, never read to decide. So the extraction that builds the
instances already answers the classification. One pass now does both, and
fans out across cores, since it reads a wire and writes nothing shared. That
took 173 ms of shape extraction and splitting down to 19 ms, and the whole
highlight from 299 ms to 150 ms.
Gathering the highlighted wires also cloned each handle's slot vector and
sorted the copy — one allocation and one sort per selected entity. The slots
are already ascending: both builders walk the wires in order and push as they
go, and `patch_handle_index` shifts a handle's slots by a constant, which
preserves it. A `debug_assert` on the slots actually read holds that in
place. The compat path of `from_highlight_refs` emitted its instances
sequentially while the native path beside it fans out; it now matches.
Nothing renders in a test, so a wrong bucket in the classification would
surface only as circles drawn wrong when selected. Two tests cover it: each
wire lands where the old predicate put it, and the colour override — always
for hover, and the configured tint or `SELECTED` for a selection — reaches
the instances.
`wire-highlight-detail` reports the phases: gathering, classifying,
uploading the analytic instances, the regular wires and the block ones.
A window over a whole drawing spent 407 ms gathering candidates and 259 ms
testing them, of 825 ms total, at 186 468 entities.
**Gathering** ran eight spatial queries because that is what hover and
snapping need between them. An area selection reads five: the wires and their
segments, plus the fill triangles, pick triangles and glyphs that
`indexed_box_crossing_hits` uses to catch hatches and text inside the box.
The three that exist only for snapping are skipped.
That was worth 148 ms. The rest came from the queries themselves.
`SpatialGrid::query` sorts its result twice — once over the entry indices, to
remove the repeats an entry collects for spanning several cells, and once
over the values. Over a box covering the drawing that is two O(n log n)
passes across 4.4 M elements to remove a handful of duplicates.
`query_unordered` returns the same set in cell order with the duplicates left
in, and reserves the space it needs instead of growing a 17 MB vector in
steps. Gathering went from 515 ms to 69 ms on a synthetic set at that size,
and from 407 ms to 27-40 ms in the application.
Everything an area selection does with the result already dedups and does not
read the order. Click picking keeps the sorted `query`, where scan order
decides which of two overlapping entities a click lands on.
**Testing** projected both endpoints of every segment in the box, then threw
the result away for any wire already selected. A circle or a polyline
contributes one segment per tessellated span, so at 24 spans per wire that is
23 projection pairs out of 24 doing nothing. Skipping a wire once it is in
took a 4.4 M-segment scan from roughly a second to 42 ms.
The overlay path — active after any edit, so most of a working session —
needed the same treatment, including its own merge: `extend_indexed` sorts
and dedups every category, which would have put back the sorts this removes.
`extend_indexed_area` keeps the dedup on `wire_indices`, and must: the window
branch of `box_hit` walks it directly, so a wire left in twice would be
selected twice.
Tests cover the equivalence in both directions: the area query matches the
full one as a set in every category an area selection reads, and the merge
dedups `wire_indices` and nothing else.
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]>
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]>