Commit Graph
1212 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 98aa051a74 feat(nav): turntable orbit, selection/cursor pivot, Shift+MMB rotate (#229)
Rework 3D view navigation:

- Turntable / lock-horizon orbit: horizontal drag yaws about world +Z,
  vertical drag pitches about the horizontal camera-right axis, so the
  view never banks. Pitch is pole-clamped but the straight-down default
  view can still be tilted out of.
- Orbit centre: the selection's centre when something is selected,
  otherwise the point under the cursor — no longer only the file centre.
  The camera revolves rigidly about the pivot, so it stays put on screen
  and the view doesn't jump.
- Mouse scheme: Zoom = wheel, Pan = MMB, Rotate = Shift+MMB. Right-drag
  no longer orbits (right-click stays context menu / Enter). Floating
  viewports orbit with Shift+MMB too.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 21:58:04 +03:00
Hakan SevenandClaude Opus 4.8 c303900bd2 fix(select): pin box-selection anchor to the world during zoom/pan (#234)
The window/crossing selection anchor was stored only in screen pixels, so
zooming or panning mid-drag left it frozen while the drawing moved under
it — the rectangle then covered the wrong world area and selected too much
or too little.

Store the anchor's world point and re-project it to the screen anchor
whenever the model-space view zooms (on_viewport_scroll) or pans
(on_viewport_move), so the rectangle tracks the drawing. The moving corner
already follows the cursor. (MSPACE floating-viewport box-select would
need the viewport camera and is left as-is.)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 21:10:04 +03:00
Hakan SevenandClaude Opus 4.8 d6e5b38847 fix(properties): close color picker when a panel dropdown opens (#235)
The Properties panel color picker is a custom bool-driven dropdown, while
Layer/Linetype/Lineweight are combo_boxes. Combo_boxes close each other on
blur, but nothing closed the color picker — so opening the Layer dropdown
while the color picker was open left both showing and overlapping.

Give every panel combo_box an on_open handler that collapses the color
picker, so at most one panel dropdown is open at a time.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 19:47:32 +03:00
Hakan SevenandClaude Opus 4.8 1614fd9ab2 feat(snap): dashed extension guide line for the Extension osnap (#238)
The Extension object snap worked but drew no tracking path, so it was
easy to miss — users had to keep the cursor exactly on the invisible
extension line without any visual cue.

When an Extension snap is active, draw a dashed guide line from the
endpoint it extends from, through the snap point and a little beyond,
and orient the three-dot marker along that direction. SnapResult carries
the endpoint screen position (filled once the winning snap is known);
the overlay renders the guide.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 19:39:21 +03:00
Hakan SevenandClaude Opus 4.8 53a4882c7f chore(release): bump to 0.7.1
Co-Authored-By: Claude Opus 4.8 <[email protected]>
v0.7.1
2026-07-01 19:17:42 +03:00
Hakan SevenandClaude Opus 4.8 7f3ebde663 fix(properties): repaint on linetype/lineweight/layer edits (#231 class)
Color/linetype/lineweight/layer are baked into the cached wire geometry
at tessellation time, so a property edit only repaints if the handler
bumps the geometry epoch. The color path did; several siblings only set
tabs.dirty (a save flag) and never re-tessellated, leaving the viewport
showing the stale look.

Route every handle-based edit through invalidate_property_targets
(mark dirty + recolor meshes + bump geometry), and full-bump the
name-based LAYER COLOR change:

- ribbon layer / linetype / lineweight dropdowns applied to a selection
- CHPROP (color/linetype/ltscale/transparency/layer)
- LAYMATCH (MatchEntityLayer)
- LAYER COLOR <name> <aci>

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 19:14:35 +03:00
Hakan SevenandClaude Opus 4.8 18fff5cf8c feat(command): ADDSELECTED draws a new object like the selected one (#239)
Take the first selected entity as a template, adopt its general
properties (layer, colour, linetype, lineweight, linetype scale) as the
current defaults, then start the matching draw command so the object
drawn next inherits them.

Supports Point, Line, Circle, Arc, Ellipse, all polylines (PLINE), Text,
MText, Spline, Hatch, Solid, Ray and XLine; other types report that
creation is unsupported. Registered for command-line autocomplete.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 18:42:26 +03:00
Hakan SevenandClaude Opus 4.8 1e9de924be feat(command-line): copy/clear history + selectable log (#232)
The command-line history dropdown was display-only, so users had to
screenshot and OCR the log to report output. Make it copyable:

- Render the whole backlog in one read-only text_editor so a single
  mouse drag selects across lines and Ctrl+C copies the span.
- Add Copy (whole log to clipboard) and Clear buttons to the dropdown.
- Gate the Ctrl+C/X/V accelerators on event Status::Ignored so a focused
  text widget's copy/cut/paste wins instead of firing COPYCLIP/CUTCLIP;
  the drawing's clipboard shortcuts still work when it has focus.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 18:17:46 +03:00
Hakan SevenandClaude Opus 4.8 ada9952f06 feat(layers): multi-select layers in the Layer Manager (#236)
The Layer Manager only allowed one layer at a time. Now:

- Ctrl/Cmd-click toggles a layer in the selection; Shift-click selects
  the range from the anchor; a plain click selects just one.
- Colour, linetype and lineweight changes apply to every selected layer.
- Delete removes all selected layers at once (reusing the #237 non-empty
  warning, now pluralised); layer "0" and the current layer are skipped.

Selection is stored as row indices but re-resolved by name across sorts
and layer-table rebuilds, so it never points at the wrong rows. Modifier
state is tracked via a new SetModifiers message (shift + ctrl/cmd).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 17:44:19 +03:00
Hakan SevenandClaude Opus 4.8 2712bb80e4 fix(layers): deleting a non-empty layer warns and removes its objects (#237)
Deleting a layer only dropped the layer record, leaving its objects in the
file still tagged with the (now-gone) layer name. Now:

- Empty layer → deleted straight away.
- Non-empty layer → a warning modal ("Layer X is not empty — N objects;
  deleting also removes them. Continue?") with Delete Objects / Cancel.
  Confirming erases every object on the layer, then removes the layer, as
  one undo step. The layer record is dropped before the erase so the
  locked-layer guard can't keep its objects.
- Layer "0" and the current layer can't be deleted (reported on the
  command line) instead of silently leaving a broken state.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 17:23:32 +03:00
Hakan SevenandClaude Opus 4.8 e3d1780a69 feat(plugin): quiet plugin IPC logs; print one "Loaded plugin" line
A normal run dumped the whole host↔runner trace (spawn, handshake, every
per-command dispatch and request/response) to the terminal. Gate that
behind OCS_PLUGIN_VERBOSE and, in normal runs, print just one line per
plugin: `Loaded plugin: <name> (<id> <version>)`.

The chatty host-side `[plugin]` lines now go through a `vlog!` macro that
only fires when OCS_PLUGIN_VERBOSE is set. Runner-side logs were already
suppressed (the runner is spawned with stderr = null); the remaining
`[plugin]` lines in the IPC client are genuine error messages, left as-is.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:28:10 +03:00
Hakan SevenandClaude Opus 4.8 2723e4d47f fix(layers): close locked-layer gaps found in review
- Double-clicking a locked object no longer opens its text / attribute /
  in-place block editor.
- Clicking a locked object during a command's object-gather no longer
  counts as completing the selection (don't set selection_just_completed
  on the locked path).
- copy_entities skips locked entities.
- The attribute editor's Apply refuses to write when the block's layer was
  locked while the dialog was open.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:14:33 +03:00
Hakan SevenandClaude Opus 4.8 56e1c03ac2 feat(layers): enforce layer lock (select/modify/fade + cursor badge)
Locked-layer objects were fully editable. Now they stay visible and
snappable but are otherwise protected:

- Not selectable: the five interactive pick paths (box, lasso, single
  click, cycling, box-release) plus SELECTALL and QSELECT skip locked
  entities.
- Not modifiable (defense-in-depth even if a handle slips through):
  transform_entities, erase_entities, apply_grip and the Properties
  commit all skip locked entities.
- Faded: entities on a locked layer render dimmed toward the background
  (render_style + wire tessellation); toggling lock re-tessellates.
- Snap still works: locked entities are intentionally kept in the wire
  set (visibility_ok untouched), so object snap keeps finding them.
- Feedback: a small padlock is drawn by the crosshair when hovering a
  locked object, and clicking one prints "Object is on locked layer …".

New Scene::is_layer_locked / locked_layer_name helpers centralise the
check (mirroring layer_hidden).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:10:39 +03:00
Hakan SevenandClaude Opus 4.8 c71950fa7c fix(deps): bump acadrust for MOVE attribute/MultiLeader fix (#224)
Pulls the acadrust translate fix: MOVE now carries a block's attributes
and the whole MultiLeader (text/base/block-content anchors + break
points), not just the insert point / leader line. No OCS code change —
the transform path already calls translate and re-tessellates.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 12:39:00 +03:00
Hakan SevenandClaude Opus 4.8 7764b6cbf9 feat(statusbar): Select All/Clear All in filter popup, drop osnap settings row
The selection-filter popup now has the same Select All / Clear All header
as the OSNAP popup: Select All clears every exclusion (all types
selectable), Clear All excludes every present type. Each button disables
when it would be a no-op.

Also remove the "Object Snap Settings…" footer from the OSNAP popup.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 09:47:13 +03:00
Hakan SevenandClaude Opus 4.8 d8862ee9cf feat(statusbar): icon toggles, taller bar, centered pills (#216)
The bottom status bar was hard to read: size-10 text labels in a 26px
strip. Make it legible and consistent with the drawing tab bar:

- Height 26 → 30 to match the document tab bar; the command-line input
  pill matches too, so the three strips line up.
- Replace the drafting-aid toggle labels (ORTHO/POLAR/OSNAP/OTRACK/DYN/
  LWT and the OCS pills ISO/QP/FILTER/SC/CLEAN/TPY) with 17px SVG icons;
  the name stays in each pill's tooltip. Twelve new icons under
  assets/icons/status, drawn in the existing 24×24 stroke style.
- Vertically centre every element via center_y, and bump all pill text to
  size 12 with uniform padding plus larger chrome icons (menu, scroll
  arrows, osnap dropdown) so nothing reads smaller than the toggle icons.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 09:33:00 +03:00
Hakan SevenandClaude Opus 4.8 0b60bcc075 feat(attedit): enhanced attribute editor with 3 tabs
Grow the attribute editor into a three-tab enhanced editor:
- Attribute: the tag/prompt/value list; click a row to select it, edit
  its value below.
- Text Options: the selected attribute's Text Style, Justification,
  Height, Rotation, Width Factor, Oblique Angle, Backwards, Upside down.
- Properties: the selected attribute's Layer, Linetype, Color,
  Lineweight (the colour list also surfaces a non-standard current
  colour so it displays and round-trips).

Applying writes value, text-formatting and common-property edits back to
each attribute positionally (guarded on block name + count), with undo
and a repaint. OK/Cancel are replaced by a single Apply button in a top
toolbar that commits but keeps the dialog open, matching the other modal
windows — the frame ✕ closes and discards un-applied edits. The frame
adopts the shared style-window palette, tab styling and sizing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 09:06:03 +03:00
Hakan SevenandClaude Opus 4.8 0eb347bf47 feat(attedit): show attribute prompts in the editor
Each row in the attribute editor now shows the attribute's prompt — the
text defined on the block's ATTDEF — falling back to the tag when the
block defines no prompt. The prompt is read from the block definition,
since attribute instances carry only tag + value.

Bumps the acadrust pin to pull the matching reader fix: its DWG object
reader was discarding the ATTDEF prompt, so every prompt came back empty.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 08:23:54 +03:00
Hakan SevenandClaude Opus 4.8 86a59d6c7e feat(attedit): attribute editor dialog for blocks
Double-clicking a block reference that has attributes, or running ATTEDIT
on one, now opens an editor dialog listing every attribute (tag + editable
value) with OK / Cancel. OK writes the values back to the block, with undo
and a repaint; Cancel discards. A block with no attributes still enters
in-place block edit (REFEDIT) on double-click as before. (#192)

ATTEDIT opens the dialog directly when a suitable block is already
selected; otherwise it runs the pick command and the dialog opens once a
block is chosen. This replaces the earlier command-line, per-attribute
prompt flow, which is removed along with its __ATTEDIT__ sentinel path and
the now-unused attedit_set_attrs trait hook; the ATTEDIT command is reduced
to a plain block picker.

The dialog is a tab-scoped in-canvas modal (Plan B): its working copy holds
a document-local handle, so closing that tab or switching away dismisses the
editor rather than risk applying edits to another tab's document.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 08:05:25 +03:00
Hakan SevenandClaude Opus 4.8 5f2e920b75 feat(properties): show & edit block attributes in panel
Selecting an INSERT with attributes now adds an editable "Attributes"
section to the Properties panel — each tag is a row whose value can be
changed inline (Enter commits, undo works, the attribute text repaints).
Previously attribute values could only be reached through ATTEDIT, which
didn't let them be changed (#192).

Attribute tags are runtime strings, so they can't ride the geometry
edit path whose field key is `&'static str`; a dedicated PropValue::
AttrText variant plus PropAttrInput/PropAttrCommit messages carry the
tag. Values are stored verbatim (no expression evaluation, since
attribute text is free-form).

Also guard the panel's in-progress edit buffer: it now only carries
across a rebuild when the selection is unchanged (a commit-triggered
refresh), so a typed-but-uncommitted value can't display or commit onto
a different entity — e.g. two title blocks sharing a REV1 tag. This
closes a latent stale-buffer leak that affected geometry fields too.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 07:42:50 +03:00
Hakan SevenandClaude Opus 4.8 3da81571a1 fix(ribbon): scrollable layer dropdown, stop mouse leak to viewport (#227)
The ribbon layer dropdown grew unbounded so a long layer list ran off the
bottom of the screen with no way to scroll. Cap the panel height and make
the list scrollable; short lists still shrink to fit.

While a dropdown was open, the 3D viewport beneath it kept tracking the
cursor over the panel's empty areas, and the OS cursor vanished there. In
iced 0.14 mouse_area/opaque only capture button presses, never CursorMoved,
so a higher stack layer can't swallow motion — gate it state-side instead:

- ignore viewport motion in on_viewport_move while ribbon.open_dropdown is set
- suppress the crosshair overlay's Hidden cursor (and crosshair draw) so the
  normal OS cursor shows over an open dropdown
- route all ribbon dropdown backdrops through a shared dropdown_backdrop()

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 00:45:23 +03:00
Hakan SevenandClaude Opus 4.8 1b198adc10 fix(purge): delete unused blocks, refresh UI, report per-type counts (#228)
PURGE removed unused layers/styles/linetypes from the document but never
refreshed the layer panel + ribbon caches, so the removed definitions
stayed visible in the UI (issue 228). It also never purged blocks.

- call refresh_layer_panel() after a purge so panel/dropdowns update
- purge unused block definitions (no INSERT/Dimension reference), skipping
  anonymous, layout, and xref records; drop their member entities too
- add PURGE BLOCKS sub-arg; bare PURGE / PURGE ALL now include blocks
- report a per-type breakdown in the result message

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 00:15:02 +03:00
Hakan SevenandClaude Opus 4.8 f6a7b0885f fix(render): repaint on layer/property edits (#231)
Layer color/linetype/lineweight and MATCHPROP edits set the
document dirty flag but never bumped geometry_epoch. The wire
caches key on that epoch and bake a ByLayer entity's resolved
color at tessellation time, so they returned stale wires until
a new entity forced a rebuild. Bump geometry on those four
handlers so the edit repaints immediately.

Also remove REGEN/REGENALL/REDRAW/REDRWALL: the GPU raster
pipeline keeps the display continuously in sync, so on-demand
regen/redraw is a no-op concept in OCS.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-30 23:41:13 +03:00
Hakan SevenandClaude Opus 4.8 a994a148fe perf(render): batch solid meshes, fix per-pick freezes on heavy 3D models
Opening / navigating a heavy 3D DWG (e.g. a BIM model with ~10k ACIS solids)
was effectively frozen. Several independent costs, all addressed here:

- Face3D edge/fill buffers were re-uploaded on every camera move (keyed on
  camera_generation). They are world-space and selection-independent, so gate
  them on (geometry_epoch, fill_mode) instead — a pan no longer re-walks every
  wire.
- Solid meshes were drawn one buffer + one draw call per solid (~10k draws/
  frame), which strangled the GPU front end. Concatenate every solid's LOD0
  into a few large buffers (split only to stay under the 256 MB buffer cap) and
  draw the whole set in a handful of calls. Built once per geometry epoch, so
  selection / hover never re-pack it.
- Selection / hover highlight is now a small tinted overlay of just the picked
  solids, drawn last with an Always-depth pipeline so it shows on top even when
  the solid is occluded — instead of re-tinting and re-uploading all meshes.
- Hover / pick hit-testing scanned every triangle of every solid each move
  (seconds on a heavy model). Add a 3D-AABB screen-rect broad phase
  (MeshLodSet now carries z_aabb) and reuse the renderer's cached expanded mesh
  set, so picking is O(solids) cheap projections plus the few solids actually
  under the cursor.
- Block-internal hatch hit-testing exploded every INSERT on every hover.
  Cache the result per geometry epoch and skip blocks that contain no hatch.
- Curved-surface (cone/sphere/torus) tessellation tolerance is now radius-
  relative (CURVE_REL_TOL) so a cylinder's facet count is size-independent and
  matches the circle/arc wire tessellation, instead of exploding on large radii.

Also adds docs/tessellation.md mapping every EntityType to its tessellation
path (truck B-rep vs truck curve topology vs direct).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-30 22:46:29 +03:00
Hakan SevenandClaude Opus 4.8 27896e3401 chore(deps): bump acadrust for AcDs SAB scan perf fix (#203)
Pulls the quadratic→linear AcDs blob-extraction fix in acadrust. 3D-heavy
DWGs (and master files that xref them) that took 28s+ to parse — minutes
for a multi-discipline set — now open in well under a second each.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-30 18:51:50 +03:00
Hakan SevenandClaude Opus 4.8 1d7f5d2618 fix(background): step-by-step prompt + Default preset (#217)
Bare BACKGROUND now opens an interactive colour prompt (via
ValuePromptCommand, same idiom as PDMODE/LTSCALE) instead of just
printing usage, so the command works both step-by-step and as a
one-shot `BACKGROUND <colour>`. Options reordered to
Default/Black/DarkGray/Gray/LightGray/White; DEFAULT restores the app
default, now unified to rgb(33,40,48) across startup, load and reset.
Also drop the unreachable OPTIONS arm in dispatch_display.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-30 17:55:44 +03:00
Hakan SevenandClaude Opus 4.8 970ee5a09f fix(blocks): layer-0 children inherit the block instance layer (#221)
Entities inside a block definition that sit on layer "0" must take on the
properties of the layer the block reference is inserted on; every other
layer is "sticky" and keeps its own identity. OpenCADStudio resolved a
block child's ByLayer color/linetype/lineweight against the child's own
layer in all cases, so layer-0 content stayed layer-0 (white) instead of
inheriting the instance layer.

Resolution now follows the standard rule for block children — explicit
wins, then ByBlock → the insert's own style, then layer-0 + ByLayer →
the insert's *layer* style, else the child's own layer. The insert's
layer style threads through the whole block-expansion chain (and chains
correctly through nested inserts), with the child's own transparency
preserved.

Covered across every block-render path:
- wires (line/arc/circle/polyline/text) via the block cache + the
  explode fallback + insert attributes
- hatch / solid fills (also fixes the PDF/print export path)
- 3D solid block children (per-instance mesh recolor)
- Dimension / Table baked-block sub-entities

New shared helpers in scene::view::render: InheritStyle, layer_render_style,
lineweight_to_px; render_style_for_block_sub gained the layer-0 branch.
Adds unit tests covering the layer-0, sticky-layer, ByBlock, explicit and
transparency cases.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-30 17:24:04 +03:00
Hakan SevenandClaude Opus 4.8 0a5d85aa8a fix(select): accumulate selection set, apply on Enter/RMB (#220)
Modify commands invoked with nothing pre-selected (ERASE, MOVE, COPY,
ROTATE, SCALE, MIRROR, ARRAY*, STRETCH, EXPLODE, GROUP, BLOCK, the
clipboard cuts, LAYOFF/FRZ/LCK/ULK, ...) ran the command on the FIRST
picked object and terminated. You could not build up a selection.

SelectObjectsCommand now keeps gathering: each pick accumulates into the
set (Shift removes), the prompt shows the running count, and the command
fires only when the user presses Enter or right-clicks. Reuses the
existing JOIN gather pattern, so no viewport/host changes are needed.

LAYMCUR acts on a single object's layer, so it keeps the immediate
first-pick behaviour via the new SelectObjectsCommand::instant().

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 22:58:21 +03:00
Hakan SevenandClaude Opus 4.8 57136b9eda fix(textstyle): live-update style manager preview (#102)
The preview box rendered a hardcoded `text("AaBbCc 0123")` that ignored
every edited property. Replace it with a canvas that tessellates the
sample string through the same stroke path the text entities use
(`lff::tessellate_text_ex`), so font, width factor, oblique angle and the
backward/upside-down flags are reflected live as fields change. Effective
font follows the entity rule (TrueType name wins, else stroke font file);
the result is fit to the preview box with Y flipped.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 17:29:04 +03:00
Hakan SevenandClaude Opus 4.8 ea35479dd9 fix(web): restore wasm build broken by SECTION + file association
The v0.7.0 web (GitHub Pages) release failed to compile under
--no-default-features. Two symbols leaked into the wasm build:

* `solid_section` (SECTION command) used truck-meshalgo directly
  (`triangulation`/`extract_interference`), but that crate is gated
  behind the `solid3d` feature which is off on wasm. Gate the function
  with `#[cfg(feature = "solid3d")]` and add a stub for builds without
  it, mirroring `solid_model::boolean`.
* `register_as_handler` was `#[cfg(not(wasm32))]` while its caller
  `set_default_app_blocking` was not, so wasm couldn't resolve it. Drop
  the wasm gate; the existing non-win/lin/mac arm already returns `Ok`
  on wasm, matching `unregister_handler`.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 17:13:17 +03:00
Hakan SevenandClaude Opus 4.8 da76918320 chore(release): bump to 0.7.0
Co-Authored-By: Claude Opus 4.8 <[email protected]>
v0.7.0
2026-06-29 16:48:15 +03:00
Hakan SevenandClaude Opus 4.8 a9214db344 fix(autosave): autosave unsaved drawings + clean up .sv$ on file-close (#205)
Two gaps in the autosave recovery files:

1. A brand-new, never-saved drawing was never autosaved — on_autosave only
   wrote tabs that already had a path. A shared autosave_target() now writes
   saved drawings to <file>.sv$ and unsaved ones to the system temp dir
   (OpenCADStudio_<name>.sv$).

2. Closing a file (tab) left its .sv$ behind, which also defeated the
   program-close cleanup (it only scans still-open tabs). on_tab_close and the
   unsaved-discard path now delete the closing tab's .sv$, and Save/Save-As
   drop the temp copy a drawing used while it was still unsaved.

Program-close cleanup (exit_app -> cleanup_autosaves) already worked.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 16:39:42 +03:00
Hakan SevenandClaude Opus 4.8 286caa9c61 feat(io): delete .sv$ autosave files on clean save/exit (#205)
The .sv$ recovery copies only need to survive a crash, so they are removed on
a clean save (Save and Save-As both delete the file's .sv$ after writing) and
on a clean exit — a new exit_app() runs cleanup_autosaves over every open tab
before quitting, and all iced::exit() paths now funnel through it. A leftover
.sv$ therefore signals an unclean shutdown, i.e. something to recover.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 13:34:16 +03:00
Hakan SevenandClaude Opus 4.8 02d2764e45 feat(io): autosave to .sv$ recovery files (SAVETIME command) (#205)
A periodic subscription (every SAVETIME minutes — default 10, 0 disables)
writes a <name>.sv$ recovery copy of each dirty, already-saved drawing at its
own DWG version. Non-destructive: it never touches the original file or the
dirty flag, and skips unsaved (path-less) tabs. The SAVETIME command
sets/queries the interval and persists it. Recovery files (.sv$ and .bak) open
through the normal Open dialog — the loader detects DWG vs DXF from the leading
magic bytes, and a "Backup / Autosave" filter lists them.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 13:17:40 +03:00
Hakan SevenandClaude Opus 4.8 54186309b2 refactor(io): set up file association once, not on every launch (#205)
Drop the every-launch (re)registration thread. File association is now set up
once via the first-launch prompt (Yes → register + set default; No → decline,
persisted as file_assoc_enabled) and managed afterwards by the FILEASSOC
command. set_default_app now registers the handler entries (.desktop / mime /
ProgIDs) itself before asking the OS to default to us, since boot no longer
does — so the default never points at a missing handler.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 13:06:33 +03:00
Hakan SevenandClaude Opus 4.8 9ea800f049 feat(io): FILEASSOC command to toggle file association on/off (#205)
`FILEASSOC 1` (re)registers the app as a .dwg/.dxf/.bak handler; `FILEASSOC 0`
unregisters it (removes the .desktop + CAD mime package on Linux, the per-user
registry keys on Windows); bare `FILEASSOC` reports the current state. The
choice persists (file_assoc_enabled) and gates the every-launch
re-registration, so turning it off stays off across restarts.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 12:58:23 +03:00
Hakan SevenandClaude Opus 4.8 962268e16e feat(io): .bak backup-on-save + open .bak as a supported type (#205)
- Save: overwriting an existing file first copies it to a sibling <name>.bak
  (default on; toggle with the ISAVEBAK command, persisted in settings).
- Open: .bak is a first-class type — the Open dialog lists it and the loader
  detects DWG vs DXF from the file's leading magic bytes (the extension hides
  the real format).
- File association registers .bak too, every launch (so users who associated
  earlier get it automatically): Windows via the registry; Linux via a
  high-priority shared-mime-info magic that resolves DWG/DXF content under the
  .bak extension, with the generic *.bak glob claimed only once the app is the
  default DWG handler — so it never hijacks other apps' backups.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 12:49:02 +03:00
Hakan SevenandClaude Opus 4.8 b77187e672 feat(save): warn before dropping AEC/Civil objects or overwriting on Save-As
Two in-canvas confirmations on the Save-As path:

- AEC/Civil drop warning: when the drawing carries unsupported AEC/Civil
  (application) objects that survive only as verbatim source-version bytes,
  saving to a different version or to DXF would silently drop them. The
  dialog reports the count and offers "Save in source version" (keep) vs
  "Save anyway" (drop). Same-version DWG saves are lossless and skip it.
- Overwrite confirmation: when the chosen filename already exists in the
  target folder, "Replace" / "Cancel" before clobbering it.

The two chain (AEC first, then overwrite) via per-flow acknowledgement flags
reset whenever the Save dialog opens or closes. Detection is a new
io::dropped_on_save_count over the document's Unknown (raw-passthrough)
objects/entities.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 11:47:37 +03:00
Hakan SevenandClaude Opus 4.8 ab9285f114 feat(viewport): run typed command-line text on right-click
When the command line holds a typed-but-unentered command (or option
keyword / value), a viewport right-click now runs it, mirroring Enter.
Routed through CommandFinalize so the MText preview and grip-popup
guards apply and a non-empty line forwards to the submit path. All
other right-click behaviour is unchanged and applies only when the
command line is empty: right-drag orbits, the first right-click during
an active command acts as Enter, a second opens the context menu, and
an idle right-click opens the menu.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 11:20:01 +03:00
Hakan SevenandClaude Opus 4.8 2ec3348740 chore(deps): bump acadrust 7e20fd1 — DXF dangling-ref cleanup
DWG→DXF conversion now drops references to objects that have no DXF form
(unsupported AEC/application objects dropped on conversion), so the output
opens clean instead of auditing 619 dangling handle references.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 11:01:20 +03:00
Hakan SevenandClaude Opus 4.8 c653a3f38e feat(commands): add FLATSHOT and CONVTOSURFACE
- FLATSHOT: project the selected solid's edges to 2D Line entities at Z=0
- CONVTOSURFACE: convert the selected solid(s) to Surface entities

Both reuse the cached B-rep's edge wires. COMMANDS.md: 219/262.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 09:16:44 +03:00
Hakan SevenandClaude Opus 4.8 6f9565e9f7 feat(commands): add ARCHIVE / ETRANSMIT (folder package)
Package the drawing plus its referenced files (xrefs + raster images)
into a sibling "<name>_archive" folder via std::fs, reusing the
existing block_records / entities iterators. COMMANDS.md: 217/262.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 09:10:57 +03:00
Hakan SevenandClaude Opus 4.8 f513899d49 feat(commands): add REGION (closed boundaries -> region entities)
Convert selected closed polylines and circles into Region entities,
one wire loop each (reuses acadrust Region/Wire). COMMANDS.md: 215/262.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 09:07:30 +03:00
Hakan SevenandClaude Opus 4.8 e2a83a6487 feat(commands): add CLOSE, HIDE, VISUALSTYLES, QUICKCALC, HYPERLINK, XOPEN
Six more standard commands by reusing existing infrastructure:
- CLOSE: close the active tab (Message::TabClose)
- HIDE / VISUALSTYLES: per-viewport render mode (Message::SetRenderMode)
- QUICKCALC / QC: alias the CAL arithmetic evaluator
- HYPERLINK <url>: attach a hyperlink via the PE_URL XData record
- XOPEN: open a selected xref's source file (Message::OpenRecent)

COMMANDS.md updated to 214 done / 15 partial / 33 missing of 262.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 09:04:18 +03:00
Hakan SevenandClaude Opus 4.8 c19c18c2f2 feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.

3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)

Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
  (XData markers honoured by the existing geometry-gen / tessellator)

Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
  system variables, and layer/style/view commands

POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 08:23:58 +03:00
Hakan SevenandClaude Opus 4.8 76ef544cda chore(release): bump to 0.6.9
Co-Authored-By: Claude Opus 4.8 <[email protected]>
v0.6.9
2026-06-29 01:48:04 +03:00
Hakan SevenandClaude Opus 4.8 f88c2f8327 style(ribbon): cargo fmt after PR213 merge
PR #213 introduced rustfmt drift in tip_style / swatch indentation and an
inline if-expression in two previously fmt-clean files. Format pass only;
no behaviour change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 01:19:05 +03:00
Hakan Seven 0c264fc49d Merge pull request #213 from KarimJerbi/fix/ribbon-center-section-titles
feat(ribbon): centre section titles + deduplicate inline colour literals
2026-06-29 01:18:21 +03:00
Karim Jerbi 3d23c45016 chore: disable printpdf default features (unused html deps)
printpdf's default = ["html"] pulled in azul-layout, rust-fontconfig,
kuchiki, xmlparser and their transitive trees (~40+ crates including
two with cdylib targets) — none of which are used by the project's
PDF export (only basic primitives like Mm, Line, Polygon, Rgb).

Also fixes a MinGW linker error (export ordinal too large: 100035)
triggered when ld.bfd tries to build a DLL for printpdf with >65535
exported symbols.
2026-06-29 01:06:28 +03:00
Karim Jerbi 63c13f0f63 refactor(ribbon): extract repeated inline color literals into named constants
Consolidates 21 occurrences of identical Color literals scattered across widgets.rs and mod.rs into 9 named constants (COMBO_BG, COMBO_HOVER_BG, COMBO_OPEN_BG, COMBO_BORDER, COMBO_ACTIVE_BORDER, COMBO_ARROW, SWATCH_BORDER, TIP_BG, HIST_INACTIVE_BG). -85 net lines, zero behaviour change.
2026-06-28 22:59:14 +01:00