Commit Graph
1154 Commits
Author SHA1 Message Date
Karim Jerbi d99b480eae Merge branch 'HakanSeven12:main' into main 2026-06-28 18:52:03 +01:00
Karim Jerbi ca6239a32d chore: mark fill_tris_low unpaired sites with FIXME
dimension.rs (x2), multileader.rs (x3), table.rs (x1) emit fill_tris
without a corresponding fill_tris_low. These will trip the debug_assert_eq!
added in the preceding commit when the entity appears inside a block
definition. Each site is marked with a FIXME referencing block_cache.rs:1397
and the correct fix pattern (split_ds on each vertex).
2026-06-28 18:46:02 +01:00
Karim Jerbi 7da8fbe316 fix(block-cache): preserve per-wire colour and fill precision in cached blocks
Three related bugs in tessellate_sub_local and expand_defn:

1. Wire folding lost per-segment colour. Multi-wire tessellator output
   was concatenated (NaN-separated) into a single LocalWire using the
   first wire's colour. Inline \C/\c MTEXT colour overrides inside block
   definitions rendered in the entity colour instead of the override colour.
   Fix: tessellate_sub_local returns Vec<LocalWire>; each wire from the
   tessellator becomes its own LocalWire with its own colour.

2. fill_tris_low was never forwarded. The double-single precision path
   was silently degraded on cached wires. Fix: fill_tris_low forwarded
   from WireModel through to LocalWire and BatchEntry. debug_assert_eq!
   added at emit site to catch future invariant violations.

3. Fill-only wires hit the text LOD ladder. MTEXT outline/fill pairs
   produce separate LocalWires; the fill-only wire was routed through
   emit_text_baseline/emit_greeked_text which ignore fill_tris (invisible
   double-draw). Fix: is_fill_only field on LocalWire bypasses the LOD
   ladder. Also fixes StyleKey::fill_only being hardcoded false at both
   emit_wire call sites (latent batch misclassification).
2026-06-28 18:45:57 +01:00
Karim Jerbi 06bb6b54ff fix(text): canonicalise TTF filename font refs in MTEXT inline overrides
resolve_font previously returned &str, passing raw stems like "arial" verbatim to Face::resolve. System font lookup requires the canonical family name ("Arial"), so inline \f/\F overrides from block-nested MTEXT were silently unresolvable and fell through to no geometry.

Change resolve_font to return Cow<'a, str>:
- no override -> Borrowed(base), zero allocation
- LFF built-in -> Borrowed(font), zero allocation
- system font resolved -> Owned(canonical), one allocation
- unresolvable -> Borrowed(base), falls back to style font

All 4 call sites updated to &font_name (&Cow<str> derefs to &str).

Adds two unit tests covering the fallback and TTF-stem-to-family-name paths. Adds integration test verifying block-nested MTEXT produces geometry and correctly separates outline/fill wires.
2026-06-28 18:44:38 +01:00
Hakan SevenandClaude Opus 4.8 c23b956861 feat(io): default DWG save to 2018; bump acadrust handle-counter fix
- Save As now defaults the version dropdown to DWG/DXF 2018 (latest)
  instead of the loaded file's version; the selected version is written
  as-is (removed the source-version fallback for version-locked data).
- Pin acadrust 55a2caa: allocate_handle respects header.handle_seed as a
  floor, fixing the corruption where opening a file and saving it
  silently overwrote existing XRECORD/DICTIONARY objects (handle reuse)
  and AutoCAD refused the result.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-28 20:31:30 +03:00
Hakan SevenandClaude Opus 4.8 0ca3954b5d feat(commands): integrate standard CAD commands and fix behavior gaps
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).

