Commit Graph
29 Commits
Author SHA1 Message Date
Hakan SevenandClaude Fable 5 261b699dee feat(edit): MATCHPROP stays active for repeated targets (#362)
Applying to one target used to end the command. The apply now clears
the consumed selection and keeps the command in its target phase, so
properties can be painted onto object after object; Enter or Escape
finishes (prompt updated accordingly).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:22:27 +03:00
Hakan SevenandClaude Fable 5 dc10c5dfba feat(edit): STRETCH crossing window selects objects implicitly (#338)
Bare STRETCH used to need a separate object-selection round before the
crossing window. Now the window itself picks the objects: with no prior
selection the command emits StretchWindow, the host gathers entities
whose world AABB touches the window (locked layers excluded) and
restarts at the base-point step. Over-selection is harmless since only
points inside the window move. The noun-verb flow (select first, then
STRETCH) is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 22:35:35 +03:00
Hakan SevenandClaude Fable 5 5383c64d5d fix(edit): STRETCH moves dimension definition points (#372)
The STRETCH apply loop had no Dimension arm, so dimensions fell into
the generic skip — points never moved, and a reopened file's stale
baked *D block made the result snap back visually. Move every
definition point inside the window (grip-equivalent set plus pinned
text, sparing the zero auto-placement sentinel), refresh the stored
measurement, and drop the stale *D block so the stretch survives
render and save.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 22:24:20 +03:00
Hakan SevenandClaude Fable 5 0cd991e42f fix(xref): show merged xref layers in Layer Manager on attach (#407)
XATTACH / XRELOAD merge the xref's layer and linetype tables into the
document, but the Layers panel cache only rebuilds via
sync_with_viewports - which nothing triggered until the file was saved
and reopened. Refresh the layer panel (and ribbon mirrors) right after
the merge in both paths.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 15:59:26 +03:00
Hakan SevenandClaude Fable 5 84620852fe feat(select): Previous / Last keywords at Select objects prompts
Standard selection keywords, available typed or as prompt buttons in
every gathering command (#426): P / PREVIOUS re-selects the set the
last finishing command worked on (now captured before the working set
is dropped), and L / LAST selects the most recently created object of
the current space (highest handle among its selectable wires). Handled
centrally in the step router, so the command line, the option buttons
and the automation feeder share one implementation; deleted objects
and locked layers are filtered out.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 11:01:51 +03:00
Hakan Seven 5afee1d71f Merge pull request #384 from KevinGriffin-new/fix/matchprop-text
Match text style and height between TEXT/MTEXT objects (#361).

MATCHPROP copied only the generic properties, so Text->Text and
Text->MText matching left the destination's style and height unchanged.
The MatchProperties handler now also carries style + height between
text-like objects, both directions, like AutoCAD's "Text" special
property. bump_geometry re-tessellates so matched text repaints at its
new size.

Conflict: the fix (command_driver.rs) auto-merged; only the additive
test block in automation.rs conflicted with tests main added since the
PR branched. Resolved by keeping all four tests.

# Conflicts:
#	src/app/automation.rs
2026-07-16 11:33:46 +03:00
Hakan SevenandClaude Opus 4.8 1490fe4ea9 perf(undo): record entity deltas instead of cloning the whole document
Frequent entity-only edits (MOVE/ROTATE/SCALE/MIRROR/ALIGN, COPY/ARRAY,
draw-add, ERASE) captured a full ~800k-entity CadDocument clone (~46ms) on
every edit — the last big per-edit cost after the incremental-render work.

They now record only the touched entities' before/after images:

- Scene self-records through the five mutation primitives when a recording is
  open (UndoRecording + is_recording_undo guard, zero cost off-path).
- apply_entity_delta installs one side per handle (in-place / re-insert with
  the original handle / remove) plus a per-handle cache reseed, then a single
  bump_entities so the incremental caches patch instead of a full re-tess —
  undo/redo of a small edit is fast too, not just the edit.
- HistorySnapshot is now Full | Delta; a Delta is self-inverting and moves
  between the undo/redo stacks without re-capturing. Commands that touch
  non-entity state (new layer, group, *D block, viewport) fall back to the
  full snapshot via per-command predicates, and a poison flag debug-asserts
  any mis-scope.

remove_entity leaves the handle dangling in its owner block record's
entity_handles (harmless on its own, a lookup miss is skipped), so a
re-insert strips stale entries first — otherwise add_entity would list the
handle twice and the DXF/DWG writer would emit the entity twice.

Round-trip tests cover transform/erase/add/copy (do->undo->redo equality,
handle preservation, no duplication).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-15 11:11:17 +03:00
Kevin GriffinandClaude Fable 5 e35c557f64 fix(matchprop): match text style and height between TEXT/MTEXT objects (#361)
MATCHPROP only copied the generic properties (layer, color, linetype,
linetype scale, lineweight), so matching text to text left the
destination's style and height unchanged. Copy the source's text style
and height to TEXT and MTEXT destinations, in both directions, like
AutoCAD's "Text" special property.

Adds an in-process regression test that drives the real interactive
command path (source pick -> destination selection) and fails on the
old code with the reported symptom.

Fixes #361

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 18:22:48 -07:00
Hakan SevenandClaude Opus 4.8 553b91015f fix(precision): keep BLOCK base and TEXT/MTEXT insertion points in f64 (#311)
Three commit helpers narrowed a typed/picked point back to f32 before it
reached the persisted entity, so the coordinate was quantized to the f32
grid (worse at large/UTM extents):

- Scene::create_block_from_entities took the base point as glam::Vec3, so
  the block's local-space translation and the replacement INSERT's
  insert point were f32. Param is now DVec3 (matching the sibling
  define_block_from_owned_entities); the command_driver handler no longer
  downcasts with as_vec3().
- The MTEXT and TEXT in-place editors stored `pos` as Vec3 and built the
  committed insertion_point from it, quantizing a new label's position
  and re-quantizing an existing one on edit. Both `pos` fields are now
  DVec3.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-12 00:34:26 +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 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 714fed7f00 fix(spline): terminate on finish and preview the real curve (#282)
on_enter/on_escape returned CmdResult::CommitEntity, which commits the
spline but keeps the command armed with its accumulated points — the
cursor stayed in spline mode and the next click began a new spline,
trapping the user in the command loop. Return CommitAndExit so Enter/Esc
finalize the spline and return to the selection state.

The in-progress preview drew only the straight control polygon. Sample
the actual degree-3 clamped B-spline (same control points + knot vector
build() commits) so the rubber-band traces the exact final curve.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-09 23:03:25 +03:00
Hakan SevenandClaude Opus 4.8 39c0a84c3c feat(arc): keep ARC Continue drawing arcs until ended (#327)
The Continue option committed a single tangent-continuing arc and left
the command. Now each picked end point commits its arc and re-anchors at
that arc's free end, so the user keeps drawing tangent-continuing arcs
until ending the run with Enter/Esc. A degenerate (collinear) pick stays
active instead of cancelling.

Because arcs now commit via CmdResult::CommitEntity (repeating) rather
than CommitAndExit, advance the ARC_CONT continuation anchor in the
CommitEntity arm too — guarded to line/arc so non-continuable repeats
(point/ray/spline) leave it untouched. This also lets a LINE run feed a
following ARC Continue.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-09 20:59:46 +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 30b907cdd6 perf(clipboard): keep large copy/paste responsive
Copying and pasting a whole drawing froze the app. Three synchronous,
main-thread hot spots scaled with the full document rather than the
selection:

- Copy centroid did a linear document scan per selected handle
  (wire_models_for / mirror_preview_parts), making copy O(H*N). Use the
  document's O(1) handle index instead.
- The paste ghost translated every point of every clipboard wire on each
  mouse move — O(points) per frame, which stalls placement of a huge
  selection. Above a point budget, rubber-band a single bounding-box
  outline instead (O(1) per frame).
- finalize_paste re-tessellated every solid in the document on each
  paste. add_entity already tessellates pasted solids, so switch to the
  incremental populate_missing_meshes_from_document.
2026-07-08 16:09:35 +02:00
Hakan SevenandClaude Opus 4.8 4164ef7111 fix(dimension): re-bake the *D block after in-place edits (#181)
OCS renders a dimension live from its definition points but exports a
baked *D block that other applications draw. An in-place edit (DIMTEDIT,
text override, DIMSPACE, any ReplaceEntity/ReplaceMany edit) changed the
dimension while leaving the old block, so the export kept the pre-edit
graphics — BricsCAD/ODA showed the stale dimension while OCS showed the
edit.

Add explode::invalidate_dim_block, which drops a dimension's baked *D
block (record + owned sub-entities, so no orphan blocks accumulate) and
clears block_name; bake_dimension_blocks then rebuilds a fresh block on
the next save. Call it from the in-place edit paths. The transform path
(MOVE/COPY/PASTE) keeps its own block in sync and is left untouched, so
dimensions loaded from other applications and not edited are preserved.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-08 10:23:48 +03:00
sLuCHa c20f277e54 fix(clipboard): paste copies dimensions correctly (#290)
Copy-paste (CTRL+C / CTRL+V) dropped dimensions. A dimension draws from
its baked *D block, whose geometry is in absolute WCS coordinates, and
the clipboard paste path added the dimension entity but never gave it its
own transformed block — unlike the in-drawing COPY, which does (#161). So
the pasted dimension kept pointing at the source block and rendered at
the original location instead of the paste point (same drawing), or had
no block at all (cross-drawing).

Capture each copied dimension's *D block into the clipboard, and on paste
build a fresh transformed copy of it and re-point the dimension's
block_name — for same- and cross-drawing pastes alike. Factored the
block-building out of clone_transformed_block into a shared
define_transformed_block, and the block snapshotting out of
capture_blocks into snapshot_block.
2026-07-07 15:53:21 +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 ab0a4f790c fix(insert): place block attributes by their ATTDEF geometry, follow the block (#255)
Inserting a block piled every attribute at the origin: each AttributeEntity
was built from just tag + value with default geometry. Carry the full
AttributeDefinition through the attreq flow and build attributes with
AttributeEntity::from_definition, then apply the block's insertion transform
so each lands in WCS at its proper position / alignment / rotation.

Attributes are stored in world space, so moving the block must move them
too. The MOVE command already did (acadrust translate_insert); apply_grip
(grip-drag) and the Position X/Y/Z property edit now translate the
attributes by the same world delta so they no longer stay behind.

Rotate/scale/mirror of a block still don't carry the attributes — acadrust's
transform_insert has no attribute pass — tracked separately.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 16:47:39 +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 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 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 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 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 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 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