Commit graph

686 commits

Author SHA1 Message Date
Michael Flynn
82993bc1db Add demo_plugin and plugin host integration tests
Minimal in-tree add-on (DP_HELLO) validates registry, ribbon merge, and try_dispatch. Five new lib tests; EntryKind gains PartialEq for test helpers.
2026-06-09 13:52:15 -04:00
Michael Flynn
9f35456205 Strip Storm Sewer from plugin-host branch (framework only)
Removes storm_sewer module and stormsewer crate so this branch is suitable for upstream PR. Plugin host, docs, and CadCommand acquisition hooks remain.
2026-06-09 13:43:06 -04:00
Michael Flynn
e46701456f Add plugin host architecture and Storm Sewer add-on package
Generic plugin runtime (src/plugin/, HostSession, per-tab plugin_state, command router). QGIS-style add-on layout with plugin.toml and single PluginRegistration. Storm Sewer refactored as reference consumer with dispatch.rs, catchments, LandXML, headless tests (43 passing). Core update.rs decoupled from storm_sewer via CadCommand object-pick hooks. Docs: plugin-architecture.md, plugin-template, PR and issue-78 reply drafts.
2026-06-09 13:41:21 -04:00
Michael Flynn
1199895f0e Storm Sewer: make placement click-driven so it always works
Reports showed structure placement could stall if command-line text entry
didn't register (ribbon-invoked commands, dynamic-input/focus interactions).

Make a canvas click ALWAYS place a structure, using whatever values were typed
so far (defaults for the rest) — so dropping an inlet works regardless of typed
entry. If typing works, enter invert/rim/area/C first, then click to place with
those values. Pipes are pick-first: click START then END structure; the pipe
commits on the second click with the current diameter/n.

Drop the early focus_cmd_input() return in the dispatch arms (the normal
dispatch tail already focuses the command line and records last_cmd).

15 module tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:06:18 -04:00
Michael Flynn
057de504b2 Storm Sewer: fix prompt flow — collect typed values before the click
The structure/pipe commands took a viewport interaction first and then asked
for typed values. But a point-pick result (NeedPoint) does not re-focus the
command line, so after the click the typed digits went nowhere and Enter hit
on_enter -> Cancel ("Command cancelled.").

Reorder both commands to the OFFSET-style flow: collect typed values first
(command line focused at dispatch and kept focused through text input), then
take the viewport click last and commit from it.

- PlaceStructure: enter invert/rim/area/C, then click the location.
- PlacePipe: enter diameter/n, then click START and END structures; commit
  from the second pick using the click points (connectivity stays exact via
  structure handles).
- Dispatch arms focus the command line so the first prompt is typeable.
- Reverts the update.rs inject_picked_entity allow-list change (the pt-based
  pipe no longer needs the picked entity).

15 module tests updated and green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 08:42:47 -04:00
Michael Flynn
1e68219b43 Storm Sewer: interactive in-app network — snapping, tracked objects, analyze the drawing
Replaces the .ssn-file workflow with an interactive one driven by the canvas:

- Structures (SS_INLET / SS_JUNCTION / SS_OUTFALL) prompt invert/rim/area/C on
  the command line and commit a CIRCLE tagged with STORMSEWER_STRUCT XDATA
  [kind, invert, rim, area, C]. Outfalls skip area/C.
- SS_PIPE picks a START and END structure (entity-pick), snapping its endpoints
  to their centers, prompts diameter/n, and stores connectivity (the two
  structure handles) in STORMSEWER_PIPE XDATA [diameter, n, from, to].
- SS_ANALYZE / SS_REPORT / SS_PROFILE rebuild the network from the drawn
  entities (data.rs::network_from_entities) and analyze it directly; SS_ANALYZE
  overlays flow/HGL labels without redrawing the geometry.

Because the data lives on the entities as XDATA, the network round-trips to
DWG/DXF. One additive line in src/app/update.rs adds SS_PIPE to the
inject_picked_entity allow-list so the command can read the picked structure's
center for snapping.

15 module tests cover the XDATA round-trip, analyze-from-drawing, and the
command step machines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:29:14 -04:00
Michael Flynn
9b44eb6400 Add Storm Sewer module: storm-drain network analysis (Rational + Manning + HGL)
Adds a "Storm Sewer" ribbon tab backed by a new in-repo `stormsewer` engine
crate (vendored under crates/). The engine implements standard public-domain
methods — Rational-method peak-flow accumulation, Manning circular-pipe
hydraulics, time-of-concentration / IDF intensity, and an HGL backwater pass
with junction losses — with 24 unit tests and no external dependencies.

