Commit Graph
757 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 dc26c8128c fix(otrack): reset tracking points after each committed point
OTRACK acquired points only ever cleared when a command ended, so a
multi-point command (LINE, etc.) accumulated tracking points across
every input, swamping the user with alignment options. Clear the
acquired points and live alignment vector each time a point is committed
to the active command, at all five point-accept paths. (issue #85)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 14:15:53 +03:00
Hakan SevenandClaude Opus 4.8 9de2760dee fix(selection): keep active selection on empty box/lasso
An empty box or lasso drag called deselect_all, discarding a selection
the user may have built up over several picks. Drop the empty-clear from
the lasso and two-click box paths so a stray empty drag leaves the
selection untouched; Esc remains the deliberate clear. (issue #83)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 14:07:57 +03:00
Hakan Seven b3b94b0130 Merge pull request #84 from HakanSeven12/claude/windows-msi-finish-screen-13as49
Add platform-specific file association registration for .dwg/.dxf
2026-06-12 13:55:51 +03:00
Claude db607cca1f fix(windows): register HKCU ProgIDs + Capabilities for portable .exe default-app support
For MSI installs the installer already writes HKLM Capabilities +
RegisteredApplications, so the "Set as default" dialog works.  For
portable .exe builds those registry keys are never created, so the
IApplicationAssociationRegistrationUI dialog silently does nothing.

register_handler() now also writes:
- HKCU\Software\Classes\Applications\OpenCADStudio.exe  (Open-with list)
- HKCU\Software\Classes\OpenCADStudio.DWG / .DXF        (ProgIDs + icon + open cmd)
- HKCU\Software\Classes\.dwg|.dxf\OpenWithProgids
- HKCU\Software\Open CAD Studio\Capabilities            (AssociationToasts shown)
- HKCU\Software\RegisteredApplications                  (Windows Default Programs)

This makes the "Yes, set as default" button work correctly for portable
builds on all supported Windows versions.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 10:54:34 +00:00
Claude 5ba7101022 feat(app): silently register as a .dwg/.dxf handler so portable builds show in "Open with"
Installed builds already register a handler (MSI ProgIDs, the Linux
.desktop MimeType, the macOS Info.plist), but the portable .exe and the
AppImage register nothing, so they never appear in the OS "Open with"
list. Add a best-effort, idempotent runtime registration that runs
silently on a detached thread at startup:

  * Windows — writes HKCU\Software\Classes\Applications\OpenCADStudio.exe
    (open command + FriendlyAppName + SupportedTypes .dwg/.dxf), which is
    what lists the exe in "Open with → Choose another app".
  * Linux — writes a user-level .desktop pointing at the running binary
    ($APPIMAGE when set) and refreshes the desktop database. Skipped when
    a system package already provides the desktop entry, so we never
    shadow a distro install.
  * macOS — no-op; .app bundles auto-register with LaunchServices.

Unlike the default-app prompt this changes no defaults and shows no UI;
it only makes the binary discoverable as a handler.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 08:00:18 +00:00
Claude a7d85fe148 feat(app): first-launch prompt to set Open CAD Studio as default for .dwg/.dxf
On the first launch that hasn't answered it yet, the app shows a small
dialog offering to become the default handler for .dwg and .dxf files.
The choice is persisted (default_assoc_prompted) so the prompt never
reappears.

The platform plumbing lives in io/file_association.rs:
  * Windows — opens the OS's own per-app default-programs dialog via
    IApplicationAssociationRegistrationUI::LaunchAdvancedAssociationUI,
    passing the "Open CAD Studio" RegisteredApplications name the MSI
    registers. Modern Windows guards the actual default behind a
    UserChoice hash, so this is the supported consent path.
  * Linux — xdg-mime default against the installed .desktop file, whose
    MimeType entries already cover image/vnd.dwg and image/vnd.dxf.
  * macOS — LSSetDefaultRoleHandlerForContentType binds the DWG/DXF UTIs
    declared in Info.plist to the app bundle id.

The work runs on a dedicated thread (the Windows dialog is modal) with
the result delivered back through a oneshot channel and surfaced in the
command line.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 07:50:41 +00:00
Claude e24447a70d feat(installer): register Open CAD Studio as a default-app candidate for .dwg/.dxf
Adds a DefaultPrograms component publishing Capabilities and a
RegisteredApplications entry under HKLM. This makes Open CAD Studio
appear in Settings > Default Apps and the shell "Open with" list, so
users can set it as the default handler for .dwg and .dxf files.

Modern Windows guards the actual default behind a per-user UserChoice
hash that an installer cannot forge, so this is the supported path to
becoming the default rather than silently overwriting the association.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 07:30:49 +00:00
Claude fd949b74c7 refactor(installer): split installer UI into its own ui.wxs
Moves the WixUI_Minimal UI and finish-screen launch checkbox out of
main.wxs into a dedicated ui.wxs fragment, referenced from main.wxs via
<UIRef Id='OpenCADStudioUI'>. Keeps the package/feature definition and
the UI concerns in separate files.

Updates the release workflow to compile both .wxs files (candle now
writes per-source .wixobj into a directory) and link both objects.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 07:28:04 +00:00
Claude 556989dbdb feat(installer): add finish screen with launch-app checkbox to Windows MSI
Adds WixUI_Minimal UI to the WiX installer so users see a proper
Welcome → Progress → Finish wizard. The finish screen includes a
pre-ticked "Launch Open CAD Studio" checkbox; clicking Finish with
it checked starts the application immediately after the installer closes.

Also updates the light.exe invocation in release.yml to include
-ext WixUIExtension, which is required to link the WixUI dialogs.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 07:24:36 +00:00
Hakan Seven 99f1a2679e remove perf roadmap 2026-06-12 09:57:10 +03:00
Hakan SevenandClaude Opus 4.8 9c7dd2fa6b ci(release): retry .dmg creation on transient hdiutil failure
hdiutil create intermittently fails with "Resource busy" on the hosted
macOS runner (stale mount / background indexer), failing an otherwise
successful build. Detach any leftover volume and retry up to five times
before giving up, then assert the image exists.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 02:14:05 +03:00
Hakan SevenandClaude Opus 4.8 71837895b0 feat(ui): right-edge side toolbar; drop the paper-space ribbon tab
Switching to a paper layout used to hijack the ribbon with a contextual
"Layout" tab. Replace that with a reusable right-edge vertical toolbar:
a single centred column of icon buttons floated over the canvas, built
from a flat ToolDef list and dispatching through the existing
RibbonToolClick path (ui::side_toolbar). Paper space now shows Viewport /
Page Setup / Export PDF there and leaves the ribbon on the user's tab;
the contextual layout tab and its three auto-switch hooks are removed.
The toolbar is generic infrastructure for future context action sets.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 02:07:26 +03:00
Hakan Seven c9071579a2 chore: release v0.5.3 v0.5.3 2026-06-12 01:48:34 +03:00
Hakan SevenandClaude Opus 4.8 b47e1bf706 fix(ribbon): load style/layer dropdowns from the document
The Annotate style dropdowns (text / dimension / multileader / table) and
the layer dropdown were hard-coded to "Standard" / "0" and never synced
from the opened document, so they showed placeholder values even with no
file open and stale values after closing one.

- Start the ribbon's style and layer lists empty instead of with a
  placeholder entry.
- Sync the style dropdowns from the document on file open and on tab
  new/switch/close (the layer sync already ran there).
- Guard sync_ribbon_layers / sync_ribbon_styles / sync_ribbon_from_selection
  for the Start (welcome) tab so all three dropdowns stay empty there.
- When a style family is absent (e.g. no table/multileader styles), fall
  back to empty rather than a non-existent "Standard".

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 01:46:17 +03:00
Hakan SevenandClaude Opus 4.8 2519d64907 fix(select): box/lasso adds to the selection (#83)
Box selection is a two-click window (corner, corner): the first empty
click armed the box AND cleared the selection, and the box completion
cleared again before applying its hits — so a second box dropped
everything the first one picked. Stop clearing on the arming click and
make the completion accumulate like individual picks (#47): a plain box
or lasso adds, Shift+box/lasso removes the enclosed entities, an empty
box/lasso (or Esc) clears.

Fixes #83

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 01:22:59 +03:00
Hakan SevenandClaude Opus 4.8 610f7f140d fix(trim): trim/extend against polyline boundaries
build_geos only turned Line/Arc/Circle/Ray/XLine/Ellipse/Spline into
cutting-edge geometry, so a polyline contributed no boundary and a line
could not be trimmed (or extended) against it — even though trimming the
polyline itself against lines already worked. Explode each polyline
(LwPolyline / Polyline / Polyline2D / Polyline3D) into its Line + Arc
segments and add them as boundaries, tagged with the polyline's handle so
the click-target self-exclusion still holds. Factored the per-entity Geo
build into geo_from_entity and exposed explode_polyline_segments.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 01:06:29 +03:00
Hakan SevenandClaude Opus 4.8 cea84ccb64 fix(edit): correct entity-pick hover previews on offset drawings
The trim/extend hover preview (on_hover_entity) gets the cursor in
offset-relative space but compares it against WCS document entities and
returns WCS preview wires. On a drawing with a non-zero world_offset the
preview highlighted the wrong segment and the wires were shifted off the
geometry by the offset. Pass the world-offset-corrected click to
on_hover_entity and shift its returned wires back to the offset-relative
frame the renderer uses. Point-creation previews keep the local cursor.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 01:00:34 +03:00
Hakan SevenandClaude Opus 4.8 14d047ce9a fix(edit): add world_offset to entity-pick clicks
The pick point handed to commands is in offset-relative (local) space,
matching the camera and point-creation commands. Entity-pick, tangent
and structure-pick handlers (TRIM / EXTEND / FILLET / OFFSET, …) instead
compare the click against WCS document entities, so on a drawing with a
non-zero world_offset (UTM-scale coordinates) the click landed ~1e6
units off the geometry — TRIM cut the wrong side or reported the click
as off the segment. Add world_offset back to the pick (model space only)
before passing it to those handlers, matching what the grip path already
does. Point-creation commands keep the local point unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:53:20 +03:00
Hakan SevenandClaude Opus 4.8 f6b894813e fix(zoom): fit 3D solids in ZOOM EXTENTS (fit_all)
ZOOM EXTENTS routes through fit_all, which built its bounds from the
wire set and returned early when there were none. A drawing of only 3D
solids has no wires, so ZOOM EXTENTS did nothing. Collect the
layout's mesh AABBs alongside the wires, keep going when only meshes
exist, and fold them into the fitted bounds.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:42:50 +03:00
Hakan SevenandClaude Opus 4.8 8eeb155bfe fix(zoom): include 3D solid meshes in model-space extents
ZOOM EXTENTS / auto-fit derived the drawing bounds from wire AABBs and
tessellated key-vertices only. 3D solids (Solid3D/Region/Body) render as
meshes with an empty wire path, so they contributed nothing and a
drawing containing only solids zoomed to nothing. Fold each mesh's
offset-relative XY AABB into the extents on both the wire-cache and
tessellate-fallback paths.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:09:05 +03:00
Hakan SevenandClaude Opus 4.8 5fa93b9cb0 fix(solid3d): stop drawing a placeholder line beside modeler solids
Solid3D / Region / Body have no truck wire conversion, so the wire path
fell through to the generic fallback, which returns a 1-unit placeholder
segment at the origin — a stray little line next to every 3D solid. Give
these types their own fallback that emits only the entity's pre-computed
edge wires (empty for binary SAB) plus an insertion snap, never the
placeholder.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:04:55 +03:00
Hakan SevenandClaude Opus 4.8 876474f5aa fix(solid3d): place ACIS solids by their body transform
ACIS keeps a solid's geometry in body-local space and records the world
placement in a `transform` record (3×3 affine + translation + scale).
The tessellator ignored it, so solids rendered at their local origin
instead of where the drawing puts them. Apply the body transform to the
mesh vertices (and rotate the normals) after tessellation.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 23:57:40 +03:00
Hakan SevenandClaude Opus 4.8 e68f95aff2 chore: bump acadrust — decode SAB Position tokens
Binary (SAB/ASM) solids no longer collapse every vertex to the origin,
so 3D solids from AutoCAD 2013+ render with real geometry.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 23:48:44 +03:00
Hakan SevenandClaude Opus 4.8 572415485e chore: bump acadrust — 3DSOLID AcDs geometry loading
Loads ACIS/ASM modeler geometry for 3DSOLID/REGION/BODY from the AcDs
data store (AutoCAD 2013+), so solids stored there now render instead of
coming through empty.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 23:38:31 +03:00
Hakan SevenandClaude Opus 4.8 9b1bd011ca feat(blocks): switch dynamic-block visibility states from a lookup grip
A block reference with a visibility parameter now shows a triangular
lookup grip at the parameter point. Clicking it opens a dropdown of the
named states (current one marked); picking a state toggles which of the
anonymous block's members are visible and rebuilds, so the reference
shows the chosen profile instead of being frozen on the saved state.

The state list is data-driven (owned strings from the file) and applying
it mutates other entities (the anonymous block's members), so it stays
separate from the static GripMenuAction grip-menu: a dedicated
VisibilityPopup and an app-level apply path. The anonymous block is a
parallel-ordered clone of its definition, so a member visible in the
definition's state maps to the same index in the inserted block; the
chosen visibility flags round-trip through the DWG writer.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 20:11:19 +03:00
Hakan SevenandClaude Opus 4.8 1f54b1241f chore: bump acadrust — dynamic-block visibility parsing
Brings in AcDbBlockVisibilityParameter / AcDbBlockRepresentationData
parsing and the dynamic_visibility_for_insert resolver, the foundation
for the upcoming visibility-state UI.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 19:44:53 +03:00
Hakan SevenandClaude Opus 4.8 f366aae33c fix(blocks): hide invisible sub-entities in block definitions
A dynamic block with a visibility-state parameter stores the geometry
for every state in one anonymous block and marks all but the active
state's entities invisible. The block-cache builder tessellated every
sub-entity regardless, so a visibility block rendered all variants
stacked on top of each other instead of the one selected profile.
Skip invisible sub-entities in both the render pass and the precision-
offset centroid pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 19:06:55 +03:00
Hakan SevenandClaude Opus 4.8 28a30de15d fix(wipeout): close rectangular fill loop so the mask stops bleeding
The GPU in_polygon ray-cast walks sequential boundary pairs and never
wraps last→first, so it needs an explicit closing vertex. The polygon
fill branch already appends one, but the rectangular branch returned a
bare 4-corner quad — the v3→v0 (left) edge went untested and the solid
mask bled past the boundary. Newly created wipeouts take this branch,
so the fill rendered wider than the clicked box while the border/grips
(drawn as wires) stayed correct. Repeat corner 0 to close the loop.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 18:10:47 +03:00
Hakan SevenandClaude Opus 4.8 ece5d50796 chore: bump acadrust to current main
Picks up the AC18 section-name fix (cut at first null), so DWGs whose
writer left garbage after the name terminator load their entities
instead of opening with zero. Also rolls in the intervening upstream
ellipse-WCS and reflecting-transform fixes already on main.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 17:28:21 +03:00
Hakan SevenandClaude Opus 4.8 cf817ff9eb fix(io): purge near-collinear arcs that hang load
A 35-unit arc sweeping 6.5e-7 rad has arc length 2.3e-5 — past the
arc-length floor — yet start/mid/end bow off the chord by only ~2e-12.
truck's 3-point circle_arc fit then returns a near-infinite radius and
parameter_division subdivides without bound, hanging the load. Gate on
the sagitta (r·(1−cos(sweep/2))), the true measure of the fit's
conditioning, so long thin arcs survive and sub-precision ones drop.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 17:10:00 +03:00
Hakan Seven 8f39e77a0d chore: release v0.5.2 v0.5.2 2026-06-11 14:41:24 +03:00
Hakan SevenandClaude Opus 4.8 d09614be39 fix(ellipse,mirror): WCS ellipse rendering + delegate hatch MIRROR upstream
Two follow-ups from the boundary-arc convention audit:

- ELLIPSE is a WCS entity in DXF (codes 10/11 are world coordinates,
  unlike ARC/CIRCLE). The renderer ran center and major_axis through the
  arbitrary-axis OCS, which misplaced any ellipse whose normal isn't Z-up
  — e.g. the (0,0,-1) result of a mirrored-block explode. Read them as
  WCS; pairs with acadrust 569bd4d, which stores WCS on its explode
  paths. Z-up ellipses are unaffected (the conversion was an identity).

- The MIRROR command's hand-rolled hatch closure swapped boundary-arc
  angles itself — only valid for ccw arcs and ignorant of the stored
  mirrored-angle / wrap-encoded-sweep conventions. It now builds a
  reflection Transform (new reflection_about_xy_line helper) and
  delegates to acadrust's transform_hatch, the single source of truth
  for those conventions. Covered by a Line→Arc→Line continuity + sweep
  regression test.

Note: full effect of the ellipse change lands once the acadrust pin is
bumped to 569bd4d; until then only the (previously broken) non-Z-up
corner differs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 09:03:18 +03:00
Hakan SevenandClaude Opus 4.8 80059922c5 fix(mirror): negate polyline bulges in the MIRROR command
The MIRROR command's LwPolyline / Polyline2D closures reflected the
vertices but left the bulges untouched, so every curved segment of a
mirrored polyline bowed to the wrong side. A reflection reverses the
side a bulge arc bows to — negate it alongside the vertex reflection
(same convention acadrust applies on its reflecting-transform paths).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 01:17:47 +03:00
Hakan SevenandClaude Opus 4.8 6d60ef45d5 fix(hatch): wrong-way giant boundary arcs in blocks — bump acadrust
Hatches inside blocks (xref_Plan-Sade) drew some boundary arcs as their
near-full-circle complement, covering huge areas — in the pattern clip
when rendered through the INSERT, and in both pattern and boundary wires
after EXPLODE.

Root cause (acadrust 64efaf8): the stored sweep of a boundary arc is
always forward and AutoCAD encodes a wrap through 0 by writing the end
angle ABOVE 2π (5.81 → 6.64 for a 0.83 rad arc). transform_hatch
normalized angles into [0, 2π), turning such arcs into their 5.46 rad
complement on ANY insert pass-through — even a pure translation, which
is why the same hatch looked fine outside the block. The fix transforms
only the start angle and carries the stored sweep over unchanged
(invariant under rotation and mirror); RAW vs exploded edge angles are
now bit-identical on the reporting file.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 01:01:56 +03:00
Hakan SevenandClaude Opus 4.8 14b5067e25 fix(hatch): mirrored-block arcs — bump acadrust, document angle convention
Hatches inside mirrored INSERTs (x_scale = -1, e.g. the *U blocks in
xref_Plan-Sade) drew some boundary arcs swept the wrong way, covering
huge complementary regions — in the pattern clip before EXPLODE, and in
both pattern and boundary wires after (the exploded entity bakes the
broken angles).

Root cause was in acadrust's transform_hatch, not the renderer: DXF
stores CW (ccw=false) boundary-arc angles MIRRORED (verified against
AutoCAD output by endpoint continuity — mirrored interpretation gives
Δ=0.0 against adjacent edges), and the transform stored geometric angles
after flipping the direction flag. The legacy (TAU-θ) sampling flip in
arc_signed_span is exactly right for file data and stays; its comment now
records the verification so the convention isn't "fixed" away again.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 00:41:16 +03:00
Hakan SevenandClaude Opus 4.8 25715d1b03 fix(grips): compute grip positions in f64 so they sit on the wire
Grip markers drew visibly offset from the entity on large-coordinate
(UTM-scale) drawings: every Grippable impl cast the f64 entity coordinates
to f32 BEFORE the world-offset subtraction, and at 1e7 magnitude an f32
holds ~0.9 drawing units of rounding error. The wires subtract the offset
in f64 first (precision-preserving), so the grips landed up to ~1 unit
away from the geometry they belong to.

GripDef.world is now DVec3: producers fill it straight from the f64
entity data, refresh_selected_grips subtracts the world offset in f64,
and only the small offset-local result is cast to f32 for screen-space
projection (grips_to_screen / find_hit_grip) and drag state.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 00:04:37 +03:00
Hakan SevenandClaude Opus 4.8 ed29933d87 fix(hover): drop the geometry-only pick cache — it served stale culled wires
hit_test_wires got a cache keyed on geometry_epoch alone (462d9b4), but
the underlying wire set IS view-culled (wires_for_block culls to
view_world_aabb). After zoom-in + edit, the cache pinned the set culled
to that close-up view; zooming back out, blocks that had been off-screen
were missing from the pick set, so hover/selection stopped working on
them until the next geometry change.

Revert to the camera-keyed entity_wires_arc for picking — it re-culls
when the view changes. The actual 0.5.1 hover regression fix (hatch
rebuild/re-upload on rollover) stays.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 23:41:51 +03:00
Hakan SevenandClaude Opus 4.8 462d9b4b66 perf(hover): don't rebuild/re-upload hatches on every rollover (0.5.1 regr)
Selected hatches carry a tint (#71), so 0.5.1 keyed both the hatch model
cache and the static GPU buffer re-upload on `selection_generation`. But
that counter also bumps on hover, so moving the cursor over a new entity
rebuilt EVERY hatch model and re-uploaded the hatch + face3d buffers each
time — the large-file hover stutter introduced between 0.5.0 and 0.5.1.

The hatch tint depends only on the *selected set*, never on hover. Key
both on an order-independent signature of `selected` instead, so a
rollover keeps the caches warm; an actual select/deselect still rebuilds.

Also cache the picking wire set on `geometry_epoch` alone (separate from
the camera-keyed render cache) so a pan/zoom no longer re-tessellates the
whole un-culled model on the next mouse move.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 21:20:45 +03:00
Hakan SevenandClaude Opus 4.8 14e85171b3 perf(hover): stop scanning every wire on hover for large drawings
Two per-hover O(N) costs on big drawings, both keyed off the cursor:

- The selection/hover xray overlay rebuilt by scanning ALL wires and
  string-parsing each `name` into a handle on every hover change. Index
  handle → wire slots once per wire upload instead, so the overlay gathers
  only the highlighted entity's wires (O(highlighted)).

- click_hit (runs on every mouse move) projected every vertex of every
  wire to screen. In the flat top-down ortho view it now pre-rejects a
  wire by its world-space AABB (four corners projected, so a Z-rotated
  plan view is still correct) and only projects points for wires actually
  near the cursor. Tilted/orbit views fall back to the full test, so no
  pick is ever missed; covered by a unit test.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 21:01:12 +03:00
Hakan SevenandClaude Opus 4.8 ebae67166e docs(roadmap): collapse completed items to headings, drop 1.2
Strip the body of every ✅ DONE item down to its heading, mark 3.5
(glyph-stroke batching) done — fonts already parse once via OnceLock and a
text emits one grouped WireModel, not one-per-stroke — and remove 1.2
(background XREF resolve). Priority list updated to the remaining items.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 20:28:01 +03:00
Hakan SevenandClaude Opus 4.8 0ca1e58b1f fix(io): purge zero-length point-collapsed splines that hang load
sample_layout_03/04.dwg would not open: a periodic 9-control-point spline
with every control point pinned at the origin (zero extent) reaches truck's
parameter_division, which never converges on a zero-length curve and hangs
the first-frame tessellation. The spline passed every existing guard
(finite points, no knots, degree 3). Reject splines whose control-point
extent is sub-precision (< 1e-6 on all axes).

Verified: all five sample_layout DWGs now load + tessellate (03/04 went
from a hang to ~356 ms; 01/02/05 unchanged, no valid spline dropped).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 20:27:45 +03:00
Hakan SevenandClaude Opus 4.8 e5d0c3950b feat(styles): persist current text/dim/mline style change to DWG
DWG stores the current text/dim/multiline style as a header handle; the
writer keeps a valid stored handle and otherwise falls back to Standard,
so a Set Current change (which only updates the name) was lost on DWG
save. Treat the current-style names as the source of truth and re-resolve
the header handles from them at save time, alongside the table/multileader
variable-dictionary sync. Now every current-style choice round-trips
through DWG, not just DXF.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 19:33:56 +03:00
Hakan SevenandClaude Opus 4.8 b3c1c8b4cb feat(styles): write current table/multileader style back to DWG
On save, mirror the current table/multileader style from the header into
the variable dictionary (CTABLESTYLE / CMLEADERSTYLE DICTIONARYVAR
entries) so a Set Current choice round-trips through DWG, which stores
them there rather than in the header. Done once in save_as_version (the
shared clone point), so every save path and both formats stay in sync.
No-op when the document has no variable dictionary (fresh files).

Verified: mutate the entry, write+read a real DWG, value survives.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 19:29:17 +03:00
Hakan SevenandClaude Opus 4.8 2044755418 feat(styles): read current table/multileader style from DWG too
DWG has no header field for the current table / multileader style — they
live in the variable dictionary as DICTIONARYVAR entries keyed CTABLESTYLE
/ CMLEADERSTYLE. Resolve them on load via the document's dictionaries and
set the header names, so the green ✓ marks the right style for DWG files
as well (DXF already worked via the header vars). A missing entry leaves
the value untouched, so there is no risk to the binary header parse.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 19:17:07 +03:00
Hakan SevenandClaude Opus 4.8 31cdd6632f chore: bump acadrust to current main (upstream sync)
Picks up the fork's merge of upstream acadrust (geodata decode, DXF
roundtrip newline sanitization, DWG alignment) on top of the current
table/multileader style work.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 19:09:46 +03:00
Hakan SevenandClaude Opus 4.8 e65a7a6d67 feat(styles): seed current table/multileader style from the file
Bump acadrust to pick up the new $CTABLESTYLE / $CMLEADERSTYLE header
variables, then on load seed the ribbon's active table style and the
tab's active multileader style from the document header so the ✓ marks
the style the file actually declares (DXF). Set Current writes the header
field as the round-trip source of truth, and the style-manager staging
snapshot now covers it. sync_ribbon_styles honours the active table style
instead of always falling back to the first one.

DWG still leaves these at "Standard" until acadrust parses them from the
binary header.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 19:05:07 +03:00
Hakan SevenandClaude Opus 4.8 b3ba3a53d1 feat(styles): green ✓ for current style + resolve it from DWG files
- The current-style marker is now a green ✓ in its own fixed-width column
  so names stay aligned; rendered as a separate coloured glyph rather than
  baked into the label string.
- Resolve the current text / dimension / multiline style from the handle
  the DWG header stores into the name the app reads (fix_current_style_names
  on load). DXF already provides these as names ($TEXTSTYLE / $DIMSTYLE /
  $CMLSTYLE); DWG only stores handles, so the marker previously always fell
  back to "Standard". Only overrides when the handle resolves.

Note: table and multileader current styles are not surfaced by the file
reader (no header name/handle exposed), so those markers still default to
the app's active selection until acadrust exposes them.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:56:13 +03:00
Hakan SevenandClaude Opus 4.8 885bacd0f6 feat(styles): mark the current style with a ✓ in the list
Every style manager now shows a leading ✓ next to the current style
(current text/dim style, active table/multileader/multiline style), with
the other rows padded so names stay aligned. The marker replaces the
multileader/multiline-only ◀ and is wired for all five managers by
threading the current-style name into each view.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:45:48 +03:00
Hakan SevenandClaude Opus 4.8 de4eb31003 feat(styles): make style managers transactional — persist only on Apply
A style manager is now a staged transaction. When it opens, a snapshot of
the style tables, style objects and current-style pointers is taken. Every
New / Copy / Delete / Rename / Set Current / property edit mutates the
document live so the dialog previews the result, but the tab is not marked
dirty, nothing is pushed to undo, and the drawing is not rebuilt.

- Apply commits: one undo entry (restoring the pre-edit state), the tab is
  marked dirty, the drawing rebuilds, and the baseline is reset so further
  edits stage afresh.
- Closing the window without Apply discards: the opening snapshot is
  restored, so none of the changes persist.

Side effects (dirty / undo / bump_geometry) were removed from every style
edit handler and routed exclusively through the commit. Multiline's Apply
is a structural-only commit since its editor has no editable properties.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:38:32 +03:00
Hakan SevenandClaude Opus 4.8 65a25033bc fix(styles): make double-click inline rename actually fire
The list row wrapped a `button` inside the `mouse_area`. The button
captured the press, so the parent `mouse_area` never saw the second
click and `on_double_click` never fired — double-clicking a style name
did nothing.

Drop the inner button: the row is now a plain `mouse_area` carrying both
`on_press` (select) and `on_double_click` (start rename), with selection
shown via the container background. Focus the rename field on start so
the user can type immediately.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:10:09 +03:00