New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
  (DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
  integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
  FINDNONPURGEABLE

Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
  (correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
  interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
  text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count

Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-28 19:27:35 +03:00
Hakan SevenandClaude Opus 4.8 e04b580a70 chore(deps): bump acadrust to R2018 cross-version conformance fixes
Pins acadrust 0831995 (HakanSeven12/acadrust main): cross-version DWG
writer/reader conformance so R2004->R2018 saves open in AutoCAD for
LAYER, VPORT and ATTRIB objects (22/25 previously-discarded objects in
the Civil3D test file now open clean). MLEADER cross-version still WIP.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-28 19:26:06 +03:00
Hakan SevenandClaude Opus 4.8 9928cbf0e5 feat(view): zoom-window ribbon button and bare ZOOM command
Bare `ZOOM` had no dispatch arm — only the sub-keyword forms (ZOOM
EXTENTS/IN/OUT/WINDOW/…) were handled, so typing `ZOOM` fell through to
an unknown-command error. It now starts the interactive window zoom.

Add the missing Zoom Window ribbon button: the command existed but had no
tool()/icon, so it was reachable only by typing. Wire it into View ▸
Navigate with a new dashed-rectangle magnifier icon.

Closes #210

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-28 16:57:00 +03:00
Hakan SevenandClaude Opus 4.8 ccd249d40f fix(plugin): bound the runner handshake read with a deadline
The handshake verification used a raw blocking `recv`, the one unbounded
read in the spawn path: `accept` is guarded by `spawn_timeout` and every
host->runner `call` by `call_timeout`, but a process that won the accept
race and then sent nothing — or a runner that died mid-handshake — would
hang the host forever.

Route the handshake through the existing `recv_with_deadline` helper so
the first frame is bounded too (marking the process dead on timeout), and
reduce `verify_runner_handshake` to a pure token check on the received
message. Tests updated to match.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-28 10:41:01 +03:00
Sebastian 04240862e1 Adding runner handshake 2026-06-28 10:41:01 +03:00
Hakan SevenandClaude Opus 4.8 d209e7bbb5 fix(selection): clear selection when an interactive command ends
Editing tools (MOVE, COPY, STRETCH, ROTATE, OFFSET, SCALE, MIRROR) and
other interactive commands left their working set selected after they
finished, so the next edit silently reused it — confusing especially for
copy-producing tools. Drop the selection centrally in apply_cmd_result
when a command ends (active_cmd Some->None), excepting Relaunch/Dispatch
which hand a deliberate selection to a chained command. Pure-selection
commands run without an active command, so their result is preserved.

Closes #207

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-28 10:28:23 +03:00
Hakan SevenandClaude Opus 4.8 d1efa4ea7c feat(io): preserve source DWG version for files with version-locked data
Civil3D/AEC objects (and raw MLEADER/Surface records, EED) can only be
written losslessly in their source DWG version. On save, fall back to the
source version when the chosen version is an incompatible encoding family
(acadrust CadDocument::has_version_locked_data), with a command-line
notice, across every save path (Save, Save As, save-before-close/quit)
via a shared dwg_save_version helper. Default the Save-As format dropdown
to the loaded file's version so a round-trip preserves it.

Picks up acadrust DWG-writer conformance fixes (3dbae05) so a Civil3D
R2004 drawing round-trips through OCS and opens in AutoCAD with no errors.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-28 02:46:13 +03:00
Hakan SevenandClaude Opus 4.8 12f4264183 feat(dimension): bake full arrow shapes, arrow-fit, fixed-length ext lines
Completes the bake↔live dimension parity from the audit:

- terminators now reproduce the style's actual arrow block: dim_metrics
  resolves arrow1/arrow2 via arrow_from_block (DIMTSZ tick, DIMBLK /
  DIMBLK1+DIMBLK2 per DIMSAH, else closed-filled), and dim_terminator bakes
  each kind (None, filled/blank triangle, tick, open, dot, origin, box,
  datum) as Line + SOLID — no more every arrow degrading to a filled triangle
- short dimensions flip their arrows outside with stubs (DIMSOXD honoured)
  when the arrowheads don't fit between the extension lines (DIM-ARROWS-OUTSIDE)
- DIMFXLON fixed-length extension lines via ext_endpoints (DIM-FXL)

Refs #181. Remaining (annotative viewport scale at non-1:1, the ~1deg angular
arrowhead-tangent cosmetic, and DIMTFILL text-break gap) are niche/cosmetic
and tracked separately.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 20:43:14 +03:00
Hakan SevenandClaude Opus 4.8 58da76ff70 fix(dimension): baked *D block matches live render (gaps, arrows, leaders, text)
Brings the save-time *D-block bake into parity with the live render so a
dimension no longer changes when the file is saved and reopened (the reload
draws from the block). Per a bake-vs-live audit:

- text is now the live render's own Text/MText entity (alignment, MText
  codes, text style, alignment point) — fixes every label shifting ~half a
  glyph on reload