Module (src/modules/storm_sewer/):
- Drafting: SS_INLET / SS_JUNCTION / SS_OUTFALL place structure markers and
  SS_PIPE chains pipe runs (interactive CadCommands).
- Analysis: SS_ANALYZE / SS_REPORT / SS_PROFILE open a .ssn network file, run
  the engine, draw the plan and HGL long-section, and print a report.
- SVG icons; SS_* commands registered for command-line autocomplete.

Integration is additive: build.rs auto-discovers the module and one dispatch
block is added to src/app/commands.rs. 7 module integration tests.

See src/modules/storm_sewer/INTEGRATION.md for the design and the optional
follow-up (drive analysis from canvas geometry via XDATA).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 17:16:30 -04:00
Hakan Seven
99b7ab1176 perf(scene): incremental tessellation for MOVE / COPY / ERASE
Extends the Phase 2.2 per-entity memo to the common modify commands, which
still did a full bump_geometry (whole-model re-tess + block rebuild):

- transform_entities (MOVE / ROTATE / SCALE / MIRROR) marks each
  transformed handle dirty, then bump_geometry_no_blocks;
- erase_entities drops the deleted handles from the memo and
  bump_geometry_no_blocks;
- copy_entities' new handles are natural memo misses, so just
  bump_geometry_no_blocks.

All three are top-level operations (including on inserts) that can't change
a block definition, so keeping the block cache is sound. Each now
re-tessellates only the touched entities and reuses the memoized wires for
the rest, matching the grip / line-commit path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:16:44 +03:00
Hakan Seven
eb89717b47 perf(scene): per-entity tessellation memo for the render path
Phase 2.2. wires_for_block_culled re-tessellated every visible entity on
any geometry bump, so a single-entity edit cost a full model re-tess
(~30ms baseline after the block_epoch fix).

Add a per-entity memo (tess_memo: Handle -> Arc<Vec<WireModel>>) on the
culled Model render path. It's guarded by a hash of the tessellation
parameters (tol / view / anno / world_offset / bg / entered viewport); a
mismatch (zoom, layout switch) clears it. The render path now classifies
the visible set against the memo, materializes hits and tessellates only
the misses in parallel, then stores the misses back.

- bump_geometry clears the memo (structural change — safe default);
- incremental edits drop just the changed handle via mark_entity_dirty
  and bump with bump_geometry_no_blocks: line commit (new handle is a
  natural miss) and grip commit / Esc-cancel (mark the dragged handle).

The hit-test (view_aabb == None), paper and per-viewport paths bypass the
memo so their differing cull parameters don't thrash it. A single-entity
edit now skips the per-entity geometry math for the entire unchanged set;
the Vec assembly + batched-buffer upload stay O(N).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 15:46:31 +03:00
Hakan Seven
7f94a9d66a chore(release): bump to 0.5.0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 15:35:08 +03:00
Hakan Seven
3b2f8431a3 perf(scene): keep block cache across non-block edits
The block-definition tessellation cache (BlockCache::build — every block
defn) was keyed on geometry_epoch, so every edit rebuilt all block defns.
On a block-heavy drawing that turned the ~30ms visible-wire re-tess into a
~400ms spike at line-commit, grip first-move and grip release.

Key the block cache on a separate block_epoch. bump_geometry bumps both
geometry_epoch and block_epoch (safe default — xref, block create,
explode, REFCLOSE, undo, open all keep rebuilding blocks). Operations that
provably can't change a block definition now call bump_geometry_no_blocks,
which bumps only geometry_epoch so the visible wires re-tessellate against
the reused block cache:

- add_entity for a non-Insert/Block entity (line, arc, text, hatch, …) —
  scene.add_entity only ever routes to model/paper space, never block
  content;
- grip drag hide / commit / Esc-cancel — moving a top-level entity or an
  insert never edits a block defn.

Line-commit and grip start/end drop from ~400ms to the ~30ms baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 15:24:02 +03:00
Hakan Seven
e37dfa5890 perf(scene): draw preview/grip overlay from a separate GPU buffer
Command-preview / interim / grip-drag wires were appended into the main
wire buffer (all_wires), so every drag frame re-packed and re-uploaded the
entire batched base set — O(N) per move even though only the small overlay
changed.

