Commit Graph
53 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 c0844ad55c feat(annotative): synthesize per-object contexts + editable UI
Make objects genuinely annotative — carrying real per-scale
representations that interoperate with other CAD apps — rather than just
a native flag. `create_annotation_context` synthesizes the extension-dict
chain (AcDbContextDataManager -> ACDB_ANNOTATIONSCALES -> per-scale leaf)
from the entity's placement; the editable Annotative toggle now covers
Text and block references as well as MText/MLeader; and a new
"Annotation Object Scale" dialog (OBJECTSCALE) adds/removes an object's
per-scale memberships. Requires the acadrust ObjectContextData encoder
(Cargo.lock bump).

Also fixes a render bug where objects carrying an *empty*
ACDB_ANNOTATIONSCALES (a single-representation marker with no per-scale
reps) were treated as annotative and (mis)scaled by the annotation factor
in scaled paper viewports, ballooning the text: an object is now
annotative-by-context only when its scale collection is non-empty, so
such objects render at their base geometry.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-13 22:38:29 +03:00
Hakan SevenandClaude Opus 4.8 b1eba180eb feat(annotative): add/remove drawing annotation scales
The drawing's annotation-scale list (ACAD_SCALELIST) is now editable, so the
set of scales offered by the status-bar scale pill can be grown or trimmed:

- Scene::add_scale / remove_scale create or drop a Scale object and its
  SCALELIST dictionary entry (creating the SCALELIST dictionary, and
  registering it in the root, for drawings that carry none).
- SCALELISTEDIT gains ADD <ratio> / DELETE <name> alongside the existing
  listing (e.g. `SCALELISTEDIT ADD 1:50`).
- The scale popup (model space) grows a delete affordance on each non-active
  scale and a "+ Add scale..." row that primes the command line. The current
  annotation scale is protected from deletion.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-13 10:18:05 +03:00
Karim Jerbi 8ef65f59f8 fix(ribbon): highlight toggle buttons by their actual state
The UCS Icon, Properties, File Tabs, and Layout Tabs buttons were highlighted unconditionally because their ids fell through to an active_tool equality check in is_active_tool, which activate_tool sets on every click and never clears. Each id is now mapped to its corresponding visibility flag (show_ucs_icon, show_properties, show_file_tabs, show_layout_tabs), and those flags are kept in sync from their toggle handlers so the highlight reflects live state. The per-toggle booleans previously threaded through the render path are consolidated into a single ToggleState struct read from one place, so introducing a further toggle no longer requires changing every render-function signature and call site.
2026-07-12 19:53:48 +03:00
Hakan SevenandClaude Opus 4.8 57710eec2b fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).

Two-layer fix:

1. Coordinate backbone -> f64. last_point, last_cursor_world,
   dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
   the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
   paths, command-line coordinate entry and the coordinate readout all
   compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
   geometry, projection, UCS-icon/viewcube drawing) with casts only at
   those boundaries.