- extension lines carry the DIMEXO start gap + DIMEXE overshoot, and slant
  by ext_line_rotation (DIMEDIT Oblique)
- terminators follow the style: oblique tick when DIMTSZ>0, else a closed
  filled SOLID arrow (was a generic open stroke)
- angular dims bake the swept arc + tangent terminators, flush extension
  lines (matching live exactly)
- radius/diameter bake the leader leg; diameter runs edge-to-edge through
  the centre; centre mark honours negative DIMCEN (centre lines)
- per-element colours/lineweights (DIMCLRD/E/T, DIMLWD/E), DIMSE/DIMSD
  suppression, DIMDLE tick overshoot
- consistent arc sweep at exactly 180 degrees

Refs #181. Remaining (custom DIMBLK arrow shapes, DIMFXLON, arrow-outside
fit, annotative viewport scale) tracked separately.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 20:29:35 +03:00
Kevin GriffinandClaude Opus 4.8 73eabc3aaa fix(shortcuts): honor Cmd as the accelerator modifier on macOS (#202) (#206)
Keyboard accelerators (copy/paste/save/undo/new/open) were gated on
`Modifiers::control()`, which on macOS matches only the physical Control
key. Mac users pressing Cmd+C/V/S/Z got nothing, contrary to platform
convention and every other Mac app.

Switch the gate to `Modifiers::command()`, which iced resolves to the
Logo (Cmd) key on macOS and Ctrl on Windows/Linux. On non-mac targets
this expands to exactly `control()`, so behavior there is unchanged.

Also make the Keyboard Shortcuts reference window display the
platform-correct modifier name (Cmd vs Ctrl) so the help matches the
keys the user actually presses.

Closes #202

Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-06-27 19:33:08 +03:00
Hakan SevenandClaude Opus 4.8 ddf576765b fix(view): drop non-standard camera View record; restore from VPORT
OCS wrote an app-specific "OpenCADStudio_Camera_Model" entry to the VIEW
table and preferred it when restoring the model view. That record polluted
the file for other CAD programs and its DXF round-trip was broken, so a
drawing saved in TOP reopened from DXF at an isometric-ish angle.

The model view now persists and restores entirely via the standard *Active
VPORT entry (view direction / target / twist), which round-trips natively in
both DWG and DXF. The custom View record is no longer written; existing ones
in older files are left untouched. Paper space already used the standard
sheet viewport and keeps a read-only fallback for very old files.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 19:27:23 +03:00
Hakan SevenandClaude Opus 4.8 959dd66e93 fix(dimension): land DXF round-trip fixes (re-pin acadrust)
Re-pins acadrust to the dimension DXF round-trip fixes (rotation/text/oblique
degree<->radian units, radial/angular/ordinate group codes, ordinate X/Y bit)
and drops the OCS-side fix_dxf_dimension_rotations dimension arm, which now
lives in the acadrust DXF reader (keeping it here would double-convert).

Completes the dimension audit (Batch 5). Refs #181.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 18:15:14 +03:00
Hakan SevenandClaude Opus 4.8 0d42d57927 feat(dimension): implement QDIM, fix DIMSPACE, honest DIMBREAK/DIMJOGLINE
Batch 4 of the dimension audit (command features).

- QDIM now works: the relaunched QDIM_PLACE stage decomposes the selected
  geometry into endpoints, picks the axis from their spread (not Z), and
  builds a continuous chain of linear dimensions at the picked line. Was a
  no-op that errored with 'Unknown command'. (DIM-007/008/032)
- DIMSPACE steps parallel dimensions along the in-plane perpendicular
  instead of Z (which never moved the dim line). (DIM-021)
- DIMBREAK / DIMJOGLINE no longer report success while doing nothing — they
  state honestly that they are not yet implemented (need a break-gap / jog
  field on the dimension model). (DIM-019/020)

Refs #181.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 17:56:01 +03:00
Hakan SevenandClaude Opus 4.8 3c5aa4453a fix(dimension): bake↔live consistency, edits, baseline/continue, grips
Batch 1-3 of the dimension audit (19 issues). The dominant class was the
baked *D block diverging from the live render: a reloaded dimension renders
from that block, so on save+reopen it silently changed.

Bake now matches the live render:
- text value/format via the shared style-resolved path (DIMDEC/DIMLFAC/
  DIMPOST/units/<>), correct height + rotation ladder, suppression
- angular dims bake their swept arc (no longer collapse to a V)
- diameter draws edge-to-edge through the centre (live + bake), centre mark
- radius/linear/aligned bake arrowheads
- group-10 (base.definition_point) synced from the edited geometry so an
  edited dim no longer shifts on reload

Editing / properties:
- aligned no longer corrupts ext_line_rotation when a coordinate is edited
- DIMTEDIT pins the moved text (text_user_positioned)
- angle properties shown/edited in degrees
- non-linear text respects the user-positioned gate

Commands / geometry:
- DIMBASELINE stacks outward (not toward the points) and its preview no
  longer tilts over non-level points
- DIMCONTINUE/DIMBASELINE inherit the base dim's text rotation (UCS chains)
- angular extension lines reach the arc (no gap)
- text grip sits on the text for auto-placed dims instead of the origin

Refs #181. (acadrust DXF round-trip + QDIM/DIMBREAK/DIMJOGLINE/DIMSPACE/
DIMEDIT to follow.)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 17:47:50 +03:00
Hakan SevenandClaude Opus 4.8 b691854e7a feat(viewport): RMB 1st=Enter, 2nd=menu (no setting)
Replaces the opt-in `rmb_enter` setting (and `RMBENTER` command) with
always-on behavior. While a command is active, the first viewport
right-click acts as Enter (commit / close) and a second consecutive
right-click opens the context menu instead. Idle right-click still opens
the menu; right-drag still orbits.

