Commit Graph
2233 Commits
Author SHA1 Message Date
Hakan Seven 055f1cdb1a Redesign modelling ribbon icons 2026-09-05 14:46:06 +03:00
Hakan Seven de3fe2fdf1 Remove native and web compiler warnings
Reuse the application library from the binary entry point instead of compiling the module tree twice. Keep desktop-only transports and caches out of web builds, remove unused test code, and check executable targets in CI.
2026-09-05 13:21:02 +03:00
Hakan Seven 3253965194 Run wire arena regressions in CI and shorten cache documentation 2026-09-05 12:33:20 +03:00
Hakan Seven ff3262e39d Merge commit '7bf095e02be49764fdda1fa32ef13a1675592246' 2026-09-05 12:30:43 +03:00
Hakan Seven c39fd8c4c0 Merge commit '69bd3c40a9ed3c0a2e412cc5716ef52b5e300033' 2026-09-05 12:30:43 +03:00
Hakan Seven d91e54e501 Merge commit 'b9c252474853d47c91bd6c03cd6bf8752378061f' 2026-09-05 12:30:43 +03:00
Hakan Seven af0cc68c5a Merge pull request #1006 with block rendering fixes
Invalidate cached world-space block vertices when the base translation changes. Share the regular/mesh partition between full uploads and patches so instanced blocks are uploaded only once. Cover both paths with CPU and GPU regression checks.
2026-09-05 12:30:43 +03:00
PanosandClaude Opus 5 19490a548b fix: give the wire arena its own buffer budget
`PersistentWireArena` keeps one instance buffer for the whole resident
set and patches it in place on an edit. Sizing that against the shared
per-chunk budget caps it at 32 MiB, which is 524 288 `WireInstance`s.

A drawing with more than that fails `WireArena::build`, falls back to the
batched path, and re-uploads everything on every edit. On the 198 k-wire
reproducer -- 821 316 instances -- that shows up as:

    wire  200-370ms  arena-hybrid

instead of the ~12 ms `arena-patch` the same drawing reached before the
limits moved onto the shared budget.

The 32 MiB cap is right for transient chunks, where many are allocated
per frame and any one of them failing loses the frame. The arena's buffer
is a single long-lived allocation that exists precisely to avoid those
re-uploads, and it can afford more: 821 k instances is 53 MB, ~79 MB with
the arena's headroom. 128 MiB leaves it four times smaller than the
505 MiB allocation that crashed a 2 GB card, and it is allocated through
`gpu_upload`, so exhausting VRAM still degrades the frame rather than
aborting.

`OCS_GPU_CHUNK_MIB` overrides both caps, so a test can still force small
buffers everywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:50:17 +03:00
PanosandClaude Opus 5 fb64e76f04 fix: let the wire arena serve drawings that contain blocks
Clicking to end a segment froze the UI until the whole drawing had been
re-uploaded. With block geometry cached the remaining cost is the regular
half, and on two drawings it dominates:

    113 k wires   regular=96ms  block=13ms  index=12ms   total 122ms
    198 k wires   regular=85ms  block= 7ms  index=25ms   total 118ms

It is rebuilt from scratch because `PersistentWireArena` -- the only
thing that patches wire buffers in place -- was disabled outright by:

    && !vp_wires.iter().any(|wire| wire.render_instance.is_some())

One block reference was enough; the reproducer has 11 363. The scene
already produces an incremental patch in 0.9 ms and the GPU side could
not use it.

Instanced wires are not something the arena has to handle: they are a
different batch type with their own pipeline. Partition them out, let the
arena own and patch the rest, and keep building `BlockWireGpu` for them.

The handle index is unaffected -- it maps into `vp_wires`, not into arena
slabs, and the patch path already maintains it.

Measured on the 198 k-wire drawing, per drawn segment:

    before   wire 350ms  (shared-fullupload)
    after    wire  12ms  (arena-patch)

with the whole click, including `bump_entities`, going from ~411 ms to
~20 ms.

Verified visually on drawings with and without blocks, through repeated
edits and undo/redo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:18:50 +03:00
PanosandClaude Opus 5 071b4b6a87 perf: keep block geometry on the GPU across edits
Adding one entity to a drawing rebuilt every block group's geometry from
scratch. Measured on a 1.17 M-entity drawing with 11 380 block wires, the
per-edit upload split as:

    upload-split regular=80.4ms block=230.3ms