2. Per-command committed storage -> DVec3. on_point already received a
   DVec3, but many command impls narrowed it back to f32 (let pt =
   pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
   the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
   INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
   wipeout, attdef, table, tolerance, array center, mview, plot_window,
   plus the QDIM_PLACE handler and qdim_collect_points. Preview,
   hit-test and rubber-band paths keep f32 at the boundary.

Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 21:26:40 +03:00
Hakan SevenandClaude Opus 4.8 8ed10ceb2a feat(mtext): structured editor with selection, copy, and visible bold
Rewrite the in-app MTEXT editor to run on acadrust's structured
MTextDocument model end-to-end: load via parse_mtext, edit a flat Cell
model, serialize via to_mtext_string. Retires the hand-rolled format-code
string handling, the parallel visible_spans parser, the composed_value
prefix, and the Edit-raw-code tab.

Selection and copy:
- double-click selects the word, triple-click selects all (click-counting)
- Ctrl+A selects all text
- Ctrl+C copies the selected text to the clipboard

Formatting:
- per-selection Bold/Italic/Underline/Overline/Strike/Case/Alignment
- Bold renders as a visibly thicker SDF pen, keyed (family, char, bold)
  through the whole text pipeline; the resolved style font name is stamped
  so the \f...|b1; code survives serialization
- per-selection font and color changes
- editor color dropdown reuses the Properties panel color selector

Global toolbar defaults (font/color/oblique/width/tracking) fold onto the
document as real span properties instead of a leading code prefix; a
trailing empty paragraph is preserved so Enter at the end keeps its line.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 20:18:50 +03:00
Hakan SevenandClaude Opus 4.8 438cbd17b1 feat(help): open GitHub Discussions from the HELP command
HELP was registered for autocomplete but had no dispatch arm, so it
reported an unknown command. Route it through open_url to the project's
GitHub Discussions page, matching the DONATE/WEBVERSION external-link
commands.

Closes #307

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 10:02:08 +03:00
Hakan SevenandClaude Opus 4.8 2a72227d5d feat(attribute): route ATTMAN/BATTMAN to the attribute editor (ATTEDIT)
ATTMAN and BATTMAN previously produced a command-line listing of block
attributes. Route both to the attribute editor instead: extract the bare
ATTEDIT behavior into a shared open_attedit_dialog helper (edit the
selected block, or start the block-pick command) and call it from the
ATTEDIT, ATTMAN, and BATTMAN arms.

Kept as a match arm rather than an ocad.pgp alias so it works for
existing users, whose config pgp is only seeded on first run.

Replace the listing regression tests with one asserting ATTMAN and
BATTMAN behave byte-for-byte like ATTEDIT.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 09:37:54 +03:00
Kevin GriffinandClaude Opus 4.8 0f6999a838 fix(blocks): make BATTMAN work instead of erroring like ATTMAN (#295)
BATTMAN matched the ATTMAN command arm but the argument was extracted
with `cmd.trim_start_matches("ATTMAN")`. "BATTMAN" has no leading
"ATTMAN" to strip, so the whole word survived as a bogus block-name
argument — BATTMAN always reported "ATTMAN: no matching block" instead
of listing attributes. `BATTMAN <name>` never matched at all.

Strip whichever keyword was actually typed (BATTMAN first, since ATTMAN
is a suffix of it) with `strip_prefix`, and match the `BATTMAN ` prefix
form too. BATTMAN now behaves identically to ATTMAN.

Adds headless regression tests driving the real dispatcher; they fail on
the previous code with the reported symptom and pass with the fix.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-10 15:06:12 -07:00
Hakan SevenandClaude Opus 4.8 da2c600953 feat(sysvars): persist Ortho, running OSNAP and Isolate in the drawing
Ortho and the running object-snap set were app-global (settings.txt only) and
Isolate/Hide was a session-only view state, so none survived save/reopen and
none were per-drawing. Bind all three to the file like AutoCAD:

- Ortho -> header.ortho_mode ($ORTHOMODE); running OSNAP <-> header
  .object_snap_mode ($OSMODE bitmask) via a new SnapType<->bit map in settings
  (osmode_from_snaps / snaps_from_osmode, round-trip tested). The header is the
  per-drawing store, live state mirrors the active drawing: adopt header->live
  on open and tab-switch, stamp live->header on save and when leaving a tab, a
  fresh drawing inherits the current values, and SETVAR ORTHOMODE/OSMODE now
  also update the live state. settings.txt stays as the boot/new-drawing
  default; the per-file header overrides it on open.
- Isolate/Hide now set each entity's persisted invisible flag (DXF code 60,
  which acadrust round-trips in DXF and DWG) in addition to the session set;
  End Isolation clears it. The set is reseeded from invisible entities on load
  and after undo/redo, the commands are undoable and mark the tab dirty. The
  renderer already skipped invisible entities, so no render change was needed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-10 15:45:08 +03:00
Hakan SevenandClaude Opus 4.8 91ba9f7699 feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:

- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
  embedded and copied to config/ocad.pgp on first launch; only that user
  copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
  dispatch_command_inner, so aliases resolve for every path (families,
  plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
  surfaces its target command as the top (highlighted) suggestion so the
  highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
  button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
  CommandRegistration arrays, keeping canonical/synonym/distinct-command
  names. ocad.pgp is now the sole source: deleting or remapping an alias
  there takes effect. CUI stays on the keyboard-shortcut panel.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-10 12:39:22 +03:00
Hakan SevenandClaude Opus 4.8 c249a99d4c fix(clipboard): paste anchor at bounding-box lower-left, not center (#285)
COPYCLIP/CUTCLIP used the mean of each entity's bounding-box centre as the
paste anchor. Standard clipboard behaviour puts the lower-left corner of the
box enclosing all copied items under the cursor. Union each entity's bbox min
corner instead, and rename clipboard_centroid -> clipboard_base (COPYBASE also
stores a picked point there, so "centroid" was inaccurate).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-10 11:16:13 +03:00
Hakan SevenandClaude Opus 4.8 3c3ca89d97 feat(text): TEXTFILL — fill or hollow TrueType glyphs (#260)
TEXTFILL 1 (default) fills TrueType text; TEXTFILL 0 draws it hollow
(outline only). Settable directly or via SETVAR, 0/1 or ON/OFF. SHX/LFF
stroke fonts are unaffected.

Reuses the existing stroke-SDF path: bake_glyph treats a TTF glyph as a
stroke (outline) field instead of a signed fill field when TEXTFILL is
off. Toggling resets the glyph atlas and re-tessellates text so quads
pick up the re-baked tiles. Persisted in user settings.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-09 16:41:53 +03:00
Hakan SevenandClaude Opus 4.8 694c5b5a7f feat(modify): EXTRIM — cookie-cutter trim on one side of a boundary (#253)
Run EXTRIM, pick a cutting boundary (line/arc/circle/ellipse/spline/
polyline), then click a side: every object crossing the boundary is
trimmed on that side and objects wholly on it are erased. Side is decided
by a parity crossing count (a segment to the pick point crossing the
boundary an even number of times is on the pick side), reusing the trim
intersection machinery; line boundaries extend to infinity. Line/arc/
circle are trimmed exactly; polyline/ellipse/spline via dense sampling.

Live preview while choosing the side: boundary in yellow, surviving parts
in blue, the removed parts in red.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-09 16:26:24 +03:00
Hakan SevenandClaude Opus 4.8 c45fe0267c feat(plot): QUICKPRINT/QP — plot a selection's bounding box to PDF (#325)
Run QUICKPRINT (or QP), select objects, then Enter: the selection's
bounding box becomes the plot window and a PDF is written next to the
drawing (<name>_<timestamp>.pdf) using the active page setup, with no
dialog. Model space.

Implemented as a selection-gathering CadCommand (src/modules/view/
quick_print.rs) returning CmdResult::QuickPrint(handles); the host
(on_quick_print_handles) unions the picked entities' AABBs, sets the
plot window, and reuses the tested clipped window-export path.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-09 12:57:35 +03:00
Hakan SevenandClaude Opus 4.8 8820b43127 feat(plot): unified plot/print dialog, retire Page Setup
Replace the old Page Setup panel with a single OCS-styled plot/print
dialog (dropdowns for printer, paper, orientation, rotation, plot area,
scale, quality, shaded plot; checkboxes for plot options). PLOT / PRINT
/ PAGESETUP all open it.

- Printer output via lp: -d printer, -n copies, -o ColorModel=Gray,
  print-quality, Resolution; Windows printto verb; list_printers via
  lpstat. Preview opens a temp PDF in the OS viewer (never a save
  dialog). PDF export reuses the tested pipeline.
- Window pick returns to the new dialog (was the old Page Setup) and the
  clipped window plot drives PDF / printer / preview.
- Remember the user's print preferences across sessions in
  config/plot.txt (printer, copies, quality, output options, area,
  scale); drawing-specific fields (offset/center/rotation) seed from the
  layout. Default plot area Window, scale Fit.
- Delete src/ui/window/page_setup.rs and its ModalKind / Message /
  buffer state; fold on_page_setup_commit into apply_plot_page_settings.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-09 10:27:54 +03:00
Hakan Seven 17f46968d8 Merge PR #322: perf — responsive large copy/paste + point picking
External contributor sLuCHa (sLuCHaa). Gates O(k²) snap intersection
passes, O(1) handle lookups in preview, bbox paste anchor/ghost,
count-only properties panel for huge selections, incremental paste mesh.
2026-07-08 23:38:37 +03:00
Hakan SevenandClaude Opus 4.8 c4fcfacb18 feat(draw): Arc Continue command; 3-Point default
Arc ribbon default + icon + dropdown order set to 3-Point. New ARC_CONT: tangent-continuing arc from the last line/arc endpoint, pick end only; Ctrl flips direction. Exit anchor recorded on commit (disambiguated by last pick) so chaining continues from the correct end.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-08 20:39:40 +03:00
sLuCHa 56835791af perf(clipboard): derive the paste anchor from bounding boxes
COPYCLIP/CUTCLIP computed the clipboard anchor by tessellating every
selected entity and averaging all wire vertices — O(total geometry),
~250 ms when copying a whole drawing. The anchor only fixes which point
of the selection sits under the cursor at paste, so the mean of each
entity's bounding-box centre is an equally valid, far cheaper origin.
2026-07-08 18:57:11 +02:00
sLuCHa 363593b09f feat: plot a model-space window to PDF
Pick a rectangular window in model space with the existing PLOTWINDOW command (now enabled in model space, previously paper-space only) and export it to a PDF at a chosen ISO paper size (A4-A0), orientation and scale (fit or ratio). The window is a hard clip boundary. Adds paper-size presets, a window-to-sheet transform, scale + clip support in the PDF exporter, and Page Setup controls (format/orientation/pick window).
2026-07-05 23:13:43 +02:00
Hakan SevenandClaude Opus 4.8 77c0c5a9d0 feat(plugin): surface loaded plugin commands in command-line autocomplete
Autocomplete suggestions came only from all_registered_command_names() —
the compile-time inventory registry returning &'static str — so
runtime-loaded plugin commands could never appear. Typing a plugin's
command prefix (e.g. `LS_`) showed nothing even with the plugin loaded
and its ribbon tab active; dispatch worked, only discovery was blind.

Collect each enabled plugin's ribbon ToolDef command ids plus its
manifest command_prefixes into a dynamic candidate pool
(OwnedRibbonGroup::command_ids → PluginManager::command_names →
plugin_command_names), refreshed in rebuild_ribbon_modules on startup
load, settings reload, and every enable/disable toggle. ranked_matches
now merges this pool with the static registry and returns owned strings.

A plugin.toml `commands = [...]` list for sub-verbs with no ribbon button
(e.g. LS_AUTOLABEL) stays a follow-up — it's a plugin-API surface change.

Closes #272

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 23:01:53 +03:00
Hakan SevenandClaude Opus 4.8 e78e137664 fix(paper): keep model 3D solids off the paper-space sheet
The paper layout sheet pass reused the full model mesh set, so every
top-level 3D solid piled onto the paper origin — the sheet camera works
in paper coordinates, not model space. Guard `meshes` on
`inst.paper_sheet` the same way wires/hatches/images already are: the
sheet shows only paper-native content; the model's solids render inside
the floating content viewports (model camera + per-viewport scissor).

Also name 3DSOLID/REGION/BODY/SURFACE/MESH/SOLID/MULTILEADER in dxf_name
(they fell through to "ENTITY"), and add a SELHANDLES diagnostic that
reports the active space plus a per-type / per-block selection breakdown.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 23:29:18 +03:00
Hakan SevenandClaude Opus 4.8 5e4e7c1020 fix(commands): ADDSELECTED keeps CLAYER, adopts dimension style (#239)
Two follow-ups to ADDSELECTED:

- It no longer permanently changes the current layer/colour/linetype/
  lineweight. The template's properties are captured as the drawing
  defaults only while the launched draw command runs; a snapshot taken in
  cmd_add_selected is restored the moment that command ends (central hook
  in apply_cmd_result) or is interrupted by another command.
- Dimensions are supported: add_selected_verb maps each DimensionType to
  its DIM* command, and the template's dimension style is adopted as the
  current DIMSTYLE (restored afterwards). commit_entity_handle now stamps
  every new dimension with the current DIMSTYLE — the DIM commands left the
  default "Standard" on the entity, so a normal dimension also picks up
  the active style now.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 16:05:54 +03:00
Hakan SevenandClaude Opus 4.8 79a5ac6fed fix(scene): correct MIRROR of TEXT under MIRRTEXT (commit + preview)
Root cause: the glyph tessellator advanced the pen by advance*width_factor
while the transform already scales the pen position by the width factor, so
a backward (negative width factor) run kept its word order and grew on the
same side of the anchor instead of mirroring across it. Advance in unscaled
glyph units to match text_local_bounds; the transform applies the factor once.

On top of that:
- The renderer honours the entity's own text-generation flags (DXF 71: bit 2
  backward / bit 4 upside-down) XOR the text style's flags, and signs the
  horizontal anchor by the width factor so Center/Right backward text mirrors
  about its anchor.
- MIRRTEXT on: a MIRROR toggles both group-71 bits so the reflected rotation
  renders a true glyph mirror (backward-upright for a vertical axis, flipped
  for a horizontal one).
- MIRRTEXT off: the text flips left<->right and lands mirror-symmetric to the
  source (right-reading) instead of hugging the axis.
- The MIRROR preview re-derives text ghosts to match the commit; mirrored()
  now reflects the double-single residual too.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 12:42:55 +03:00
Hakan SevenandClaude Opus 4.8 a24232842c feat(commands): prompt for a system-variable value on the next line
Typing a bare system-variable name (MIRRTEXT, FILLMODE, LTSCALE, …) now
echoes the current value and prompts "Enter new value for <NAME> <cur>:",
capturing the next command-line entry as the new value — Enter keeps the
current one, Escape cancels. The single-line "MIRRTEXT 1" form still works.
Mirrors the awaiting_vports pattern: a pending_setvar field on the app,
consumed at the top of on_command_submit.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 12:42:55 +03:00
Hakan SevenandClaude Opus 4.8 7bab7c7b8d feat(ui): replace app-menu logo with a quick-access file toolbar
Remove the OCS-logo button and the dropdown menu it opened; the top strip
now shows direct New / Open / Save / Save As / Print buttons ahead of the
undo/redo controls. Drop the HELP and OPTIONS commands (HELP dumped text to
the command line, OPTIONS aliased the drafting-settings popup).

The app-menu overlay is gone, so its rendering code, MENU_ITEMS, four dead
messages, and the LOGO_RED / GEAR / HELP assets are removed. The recents
list it held is the only survivor — moved into OpenCADStudio.recent_files
via a new app/recent.rs, backing the Start page's Recent Documents panel.

Quick-access icons are redrawn in a cleaner style (file-plus, folder-open,
device-floppy, file-export, printer) and tinted light so they read on the
dark strip; FOLDER / DOC keep their old glyphs for the file browser.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 09:51:49 +03:00
Hakan SevenandClaude Opus 4.8 2bc5c2a686 fix(commands): JUSTIFYTEXT sets vertical alignment for single-line TEXT
The Text branch only set horizontal_alignment, so TL/BL/MC etc. never
moved the text box vertically. Map the T*/M*/B* codes to Top/Middle/Bottom
and re-sync the alignment point. (#246)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 09:51:34 +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 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 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 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 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 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 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 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 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 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 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 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