The cycle is tracked per tab (`right_click_entered`) and restarts on any
other interaction — a left-click pick, a new command, an orbit, or
leaving the viewport — so the next right-click is Enter again.

Refines #185.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 16:32:40 +03:00
Hakan SevenandClaude Opus 4.8 098585bb2d feat(viewport): RMB=Enter option + timed right-click
Adds a configurable right-mouse-button behavior without disturbing orbit
or the context menu.

- New persisted setting `rmb_enter` (command `RMBENTER [ON|OFF]`, default
  off): when on, a right-click while a command is active acts as Enter
  (commit/close); when idle it still opens the context menu. Right-drag
  always orbits, so nothing is lost.
- Right-click vs orbit is now time-based: orbit engages only after the
  button is held >=150ms and the pointer moves, so a quick right-click is
  no longer swallowed by slight pointer jitter (was an 8px distance gate).
- The right-click Repeat menu now lists commands from every source
  (command line, ribbon, context menu, shortcuts) via a unified
  recents list recorded at the dispatch choke point, instead of only
  typed commands.

Closes #185

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 16:16:08 +03:00
Hakan SevenandClaude Opus 4.8 63de137106 feat(cmdline): Enter runs closest command match
Typing a partial command (e.g. `bac`) and pressing Enter with no active
command ran into an "Unknown command" error. Now the top autocomplete
match is highlighted and Enter runs it (`BACKGROUND`), matching standard
DWG command-line behavior.

The fallback only fires when no command family matches the typed verb, so
complete aliases that resolve through a dispatch arm but are absent from
the autocomplete registry (`LT`, `ZO`, …) still run as typed. Programmatic
dispatch (ribbon, plugins, headless automation) is unaffected.