The block half is three quarters of it, and every byte of it was
identical to the previous frame: a new line is not a block reference.
`BlockWireGpu::from_wires` re-emitted each group's segments, re-expanded
them six-fold and re-uploaded the vertex buffers anyway.

That work depends only on the block definition being expanded, which
already has a stable identity: `render_instance.source_id`, minted once
when the block cache expands a definition and unchanged while that
expansion stays cached. Cache the vertex chunks and constants under
`(source_id, mesh_edge, colour)` -- colour is in the key because a
highlight tint changes the constants while leaving the vertices alone.

Only the per-instance placements are rebuilt, and they must be: each
carries its own draw depth and the depth map shifts on every add or
remove. They are also small, a few thousand placements against millions
of vertices.

Entries not seen in a build are dropped, so a block-cache rebuild that
mints fresh source ids does not strand old buffers on the GPU.

Measured: the block half falls from 230 ms to 7.4 ms, and the whole
per-edit upload from ~350 ms to ~118 ms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:16:12 +03:00
PanosandClaude Opus 5 7bf095e02b perf: parse wire names across cores when building the handle index
With the block geometry cached, the per-edit upload is 135 ms of which
only 92 ms was accounted for by the regular and block halves. The rest is
the handle index: a `String::parse` per wire over the whole 198 k
resident set, rebuilt from scratch on every edit.

The arena has an incremental path for this (`patch_handle_index`), but
the shared path never reaches it -- the same gate that keeps the arena
away from this drawing. Short of that, the parses are independent, so run
them across cores and keep only the map insertion serial.

`par_iter().enumerate()` collects in index order, so the slot lists keep
the emission order they depend on.

Adds a `wire-handle-index` PERF line, since this was the unaccounted
third of the upload.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:10:49 +03:00
PanosandClaude Opus 5 69bd3c40a9 perf: stop scanning every entity for associative centers on each edit
The spans put it exactly: 63.5 ms of a 63.8 ms pointer-up is
`bump_entities`, and inside it two drawing-wide scans.

`refresh_associative_centerlines` and `refresh_associative_center_marks`
each walk all 1 166 357 entities, filter to Lines and parse every one's
extended data looking for an association that references a changed
handle. About 30 ms each, run on every edit -- to find, on this drawing,
none at all.

Track whether the document holds any such association. The question is
settled once, with a single pass, and after that each edit only examines
the handles it actually changed: an entity that carries an association
flips the answer to yes, and nothing else can. Deleting the last one
leaves the answer at yes, which costs a scan that finds nothing rather
than skipping one it should have run. A coarse `bump_geometry` re-opens
the question, since it can carry a new document.

Where an association really exists the scans run exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:10:49 +03:00
PanosandClaude Opus 5 b9c2524748 perf: warm the entity quadtree alongside the handle index
Warming only the interaction handle index left the first hover at
385 ms, down from 992 but not gone:

    hover-detail  query=0.1 handles=384.6 ... candidates=186

The handles stage builds two indexes, not one. `interaction_candidate_handles`
also reaches for `entity_index()` — the entity quadtree, keyed on
geometry_epoch and just as cold at that point. Warm both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:10:49 +03:00
PanosandClaude Opus 5 909b7f3379 perf: hand the loader thread's block definitions to the scene
The first edit on a large drawing spent 1147 ms rebuilding the block
definition cache:

    wires-build 2723.3ms ... blk_cache=1147.2 ... memo_miss=186455

Not because the edit invalidated it -- `add_entity_internal` already
routes a non-block entity through `bump_entities`, so drawing a line
leaves `block_epoch` alone. The cache was empty because
`prepare_open_geometry` built it on a throwaway `Scene`, exactly as it
did the tessellation memo, and it went the same way.

`PreparedOpenGeometry` now carries the definitions across, moved rather
than cloned, and `install_prepared_open_geometry` stamps them with the
receiving scene's `block_epoch`. That stamp is sound: the document is not
touched between the loader finishing and this call, so the definitions
describe it exactly. A key that does not match what the scene later asks
for simply misses, which is the old behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:10:49 +03:00
PanosandClaude Opus 5 38f576c296 perf: warm the interaction handle index during open
The first hover over the canvas after opening a large drawing froze the
UI for a second:

    hover-detail  query=0.2 handles=986.1 ... candidates=253
    hover-dwell   986.6ms