Split them out: build_primitive keeps `wires` as the cached base only and
passes the overlay as a separate `preview_wires` Arc. The base keeps the
stable tile content id (no per-frame nonce), so its buffer stays resident
and is not re-uploaded during a drag. The overlay uploads to its own small
gpu_preview_wires buffer each frame and draws on top in the wire pass
(same pipeline, same order/depth). The dragged entity still renders
highlighted via its selected-path tessellation.

With the no-re-tess changes, a grip / command drag now costs a single
small overlay upload per move and nothing else.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:57:38 +03:00
Hakan Seven
55022ae60e feat(grip): Esc cancels an in-progress grip drag
The grip drag mutates the document live (so grips and the properties panel
track the cursor), which meant Esc left the entity at its dragged position.
Back up the edited entity when the drag starts (the same first-move point
that hides it for the overlay preview) and, on Esc, restore that backup
before un-hiding and re-tessellating — reverting the entity to where it was
when the drag began. A normal release drops the backup and keeps the
dragged geometry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:44:31 +03:00
Hakan Seven
8df9bef6f8 perf(scene): grip drag previews the entity instead of re-tessellating
Phase 2.2 (partial). Dragging an entity's grip called scene.apply_grip on
every mouse move, which bumped geometry and re-tessellated the whole model
per move (plus an O(N) clone of every wire to exclude the edited one from
snapping). On a large drawing that's the 30ms->400ms spike, per frame, for
the duration of the drag.

apply_grip no longer bumps. The drag flow now:
- first move: hide the edited entity from the base tessellation (one
  re-tess) and record it in grip_preview_handle;
- each move: apply the grip to the doc (no bump) and show the entity as a
  one-entity overlay preview (set_preview_wires) — the base is a cache hit;
- commit on release / Esc: un-hide, drop the preview, one final re-tess.

Because the edited entity is hidden, it's already absent from
hit_test_wires, so snapping runs against the cached set directly — no
per-move clone and no self-snap. Two re-tessellations per drag instead of
one per move.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:33:08 +03:00
Hakan Seven
ccb9bb832d perf(scene): stop re-tessellating on preview/interim overlay updates
Phase 2.2 (partial). set_preview_wires / clear_preview_wire /
set_interim_wire called bump_geometry, so every rubber-band frame of a
drawing command invalidated the wire cache and re-tessellated the whole
model — the same 30ms->400ms spike as selection, but while drawing.

Preview / interim wires are an overlay appended to the cached base wire
set in build_primitive; they are not part of the tessellation cache, so
they no longer bump geometry. The base model-tile tessellation stays a
cache hit (static camera, no geometry bump), and the overlay still forces
a GPU wire re-upload through the has_overlay content-id path. Real commits
(commit_entity / transform / copy) bump geometry on their own, so finished
edits and preview-clear still render correctly. Snap/hit-test via
entity_wires_arc also stops re-tessellating per preview frame.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:21:32 +03:00
Hakan Seven
db8c5dc43f feat(scene): rollover highlight for the entity under the cursor
Now that selection/hover are decoupled from tessellation, highlighting the
entity under the cursor only refreshes the GPU xray overlay — no
re-tessellation — so a general rollover is cheap. On an idle ViewportMove
(no active command, no drag) hit-test at the cursor and set_hover_highlight
the result; clear it during commands/drags and on ViewportExit so it
doesn't stick over the ribbon. hit_test_wires is the cached entity-wire
Arc, so the per-move cost is just the existing click_hit projection — the
same the command-hover path already pays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:15:23 +03:00
Hakan Seven
ff9f49511b perf(scene): decouple selection from wire tessellation
Picking an entity called bump_geometry, which invalidated the wire cache
and re-tessellated the entire model just to repaint one entity — 30ms to
400ms on a large drawing (visible as a tess-ms spike on the PERF HUD).

Selection highlight is no longer baked into tessellation. wires_for_block_
culled now feeds tessellate_entity an empty selection set, so wires are
always base-coloured and the wire cache is selection-independent. The
highlight is applied in the GPU xray overlay: prepare filters the cached
wires by the live `selected ∪ hover` handle set, recolours them to
WireModel::SELECTED, and batches them — rebuilt only when a new
selection_generation (or the underlying wire content) changes.