Closes #198

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 14:45:51 +03:00
Hakan SevenandClaude Opus 4.8 3def2f2034 feat(statusbar): scrollable layout tabs + layout-list dropdown (#199)
Keep the right-side status-bar tools from being pushed off the bar when a
drawing has many layouts: the layout tabs now live in a horizontal scrollable
that only takes the space left between the menu and the tools, with ‹ › arrows
(and the mouse wheel) to scroll them.

Repurpose the leftmost hamburger — which previously fired a no-op MENU command
— to open a dropdown listing Model and every layout (scrollable, current
highlighted); picking one switches to it, an outside click dismisses it.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 14:24:47 +03:00
Hakan SevenandClaude Opus 4.8 4116b85af0 chore(release): bump to 0.6.8
Co-Authored-By: Claude Opus 4.8 <[email protected]>
v0.6.8
2026-06-27 12:23:51 +03:00
Hakan SevenandClaude Opus 4.8 ff23abee01 feat(start): replace info cards with a clickable intro video
Swap the two "Free and open source" / "Community driven" text cards on the
start page for the intro-video thumbnail with a play badge; clicking it opens
the video in the system browser (new Message::OpenUrl → sys::open_url). The
bundled thumbnail Handle is built once via OnceLock — Handle::from_bytes mints a
fresh unique id per call, so rebuilding it per view re-decoded and re-uploaded
the JPEG every frame and the image only appeared after a long delay.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 12:21:14 +03:00
Hakan SevenandClaude Opus 4.8 495b13ec4f refactor(viewport): migrate model-space tiling to iced pane_grid
Replace the custom model_tiles tiling with iced 0.14 pane_grid as the layout
source of truth, while paper-space floating viewports are left untouched (they
overlap/free-place, which pane_grid cannot model).

Each Model pane is its own shader widget rendering its camera into the pane
bounds; the renderer Primitive carries a base_slot so several shader widgets
share the type-keyed MultiPipeline storage on disjoint slots. A second pane_grid
of transparent mouse_areas (plus on_resize) layers above the crosshair overlay
to receive input — the overlay's Hidden cursor interaction otherwise levitates
the cursor and starves any layer beneath it. The active pane follows the cursor
(hover-activate), and grid / crosshair / UCS icon render per pane while the
ViewCube and render-mode picker stay on the active pane only.

Split / close / VPORTS presets and the VPort table round-trip now drive
pane_grid (guillotine reconstruction on load). Adds drag-to-swap via a controls
bar handle with a ghost-card preview, visible pane dividers from the pane_grid
split regions, a default model background of RGB(33,40,48), and removes all the
superseded custom-tile code.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 11:50:11 +03:00
Hakan SevenandClaude Opus 4.8 8bdf5228d2 refactor(viewcube): shrink cube 30%, compact controls, centre UCS picker
Reduce the ViewCube 30% (VIEWCUBE_PX 120→84) and tighten the nav region
(NAV_INSET_F 2.2→2.0) so the home / roll / nudge controls hug the cube in the
corner instead of floating in a large dead area, with sizes scaled to keep them
from overlapping. Bump the home and roll buttons back up slightly, shift the
UCS picker down below the region, and give it a fixed width so it centres
exactly under the cube centre.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 01:56:17 +03:00
Hakan SevenandClaude Opus 4.8 edcf24e324 feat(ucs): interactive UCS icon with origin-snap grips, world-fixed compass
The UCS icon now anchors to the projected UCS origin when it is on-screen
(falling back to the bottom-left corner), and becomes a selectable object:
hover highlights it, a click shows draggable grips, and dragging the origin
grip relocates the UCS while the X/Y tip grips rotate it. Drags snap to
geometry/grid through the existing snapper, and the new UCS is persisted on
release. The icon stays selectable while parked in the corner.

Also make the ViewCube compass (N/E/S/W) world-fixed: the cube body still
reorients with the active UCS, but the cardinals project through a camera-only
rotation and snap in world frame, so the directions no longer spin with the UCS.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 01:12:45 +03:00
Hakan SevenandClaude Opus 4.8 a502379048 refactor: group ui/ statusbar and window subdirs
Continue tidying the flat ui/ root:

- ui/statusbar/: statusbar.rs becomes statusbar/mod.rs (its
  crate::ui::statusbar::* paths are preserved) and absorbs
  statusbar_config.rs + statusbar_menu.rs.
- ui/window/: the standalone manager/dialog windows move here —
  about, layout_manager, page_setup, plugin_manager, shortcuts,
  layers, update_notice, open_progress. The crate::ui::LayerPanel
  re-export is repointed so callers are unchanged.

Call sites repointed to the new paths; no compat re-exports beyond the
existing LayerPanel one. ui/ root drops from 21 to 10 flat files with
five subdirs (ribbon, style, popup, statusbar, window).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:52:26 +03:00
Hakan SevenandClaude Opus 4.8 9b8ee94c0c refactor: rename mislabeled scene files to match contents
Two names collided with the pick subsystem and misdescribed their files:

- scene/hittest.rs -> scene/camera_ops.rs. Its contents are camera/view
  operations (zoom, fit-all, named-view restore, camera<->document sync,
  per-tile grid/snap), not hit-testing — the real screen-space hit-test
  lives in scene/pick/hit_test.rs.
- scene/pick/selection.rs -> scene/pick/selection_state.rs. It holds only
  the SelectionState struct; the name clashed with scene/selection.rs
  (the Scene selection operations).

Callers are unaffected (the crate::scene::SelectionState re-export path is
unchanged). No behavior change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:45:16 +03:00
Hakan SevenandClaude Opus 4.8 1cd7aaed4e refactor: move parallel tessellation fns into scene/convert/
scene/tess.rs held the free parallel-tessellation functions
(tessellate_entity, lod_stub_wire, entity_aabb, …) that the giant-file
split had left at the scene root. They belong with the rest of the
tessellation code, so move the file to scene/convert/tess.rs alongside
tessellate.rs / tess_util.rs and re-point the re-export.

Leaves the scene/ root as a consistent set of `impl Scene` method
groups; the free-function outlier is gone.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:39:43 +03:00
Hakan SevenandClaude Opus 4.8 9cd0479047 refactor: group ui/ style+popup dirs, rename cmd_result driver
- Group the flat src/ui/ dir: move the 9 style editors into ui/style/
  (dimstyle, mleaderstyle, mlstyle, plotstyle, textstyle, tablestyle,
  point_style, style_list, style_manager) and the 6 popups into
  ui/popup/ (cycle, isolate, scale, selection_filter, snap, units).
  Call sites repointed to crate::ui::style::/crate::ui::popup::; no
  compat re-exports. ui/ root drops from 36 to 21 files.
- Rename app/cmd_result.rs -> app/command_driver.rs: the file is the
  StepInput->feed_command driver, not the CmdResult type (which lives
  in command.rs). Name now matches contents.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:34:10 +03:00
Hakan SevenandClaude Opus 4.8 7cb666801f refactor: replace build.rs module codegen with a static registry
The core ribbon registry no longer needs build-time generation. Replace
the generated all_modules() (included from OUT_DIR) with a hand-written
static list in src/modules/registry.rs, listing the seven built-in tabs
in display order.

Trim build.rs down to its only remaining job: embedding the Windows
executable icon via winresource (issue #107). The external plugin system
is unaffected — plugin::registry still appends dynamically-loaded add-on
tabs on top of the core list.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:19:20 +03:00
Hakan SevenandClaude Opus 4.8 415dbe08d4 refactor: move resource/IO root modules under io/
Relocate three root-level modules into io/, where they belong by concern:

- update_check.rs -> io/update_check.rs (its own doc-comment notes it
  mirrors how crate::io wraps blocking calls in async).
- patterns.rs, linetypes.rs -> io/ (standard resource catalogs parsed
  from asset files and populated into the document — IO-shaped work).

Fix the moved files' include_str! paths (one level deeper), add the
mod declarations to io/mod.rs, drop them from lib.rs/main.rs, and
re-point all crate:: references to crate::io::. No behavior change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:06:40 +03:00
Hakan SevenandClaude Opus 4.8 bbdbd0850b chore: remove dead extend stub, flatten single-file module dirs
- Delete src/modules/draw/modify/extend.rs (a 1-line placeholder with no
  tool()/logic; `extend::` was referenced nowhere) and its `mod extend;`.
- Flatten single-file module dirs to the modern flat style:
  src/command/mod.rs -> src/command.rs, src/snap/mod.rs -> src/snap.rs.
  Parent `mod command;`/`mod snap;` are unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:00:26 +03:00
Hakan SevenandClaude Opus 4.8 bddc050b3e refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.

- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
  dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
  with thin delegating arms; the 66 fattest arm bodies move to topic
  methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
  mspace,project,selection,modify,group_layer,preview}. The impl Scene
  body is split across files via inherent-impl-per-file; struct, ctor,
  fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
  The 233-arm dispatch match becomes source-ordered per-family
  dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.

Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 22:51:29 +03:00
Hakan SevenandClaude Opus 4.8 2baabdf4a0 feat(viewport): reorder model controls, add close-vport button
Reorder the model-tile control chip to grid, grid snap, render mode,
vertical split, horizontal split. Add a close button (shown only with
more than one model tile) that merges the active pane into its
longest-contact neighbour.

Divider drags now clamp each side to the minimum tile size, so dragging
a boundary to the screen edge no longer collapses a pane — closing is
done with the button instead.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 21:15:00 +03:00
Hakan Seven 9c03ae2b4d Merge pull request #196 from KevinGriffin-new/feat/background-presets
feat(background): named presets for BACKGROUND (WHITE/BLACK/GRAY/…) (#188)
2026-06-26 20:44:20 +03:00
Hakan Seven f45d85c7ec Merge pull request #195 from KevinGriffin-new/feat/persist-background
feat(background): persist viewport background across sessions (#188)
2026-06-26 20:42:38 +03:00
Hakan Seven b1e4330d8d Merge pull request #194 from KevinGriffin-new/fix/dist-f64-precision
fix(dist): measure in f64 so snapped endpoints stay precise (#193)
2026-06-26 20:39:13 +03:00
Kevin GriffinandClaude Opus 4.8 e22afea0a0 feat(background): named presets for BACKGROUND (WHITE/BLACK/GRAY/…) (#188)
Builds on the background-persistence change: let BACKGROUND take a single
named-colour token in addition to `<r> <g> <b>`, so a common dark/light
background is one word instead of a remembered triplet:

    BACKGROUND WHITE | BLACK | GRAY|GREY | DARKGRAY|DARKGREY | LTGRAY

Colour parsing moves into `parse_background_color`; the persisted-default
and recolor paths are unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 10:27:54 -07:00
Kevin GriffinandClaude Opus 4.8 66fc30429c feat(background): persist viewport background across sessions (#188)
The BACKGROUND command set a per-tab colour that was lost on restart and
didn't apply to new drawings, so there was no way to make a non-black
background stick. Persist the chosen model/paper background in UserSettings
(settings.txt) and re-apply it on launch and to every drawing tab created
afterward.

- settings.rs: persist `bg_color` / `paper_bg_color` as 0–255 RGB triplets.
- App gains `default_bg_color` / `default_paper_bg_color`; current_settings /
  apply_settings round-trip them, and apply_settings pushes the restored
  colour onto existing tabs. New/opened/replaced tabs call `apply_bg_default`.
- BACKGROUND updates the persisted default, saved centrally via
  persist_settings_if_changed; RESET clears it.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 10:26:53 -07:00
Kevin GriffinandClaude Opus 4.8 21192011be fix(dist): measure in f64 so snapped endpoints stay precise (#193)
DistCommand stored the first point as Vec3 and ran on_point through
`let pt = pt.as_vec3();`, downcasting the f64 picked point to f32 before
computing distance/deltas. At survey-scale coordinates (eastings ~5e5)
f32's 24-bit mantissa gives ~0.03–0.06 unit resolution, so a snapped
endpoint measured slightly off — matching the report in #193.

Store `first` as DVec3 and keep the math in f64; cast to f32 only for
the visual preview wire vertices.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 09:52:36 -07:00
Hakan SevenandClaude Opus 4.8 f40144deac deps: bump acadrust to latest main (7c0c12fe)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 18:06:14 +03:00
Hakan SevenandClaude Opus 4.8 169a8cde3f chore: clear dead-code and unused-import warnings
Remove the unused `crate::par::prelude` glob import in wire_gpu.rs and
the never-called `viewport_at_paper_point` method; fold its doc into the
screen-space twin that is actually used so no intra-doc link dangles.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 18:04:52 +03:00
Hakan Seven 4aa7abfa4e Merge pull request #189 from schoeller/plugin_timeout_protection
Adding timeout protection to host from stuck runner
2026-06-26 17:58:37 +03:00
Hakan SevenandClaude Opus 4.8 5e59421e92 test(plugin): drop Dispatch floor in timeout test; doc stream-close path
The call-timeout test asserted the real 10 s Dispatch floor, adding 10 s+
to every suite run. Add a cfg(test)-only OCS_PLUGIN_TEST_FLOOR_SECS seam
so the test fires at its 1 s base instead; production still enforces the
hard safety floors. Test now finishes in ~1 s.

Also document in mark_dead that the host-side socket is closed indirectly
by killing the child (which unblocks and drops the detached reader
thread's Stream), not by the take() in mark_dead itself.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 17:56:24 +03:00
Hakan SevenandClaude Opus 4.8 da9562466d deps: bump acadrust to LWPOLYLINE BD/3BD parse fix
Picks up acadrust 2a59d3e, which reads/writes LWPOLYLINE thickness and
extrusion as BD / 3BD instead of BT / BE. Without it, polylines carrying
a thickness or extrusion flag parsed with a garbage normal and point
count and were dropped by the corrupt-entity guard on open — e.g. ~1600
valid polylines in the PLN_Sev block of a route drawing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 16:47:31 +03:00
Hakan SevenandClaude Opus 4.8 14ebdd36d1 perf(render): hold model wire set static instead of re-tessellating on camera moves
The model-space wire geometry is camera-independent, yet every pan/zoom
re-tessellated the whole model and re-uploaded it to the GPU. On a
block/text-heavy drawing that is millions of segments, costing a ~100ms
re-tessellation plus a ~150ms GPU re-upload per camera change (~258ms
freeze) while the GPU itself draws the full set comfortably in real time.

Tessellate the full, un-culled model once per geometry epoch, hold it
resident, and return it for every camera and tile. A pan/zoom now only
changes the view uniform; no frustum cull, no zoom LOD, no re-tess or
re-upload on camera moves. Pan/zoom frame time drops to ~7ms (~140fps)
with no stutter.

Removes the now-dead per-tile cull cache, pan-invariant hash, AABB
containment helper, and the reuse-during-motion / settle-regen machinery
that an earlier iteration introduced. Also exempts Face3D from the
sub-pixel stub LOD: a flat face is trivially cheap to tessellate, so it
is drawn full at any zoom and no longer pops a cube stub at the threshold.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 16:06:11 +03:00
Hakan SevenandClaude Opus 4.8 4e8521010b fix(wipeout): keep fill boundary precise at UTM coordinates
`wipeout_boundary_2d` built the fill boundary from the wipeout's
insertion point cast straight to f32, stored in a HatchModel with
world_origin = [0, 0]. At UTM-scale coordinates (~1e6) f32 resolves to
~0.06, so the boundary collapsed and the relative-to-eye fill shader
cancelled `boundary - eye` to noise — the mask rendered as squares.

Return the boundary as small f32 offsets relative to a world_origin
(the insertion point, kept in f64) and set HatchModel.world_origin to
it at both the model-space and paper/viewport fill sites. Same fix as
the wide-polyline and on-create hatch fills.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 11:20:50 +03:00