`interaction_handle_index` and the insert-hatch map it reads are built
lazily, and first use is that hover. Neither can move to the loader
thread: they read `hatches`, `meshes` and `block_meshes`, which are
installed on the real `Scene` only after the loader has finished.

Build them at the end of the open instead, where the user is already
waiting. This does not make the work cheaper -- open grows by roughly the
second the hover used to cost -- but a second inside an operation that is
already seconds long reads very differently from a second-long freeze on
a mouse move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:10:48 +03:00
PanosandClaude Opus 5 d5d9ceec45 perf: hand the loader thread's tessellation memo to the scene
`prepare_open_geometry` builds the first Model wire set on a throwaway
`Scene`, filling its per-entity tessellation memo with an entry for every
one of the drawing's 186 455 entities. `install_prepared_open_geometry`
then transferred the wire set and the interaction index but not the memo,
so the temporary scene took it to the grave.

The first geometry edit therefore rebuilt from cold:

    wires-build 2723.3ms memo_hit=0 memo_miss=186455

against 760 ms once the memo is warm -- 1.3 s of work the loader thread
had already done, repeated on the user's first action.

`PreparedOpenGeometry` now carries the memo, moved rather than cloned
since the scene is dropped immediately afterwards. The guard hash travels
with it: the memo is only usable under the state it was built for, and a
mismatch on the receiving side clears it, which is what happened
unconditionally before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:10:48 +03:00
PanosandClaude Opus 5 2d2fdacdfd perf: parse each wire name once when applying draw order
The resident wire sort keys on the entity handle parsed out of each
wire's name `String`. `sort_by_key` evaluates that key on every
comparison, so a 198 k-wire resident set parsed every name roughly
eighteen times over.

Measured at 295 ms on every rebuild of the resident set -- including
rebuilds that re-tessellated nothing, where the memo hit on all 186 455
entities and the sort was most of what was left.

`sort_by_cached_key` evaluates the key once per element. It is stable in
the same way `sort_by_key` is, so draw order is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 09:10:48 +03:00
Hakan Seven 13afc1479f Automate weekly releases and synchronize application versions 2026-09-05 02:13:20 +03:00
Hakan Seven 28d3aa5be2 Harden native MCP compatibility 2026-09-05 01:36:23 +03:00
Hakan Seven 17e5cda3fb Tighten MCP command line control 2026-09-04 23:38:46 +03:00
Hakan Seven d7c3422dbe Space MCP control from command line edge 2026-09-04 23:30:26 +03:00
Hakan Seven 071e224d26 Add client-neutral native MCP control 2026-09-04 23:27:03 +03:00
Hakan Seven 28cc473062 Add persistent live OpenCADStudio control 2026-09-04 22:25:55 +03:00
Hakan Seven 9d991d7feb Bound GPU uploads and complete Greek and performance integration 2026-09-04 20:26:29 +03:00
Hakan Seven 89624f64fc Merge PR #1002: opt-in performance instrumentation 2026-09-04 20:11:02 +03:00
Hakan Seven 75f9205da4 Merge PR #1001: GPU allocation stability and Greek locale 2026-09-04 20:06:56 +03:00
Hakan Seven 92e1dfd685 Merge PR #1000: cache selection filter entity types 2026-09-04 20:06:56 +03:00
Hakan Seven 90761904a9 Merge reviewed modeling PRs with Unicode and PRESSPULL fixes 2026-09-04 19:49:55 +03:00
Hakan Seven d8790ef433 Use exact kernel curves for PRESSPULL area containment 2026-09-04 19:34:38 +03:00
Karim Jerbi a9fda66ac0 fix(ui): add non-color marker to CLI info entries and drop dead desk parameter
- Add 'i  ' prefix to EntryKind::Info and coordinate set_step_prompt deduplication to satisfy WCAG 1.4.1 across all 22 themes
- Drop unused _desk parameter from crosshair_background and clean up its call sites
- Document AutoCAD-standard context-based crosshair drafting surface architecture in crosshair_background
- Add doc comments documenting deliberate alpha omission in canvas_is_light and backlog scope in is_wide
2026-09-04 16:26:13 +01:00
Karim Jerbi b53cd580f6 feat(ui): shared dropdown popup width calculation with wide-character support
- Add dropdown_popup_width helper in common.rs replacing magic formula heuristics
- Implement is_wide range table to count East Asian Wide/Fullwidth (CJK) glyphs double for localized strings
- Calibrate unit scale factor to 0.68 * font_size preserving existing width behavior at font size 11
- Apply shared calculation across viewport controls and ribbon dropdowns
- Update unit tests to verify width calculation on standard and CJK labels
2026-09-04 16:26:12 +01:00
ramox81 eee077cbce Use blue for the transient LOFT preview cage 2026-09-04 17:36:33 +03:00
Karim Jerbi a7e3cf3a6d fix(ui): enforce WCAG AA contrast thresholds across UI themes
- Raise accessible_accent default threshold from 2.0 to 3.0 (WCAG 1.4.11 UI state indicators)
- Raise EntryKind::Info command line history threshold from 3.0 to 4.5 (WCAG 1.4.3 normal text)
- Update themed_check_cell to verify >= 3.0:1 contrast across both weak popup panel and strong highlighted rows
- Replace fully-qualified accessible_accent_threshold calls inside closures with use imports
- Add comprehensive 9-suite theme accessibility contrast test suite covering all 22 themes across core surfaces
2026-09-04 15:34:24 +01:00
Karim Jerbi 23a9884f90 fix(ui): canvas-aware selection colors and paper space detection
- 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
2026-09-04 15:28:25 +01:00
PanosandClaude Opus 5 6747d7125e fix: no GPU allocation path maps, however small the buffer
The previous commit converted the allocations that scale with the
drawing and deliberately left the small fixed-size ones -- uniforms, the
viewcube's static geometry, per-image and per-wipeout quads -- reasoning
that an allocation failure was not a realistic outcome for a few dozen
bytes.