Pure select / deselect / hover paths now call bump_selection instead of
bump_geometry, so a pick refreshes just the overlay — no re-tessellation,
no main wire re-upload (geometry_epoch and wire_content_id are unchanged).
Geometry edits, undo and file-open still bump_geometry and rebuild
everything. Grips/properties refresh through their existing app paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:04:22 +03:00
Hakan Seven
0d18e0d1bf perf(scene): batch the selection-overlay wire pass too
Follow-up to 3.3. The selection xray pass still built one buffer + one
draw call per selected wire, so select-all over a large drawing issued a
draw per entity. That pass applies neither scissor nor mesh-edge skip, so
all selected wires merge into one order-preserving run via from_run.
Removes the now-dead WireGpu::new / build per-wire constructors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:44:12 +03:00
Hakan Seven
c6e17deb7e perf(scene): batch the wire pass into per-run draw calls
Phase 3.3. upload_wires built one GPU instance buffer + one draw call per
WireModel — tens of thousands on a large drawing, a major per-frame CPU /
driver cost. Merge maximal runs of consecutive wires that share scissor +
mesh-edge state into one concatenated instance buffer each (WireGpu::
from_run); the existing draw loop then issues a single draw per run. A 2D
model (uniform None scissor, no mesh edges) collapses to one buffer + one
draw.

Runs are consecutive, never globally regrouped, so the sorted wire order
is preserved exactly — depth-biased overlap and alpha blending both
resolve in submission order, so the image is unchanged. from_run
guarantees instance order (index-ordered collect + sequential flatten).
The WireInstance layout, shader, scissor projection and draw loop are
untouched; only buffer packing changed. Selection overlay stays per-wire.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:35:18 +03:00
Hakan Seven
65eba3887a perf(scene): memoize Face3D wire split across frames
build_primitive ran split_face3d_wires every frame — for each wire a
string-parse + document handle lookup + clone, to separate Face3D wires
from the rest — even when a pan had just reused the tessellation. The
split is content-only, so memoize it by the Model-tile wire content id:
a pan-reuse (same id) reuses the split, and the common no-overlay frame
returns the cached `other` Arc with zero clone. Preview/interim frames
clone once to append. Non-tile paths split inline as before.

Completes the cheap-pan path: a pure pan now reuses the CPU
tessellation, the Face3D split, and the GPU wire upload — only the
view_proj uniform and per-frame scissors recompute.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:23:52 +03:00
Hakan Seven
0f40cfa054 perf(scene): skip wire GPU re-upload when only the camera moved
Phase 3.2 (wire pan path). Wire vertex buffers are world-space, so a
camera move changes only the view_proj uniform (uploaded every frame),
never the wire data — yet upload_wires re-ran on every camera_generation
tick, re-packing vertices and re-writing the GPU buffer on each pan.

Stamp every Model-tile re-tessellation with a monotonic content id
(WIRE_CONTENT_GEN), reused when a pan reuses the tessellation. The
pipeline records the resident buffer's id and skips upload_wires when the
incoming id matches. The gate is pulled out of the (epoch, camera_gen)
block so a preview/interim wire change still uploads even with a static
camera; non-tile paths and overlay frames force a fresh id, preserving
current behaviour. Monotonic id sidesteps the ABA hazard a freed/realloc'd
Arc pointer would carry.

Pairs with the pan tessellation reuse: a pan now skips both the CPU
re-tessellation and the GPU wire upload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:16:44 +03:00
Hakan Seven
d74cb891f9 perf(scene): reuse model-tile tessellation across pure pans
Phase 3.1 (partial — pan). The Model-tile wire cache keyed on the exact
camera hash, so every pan re-tessellated the whole tile even though pan
changes neither the curve tol nor the culled set (beyond a shift). Re-key
on (geometry_epoch, pan_invariant_hash, tessellated_region):

- pan_invariant_hash covers rotation + wpp (tol) but excludes the pan
  target, so a pure pan keeps it stable;
- the wires were culled to a 1.25x-margin region, recorded with the entry;
- a hit now also requires the tile's current visible rect to still fit
  inside that region, guaranteeing every on-screen wire is present.