That reasoning was wrong, and the log says so precisely. On the
reproducer the vertex buffers degraded exactly as intended:

  [gpu] uncaptured ... Out of Memory
  [gpu] uncaptured ... In Queue::write_buffer
          Buffer with 'block_wire.vertices' label is invalid
  ... errors #4 #8 #16 #32 #64 #128, session kept alive

and then the process died here:

  thread 'main' panicked at wgpu_core.rs:2253:
  Error in Buffer::get_mapped_range: Validation Error
  Caused by: Buffer with 'block_wire.const' label is invalid

'block_wire.const' is a single uniform struct. Once the device is out of
memory every allocation fails, including a 32-byte one, and
create_buffer_init maps it and panics. Size was never the relevant
property -- whether the path maps is.

So every remaining allocation goes through gpu_upload too: block-wire
const, the viewcube's seven buffers, the blit and clip-boundary buffers,
image instances/params/vertices, mesh material params and surface and the
empty index stub, the texture-hatch uniform, and all five wipeout
buffers. `grep -r create_buffer_init src` now finds only a comment, and
no mapped_at_creation:true remains outside gpu_upload's own docs.

`queue` is threaded through the last holdouts: upload_wipeouts,
upload_clip_boundary, MeshBatchStubs::new and
create_material_bind_group_from_resources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:21:08 +03:00
PanosandClaude Opus 5 0d11421dea fix: free superseded wire buffers before allocating their replacement
The renderer shares one set of wire GPU buffers per wire_content_id
across every pane and viewport slot. Every geometry edit mints a new
content id, so every edit builds a new set -- and the old one stayed in
wire_buffer_cache until the map exceeded sixteen entries.

Sixteen entries is a sane bound for small drawings and fatal for large
ones. The policy counted entries, not bytes. On the reproducer a single
set is most of the card: the VRAM trace shows 572 MiB baseline rising to
1871 MiB on a 2048 MiB GT 1030. The second set has nowhere to go, so the
first edit after opening exhausts VRAM. The perf log shows exactly that
-- two `shared-fullupload` lines, an edit, then the process dies.

Evict on every miss instead of at a count threshold, and do it before
building the replacement rather than after, so the old allocation is
released while the new one is still being sized. Dropping this slot's own
handles first is what lets the retain see the superseded entry as
garbage; a pane still drawing that content keeps its own clone and its
strong count stays above one, so shared geometry is never pulled out
from under a live viewport.

The trade is that returning to a previous content id -- undo, mostly --
now rebuilds instead of hitting a warm entry. On a card where the
alternative is dying, that is the right way round.

Adds a PERF line reporting evicted and retained entry counts, so the
policy can be watched rather than assumed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:21:08 +03:00
PanosandClaude Opus 5 d0ef752532 fix: an out-of-memory GPU no longer aborts the process
The crash in #358 was not the out-of-memory itself. wgpu reports OOM as
recoverable and the app already installs a handler that logs and keeps
the session alive. What killed the process was what happened next:

  create_buffer_init maps the new buffer unconditionally, and mapping the
  invalid buffer an OOM device returns panics. That panic unwinds through
  iced's main-thread redraw, trips the wgpu-hal swapchain assertion in a
  destructor, and becomes a non-unwinding abort -- no chance to autosave.

Every allocation whose size scales with the drawing now goes through
src/scene/pipeline/gpu_upload.rs, which creates the buffer unmapped and
fills it with Queue::write_buffer. A failed allocation then surfaces as a
validation error on the write, which reaches install_gpu_error_handler
and degrades the frame -- exactly what that handler's comment promised:
"A bad frame must degrade, not end the session."

Converted: block-wire vertices and instances, the wire const storage
buffer, all three wire-arena allocators, face3d vertices and instances,
block-text instances and text vertices, both hatch backends, and the
silhouette buffers. mesh_gpu already allocated this way, which is why the
mesh path never produced this panic; the rest now match it.

No mapped_at_creation:true remains in the renderer. The create_buffer_init
calls left are fixed-size or bounded-small -- uniforms, the viewcube's
static geometry, per-image and per-wipeout quads -- where an allocation
failure is not a realistic outcome.

Two trade-offs worth stating:

  * write_buffer stages through a copy, which mapping at creation avoided.
    Chunking bounds that transient cost, and the measured block-wire
    upload got faster with chunking, not slower.
  * `queue` had to be threaded through the wire, face3d, text and hatch
    builders and their callers. Mechanical, but it widens the diff.

This touches every upload path, so it needs a visual check against a real
drawing before it goes upstream.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:21:08 +03:00
PanosandClaude Opus 5 771a5b2c5c fix: one VRAM-aware buffer budget for every GPU upload path
max_buffer_size is an API validation limit, not physical VRAM. NVIDIA
reports several GB of it on a 2 GB card, so sizing a chunk from it
produces buffers larger than the whole device. That is the same bug as
#203 (mesh_gpu), #358 (face3d_gpu) and the block-wire vertex buffer:
rediscovered three times because each upload path spelled the budget out
for itself.

mesh_gpu.rs was the only site that clamped the result. Move that clamp
into src/scene/pipeline/gpu_budget.rs and route every allocating path
through it, so a new upload path cannot reintroduce the mistake by
copying one of the unclamped expressions.

Five sites had no clamp at all: text_gpu (block-text instances),
wire_gpu (block-wire instances), and face3d_gpu (vertices, instances,
and upload_chunks).

Three behaviour changes worth noting:

  * Silhouette chunks were capped at a private 64 MiB constant and now
    use the shared 32 MiB budget.
  * The storage-hatch chunk limit was the minimum of two device limits,
    both of them validation figures; it is now also capped by the
    budget. The `*_fits` predicates next to it are genuine "would wgpu
    reject this" checks and are left alone.
  * mesh_gpu's instanced path used the unclamped ceiling to decide
    whether a mesh fits in a single buffer, which would still admit a
    multi-GB allocation. It now uses the clamped budget, so a few more
    meshes take the chunked path.

OCS_GPU_CHUNK_MIB overrides the cap. Raising it above the real VRAM is
the cheapest way to force an allocation failure and check that the
renderer degrades instead of aborting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:20:38 +03:00
PanosandClaude Opus 5 8552450aa8 fix: chunk block-wire vertex buffers
BlockWireGpu::from_wires built one unbounded vertex buffer per
(source_id, mesh_edge) group. On a block-heavy drawing that reached
~505 MiB in a single create_buffer_init, which fails on a 2 GB card;
wgpu then returns an invalid buffer and the unchecked
get_mapped_range_mut() inside create_buffer_init panics.