Result: panning within the existing margin reuses the tessellation (tess
ms -> ~0 on the PERF HUD) with no missing geometry. Zoom, orbit and edits
change the signature/epoch and rebuild as before; the cull margin is
unchanged so miss cost — and zoom behaviour — is identical. GPU still
re-uploads per camera_generation (diff upload is Phase 3.2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:06:12 +03:00
Hakan Seven
5bb6a92235 perf(io): parallelize corrupt-entity detection scan
The purge scan tests every entity with is_entity_corrupt, whose
per-vertex finite/extent checks on large polylines dominate. Detection
is pure and read-only, so gather entity refs in one pass and test across
cores with rayon; removal stays serial (remove_entity needs &mut doc).
Cuts the "purge" span in the open-time breakdown on polyline-heavy files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 08:58:39 +03:00
Hakan Seven
44eea23c3f perf(scene): parallelize block AABB post-pass
Extends Phase 2.1. compute_block_aabbs resolved each defn's union AABB
serially via defn_aabb_recursive, which only reads the finished defns map
and re-walks shared nested defns with no memoization — real cost on
block-heavy drawings. Split into a parallel read phase (per-name resolves
fan out over rayon) and a serial write-back phase. BlockCache is Sync, so
sharing &self across the read phase is sound.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 08:52:16 +03:00
Hakan Seven
9b112ce12c feat(scene): PERF frame-budget HUD for wire re-tessellation cost
Phase 5.3 (CPU tessellation slice). Add a `PERF` command that toggles an
overlay on the active viewport showing the cost of the most recent wire
re-tessellation: ms, wire count, geometry epoch. Anchored top-left via
position_canvas_overlay.

Timing is captured at the wire-cache miss paths (model_tile_wires_arc,
paper_sheet_wires_arc) and stored in Cells on Scene, read directly by the
UI view (Scene is single-threaded / Rc-based, so build_primitive and view
share a thread). Reads ~0 ms on a warm cache, so it isolates the exact
work a miss costs — the slice 2.2 / 3.1 / 3.3 each move, making them
PR-to-PR comparable.

GPU upload/draw/GPU-wait spans (need wgpu timestamp queries) remain open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 01:07:17 +03:00
Hakan Seven
7111922852 perf(scene): skip re-tessellation on hover of selected entity
Phase 3.1 (partial). set_hover_highlight bumped the geometry epoch on
every hover-target change, forcing a full model re-tessellation. The
hover handle is folded into the tessellation highlight set as
selected ∪ {hover}, so hovering an already-selected entity contributes
nothing — the effective set and every resulting WireModel are identical.
Guard the bump on the effective highlight contribution actually changing;
hovering over or between selected entities is now free. The field is
still updated so hit-test / UI state stays current.

The full camera/selection-from-tessellation cache split (3.1 / 2.2) is
left open: highlight colour is baked into WireModel.color across several
tessellation sites, so decoupling it needs running-app verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:58:35 +03:00
Hakan Seven
16050909f9 perf(scene): parallelize block-definition build
Phase 2.1. build_defn stores nested INSERTs as by-name references and
never expands them at build time, so each block defn depends only on the
read-only doc — the builds are independent, not topologically ordered as
the roadmap assumed. Replace the serial loop with a rayon
par_iter().collect(). render_style_for / tessellate already run under
rayon in build_derived_caches, so thread-safety is established.

compute_block_aabbs stays serial (resolves nested refs, cheap post-pass).
Cuts first-frame block tessellation on insert-heavy drawings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:46:21 +03:00
Hakan Seven
997538a67a feat(io): open-time breakdown log
Phase 5.2. Time each open phase — parse, purge, derived-cache build on
the loader thread, plus xref resolve on the UI thread — and emit a
breakdown line on open completion:

  parse 1240ms · purge 80ms · caches 340ms · xref 60ms · total 1820ms

Makes open-time regressions (and the wins from 1.1 / 2.4) visible at a
glance. Timings ride back on DerivedCaches; total is wall time from the
Open click to post-xref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:41:46 +03:00
Hakan Seven
70702d063f perf(hash): swap std HashMap/HashSet for rustc-hash FxHash
Phase 4.1. Handle / u64 keys dominate the hot maps (block_defn,
hatch / image / mesh caches, draw-depth map, viewport wire caches);
the default SipHash is wasted work on integer keys. Alias the std
types to rustc_hash::FxHashMap / FxHashSet crate-wide so every map and
set — including the cross-module draw-depth map shared between scene
and the GPU pipelines — uses the faster hasher consistently.

Mechanical: no behavioural change. Constructors moved from ::new() to
::default() (Fx variants carry no RandomState). Build + 13 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:35:53 +03:00
Hakan Seven
3098523b8d perf(io,scene): drop redundant purge walk, fuse world-offset scan
Phase 1.1: resolve_xrefs now purges corrupt entities inline as it
merges each xref and returns the dropped count, removing the second
full-document purge_corrupt_entities walk in the FileOpened handler.
Every xref-bearing open saves one O(N) entities() pass.

Phase 2.4: split compute_world_offset into offset_prep / offset_centroid
/ world_offset_from_centers; centroid accumulation now rides the existing
cache-handle walk in build_derived_caches. Two O(N) entity walks collapse
into one, with identical MSPACE-membership filtering preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:32:39 +03:00
Hakan Seven
b466747074 chore(release): v0.4.9
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:14:47 +03:00
Hakan Seven
59a8573311 docs: add status bar items roadmap
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:14:41 +03:00
Hakan Seven
bb54cd034f fix(statusbar): cycling-list hover highlight on row re-entry
Row exit now clears the preview only when the highlight still points at
that row. Moving between rows can fire on_enter before the previous
row's on_exit, so the unconditional clear wiped a freshly highlighted
row (notably when returning to the first row). Guarding the clear fixes
the highlight not reappearing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:05:15 +03:00
Hakan Seven
a9228b9021 feat(statusbar): highlight cycling-list object on row hover
Add a scene hover_highlight handle that renders an entity with the
selection colour without joining the real selection. The cycling list
box sets it on row enter / clears on exit, so hovering a row glows the
matching object in the drawing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:00:52 +03:00
Hakan Seven
bcbda65746 feat(statusbar): selection cycling shows a pick list, adds to selection
Replace the repeat-click cycling with a list box: when a click lands on
two or more overlapping objects (cycling on), a small list of the
candidates opens at the cursor. Picking a row adds that object to the
current selection (accumulate); clicking outside dismisses it. A single
object under the cursor falls through to the normal click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:56:24 +03:00
Hakan Seven
ac042c5dbf feat(statusbar): selection cycling for overlapping objects (SC pill)
Add a click_hits_all hit-test and an SC pill that, when on, makes
repeated clicks at the same spot step through the overlapping objects
there (nearest first) instead of accumulating the selection. The whole
cycling path is gated behind the toggle, so default picking is unchanged
when it is off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:44:39 +03:00
Hakan Seven
f7eac09b55 feat(statusbar): selection filtering by object type (FILTER pill)
Add a per-type selection filter to the scene: the FILTER pill opens a
checklist of the entity types in the layout, and unchecking a type
excludes it from interactive picking (click, box and lasso selection all
honour it). The pill lights up while any type is filtered out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:39:48 +03:00
Hakan Seven
834780ec5d feat(statusbar): quick properties floating panel (QP pill)
When the QP pill is on, a compact floating panel appears at the canvas
top-left on selection, reusing the docked panel's editable section rows.
Toggleable from the status-bar customization menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:31:36 +03:00
Hakan Seven
66f7d24bc9 feat(statusbar): transparency-display toggle (TPY pill)
Add a transparency_enable uniform read by the wire shader: when the new
TPY pill is off, every line is forced opaque; when on, baked entity
transparency shows through. Toggling is a uniform write, no retessellate
(same mechanism as the lineweight-display toggle). Pill is toggleable
from the status-bar customization menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:25:09 +03:00
Hakan Seven
28ec360255 feat(statusbar): isolate action menu on the pill and right-click
Clicking the ISO pill now opens a small menu with Isolate Objects, Hide
Objects and End Isolation instead of ending isolation directly; rows
that don't apply (no selection / nothing hidden) are dimmed. The same
actions are added to the viewport right-click menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:17:21 +03:00
Hakan Seven
e0e7897952 feat(statusbar): isolate / hide objects with status pill
Add a hidden-entity set to the scene that the tessellation visibility
test skips, so hidden objects neither render nor hit-test. New commands
ISOLATEOBJECTS (show only the selection), HIDEOBJECTS (hide the
selection) and UNISOLATEOBJECTS (restore). An ISO status pill lights up
while anything is hidden and restores everything on click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:51:23 +03:00
Hakan Seven
243a0bc923 feat(statusbar): drawing-units pill with INSUNITS picker
Add a Units pill that shows the drawing's current INSUNITS value and
opens a picker to change it (Unitless, mm, cm, m, km, in, ft, mi, yd).
Toggleable from the status-bar customization menu like the other pills.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:06:42 +03:00
Hakan Seven
d829f80a04 feat(statusbar): customizable pills with coordinates and clean screen
Add a customization menu (far-right handle) that lists every status-bar
pill with a check mark; toggling a row shows or hides that pill, and the
choice persists across sessions in the config dir.

New pills:
- Coordinates: live cursor readout (re-adds the model-space world offset
  so it shows true drawing coordinates), updated on every move rather
  than only while a command is active.
- Clean Screen: hide the ribbon and side panels for a full canvas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:14:04 +03:00
Hakan Seven
edc8cbc969 feat(scale): drive scales from the drawing's scale list
Read the model-space annotation scale from the standard CANNOSCALE /
CANNOSCALEVALUE header variables instead of a user-variable hack, and
populate both the status-bar scale picker and the viewport Scale
property purely from the drawing's ACAD_SCALELIST — no built-in
fallback set. Xref-derived scales (`_XREF` suffix, `name|` prefix)
are filtered out so only the host drawing's own scales appear.

Bumps the patched acadrust to the commit that adds CANNOSCALE/
CANNOSCALEVALUE read/write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:18:01 +03:00
Hakan Seven
b5ce376d5c
Merge pull request #58 from mojtabakarimi/ci/azure-trusted-signing
ci(windows): sign exe and installer with Azure Trusted Signing
2026-06-04 14:37:13 +03:00
Mojtaba Karimi
3163a5ee75 ci(windows): sign exe and installer with Azure Trusted Signing
Sign the bare .exe before the MSI is built (so the packed copy carries
the signature) and the linked .msi afterwards, via
azure/artifact-signing-action with SHA256 digests and Microsoft's
RFC3161 timestamp server. Credentials come from the AZURE_* repository
secrets.

Also fix the MSI version fallback: workflow_dispatch runs use the
branch name as ref_name, which is not a valid numeric ProductVersion,
so candle failed on any non-tag run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:54:40 +02:00
Hakan Seven
695c6536c4 docs+ci(macos): ad-hoc sign bundle, add Homebrew cask, clarify install
- release.yml: ad-hoc codesign the .app before packaging so a quarantined
  download avoids the "app is damaged" Gatekeeper verdict; emit the cask
  version + sha256 to the job summary each release for easy tap bumps.
- packaging/homebrew: Homebrew cask + tap/usage notes; install via
  --no-quarantine (the actual Gatekeeper bypass for an unsigned app).
- README: split macOS install into Homebrew and manual .dmg paths with
  the xattr / Settings "Open Anyway" fallbacks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:45:58 +03:00
Hakan Seven
8699a730bf chore(release): v0.4.8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:40:16 +03:00
Hakan Seven
ef29d19ae8 fix(fillet/chamfer): polyline arc preview, edge highlight, polyline chamfer
Three polyline issues in the FILLET/CHAMFER commands:
- The polyline preview wire ignored bulge, so a filleted corner previewed
  as a straight line instead of the arc. lwpoly_pts now tessellates bulge
  segments via BulgeArc (same as the entity render).
- Hovering a polyline edge didn't highlight it: the first-pick hover only
  handled Line/Arc. Both commands now highlight the nearest polyline
  segment on hover.
- CHAMFER didn't work on polylines at all (Line-only). Added a SecondPoly
  step + chamfer_lwpoly_corner: pick two adjacent segments of one polyline
  to cut the shared corner back by the two distances with a straight edge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:20:59 +03:00
Hakan Seven
d8c60c32d4 fix(3d): revolve axis and OBJ import use the Z-up world, not stale Y-up
The truck B-rep pipeline is Z-up (solids extrude/raise along Z, profile
wires convert pass-through), but two spots still assumed a Y-up world:
- REVOLVE swapped the axis/origin to (x, z, y), so the revolve axis sat
  in the XZ plane while the profile sat in XY → wrong solid. Use the
  axis/origin straight through.
- OBJ import kept Y-up coordinates as-is on the (now Z-up) viewport, so
  imported meshes lay on their side. Rotate +90° about X on import
  (positions + normals): (x, y, z) → (x, -z, y).

EXTRUDE (sweep along +Z) and the box/cylinder primitives were already
Z-up and unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:03:03 +03:00