The instance buffer in the same function was already chunked. Convert
the vertex buffer the same way, matching the solid-mesh chunking in
mesh_gpu.rs (#203) and the fill chunking in face3d_gpu.rs (#358),
including mesh_gpu.rs's clamp: max_buffer_size is an API validation
limit, not physical VRAM, and NVIDIA reports several GB on a 2 GB card.

Chunk sizes are rounded down to a multiple of 6 so a segment's quad
never straddles two buffers -- the shader derives its corner from
vertex_index % 6 and every draw restarts that counter at zero.

Instance buffers are hoisted out so they are built once and reused
across vertex chunks instead of re-uploaded per chunk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:20:38 +03:00
PanosandClaude Opus 5 15425298b7 perf: measure widget-tree construction
Everything measured so far accounts for about 1 ms of a ~120 ms pan
frame: update is 5-28 ms at worst and usually far less, prepare 0.45 ms,
encode 0.5 ms. Meanwhile the GPU idles at 0-8%, encoding no scene at all
leaves latency unchanged, major page faults stay flat at 17 across forty
seconds of panning, and freeing 6 GB of swap changes nothing either.

So the time is in code that has never been timed. `view()` runs between
the input handler returning and `prepare` being called, rebuilds the
whole widget tree every frame, and is the largest unmeasured span in
that gap.

Reports only when it exceeds 1 ms, so a healthy frame stays quiet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:20:30 +03:00
PanosandClaude Opus 5 f1efc73cbe perf: stop rescanning every entity to fill the selection-filter menu
Pan and even plain cursor movement over the canvas ran at ~8 fps on a
large drawing. Nothing rendered was responsible: the GPU idled at 0-8%,
encoding no scene at all changed nothing, and vertices, pixels and draw
calls could each be cut without moving the number. `view` was the frame
-- 110 ms of it against 3-6 ms on a small drawing -- and inside `view`
it was the status bar.

The status bar builds the selection-filter menu, which asks for the
distinct entity type names in the layout. That call cloned the layout's
entire handle list (1 166 357 handles, ~9 MB), then did a map lookup, a
String allocation and a BTreeSet insert for each one. About 107 ms of a
110 ms frame, to produce roughly twenty names, for a menu that is only
visible when opened.

Cache the result against geometry_epoch and the layout block, since it
only changes when entities are added or removed, and make the scan
itself borrow the handle list rather than clone it and collect &str,
allocating only for the names it returns.

The immediate-mode UI rebuilds every widget on every mouse move, which is
fine as long as nothing in that path is O(document). This was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:20:30 +03:00
PanosandClaude Opus 5 2787dcc1a3 perf: instrument the file-open load path behind PERF
The 52 s silent stretch in a large-DWG open could not be attributed:
OpenTimings covered parse / purge / xref / caches, but the FINALIZING
phase -- prepare_open_geometry, which does the first full Model
tessellation and builds the interaction index -- was unmeasured and
reports only two progress ticks, so it was indistinguishable from parse
time.

Add finalize_ms to OpenTimings and a per-stage PERF breakdown:

  open-phases     parse / purge / xref / caches / finalize
  open-finalize   tessellation vs interaction index
  open-index      index build vs prepare_screen
  wires-build     sort cache, visible filter, block cache, classify,
                  memo hits, parallel tessellate, materialize, sort
  resident-post   scene markers, refedit fade, resident layout
  wire-upload     build_wire_buffers, which runs on the render thread

The wires-build line separates the parallel tessellate from the serial
materialize loop that deep-clones every WireModel while the memo retains
a second copy -- the leading hypothesis for both the stall and the
6.45 GB RSS.

No behaviour change. Every timer is gated on crate::perf::enabled(), so
nothing runs without PERF=1 or the in-app PERF panel. The localized
open-summary format string is left alone; changing it would rekey the
string across every locale, so the full breakdown goes to PERF instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:20:30 +03:00
ramox81 c9c5b35d29 Retain native PRESSPULL surface data across CAD formats 2026-09-04 15:53:15 +03:00
ramox81 9279384e9a Complete PRESSPULL selection, face editing and extrusion history 2026-09-04 15:31:30 +03:00
ramox81 8bd8ff3e33 Fix LOFT scripted picks and binary history persistence 2026-09-04 13:31:11 +03:00
ramox81 479bf87988 Complete LOFT commands, surfaces, previews and editable history 2026-09-04 13:03:33 +03:00
Karim Jerbi 886d5777d8 fix(ViewCube): WCS dropdown no longer appears to clip view cube
- enlarged render area for the viewcube container, and increased the gap between cube region and WCS dropdown
2026-09-04 10:21:33 +01:00
ramox81 8e3d851133 Fix SWEEP result counter formatting 2026-09-04 11:32:05 +03:00
ramox81 ad47b17603 Complete SWEEP command options and kernel integration 2026-09-04 11:15:44 +03:00
ramox81 44c94d1da1 Avoid surface meshes in live sweep previews 2026-09-04 10:24:56 +03:00