55 Commits
Author SHA1 Message Date
26179f7428 Draw object transparency on screen, in exports and in plots (#764)
* Draw object transparency on screen, in exports and in plots

Transparency was read and saved (entity group 440, layer xdata, ByLayer and
ByBlock) but never drawn. Display lists now carry each object's effective
transparency (its own, its layer's, or the block reference's), and:

- the canvas (GPU and CPU paths), ui.render and PNG/SVG exports blend it;
  TRANSPARENCYDISPLAY turns it off on screen and is now on by default, as in
  AutoCAD (initial value 1);
- plots draw it only when "Plot transparency" is on: a page setup option
  (PAGESETUP `plotTransparency`, a checkbox in the Page Setup Manager and the
  Plot dialog), overridable per plot, with PLOTTRANSPARENCYOVERRIDE (0 never,
  1 the setting, 2 always). PDF gets /ExtGState alpha, PNG plots blend, SVG
  uses opacity. Plots without it are byte-identical to before.

The public DXF Reference has no plot layout flag for plot transparency, so
the option is kept in the session and in JSON, not in DXF/DWG.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Transparency fixes: attributes, PDF overlaps, drag previews

- Attributes draw with their own transparency (their definition's, layer 0
  meaning the reference's layer, ByBlock the reference's) instead of the
  block reference's.
- PDF plots stroke overlapping transparent objects one by one, so they build
  up where they cross as in PNG plots; opaque output is unchanged.
- Drag and rubber-band previews follow TRANSPARENCYDISPLAY.
- Effective transparency is worked out only when it is drawn, reusing the
  layer the property lookup already found.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Transparency: one PDF stroke per transparent primitive; opaque rebuilt previews

- PDF plots never batch two transparent primitives into one path. Block and
  viewport contents share their reference's handle, so the per-object flush
  missed them; each primitive now blends on its own, as in PNG plots and on
  screen. Opaque output is unchanged.
- Previews the session rebuilds with the current properties (rubber bands,
  copies of objects) are drawn opaque again: they carry CLAYER's
  transparency, not the objects'. The GPU drag of the objects themselves
  keeps following TRANSPARENCYDISPLAY.
- Tests for attributes on a transparent reference layer, attributes without a
  definition, and the drag cache across the TRANSPARENCYDISPLAY toggle.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Selection like AutoCAD: colour highlight and the Options Selection tab

Selecting or hovering an object outlined each filled primitive (TrueType
glyphs, dimension and leader arrowheads, solid hatches, wide polylines)
by the rectangle around its triangles, and dashed everything else. A
selected dimension's text became a row of dashed boxes, one per glyph,
and its closed arrowheads small rectangles.

Selected objects are now redrawn in the selection colour: lines solid,
fills filled half see-through (so the lines and text on a highlighted
hatch stay visible), and the whole object reads as selected, text and
arrowheads included. A wipeout is always outlined, since filling it would
hide the drawing under it.

The Options dialog's Selection tab is laid out as AutoCAD's, with the
settings CADCraft can act on, each one AutoCAD's variable, kept in the
user profile:

- Selection modes: noun/verb selection (PICKFIRST), Shift to add
  (PICKADD), Object grouping (PICKSTYLE bit 1, now saved in the profile).
- Selection effect color (selectionColor, an interface preference; null
  is the theme's).
- Grips: Show grips (GRIPS) and the object selection limit for display of
  grips (GRIPOBJLIMIT, 100; 0 no limit): grips are neither drawn nor
  picked beyond it.
- Selection preview (SELECTIONPREVIEW): the object under the cursor is
  highlighted when a command is active (2) and when none is (1).
- Visual Effect Settings: the selection area effect (SELECTIONAREA,
  WINDOWAREACOLOR 150, CROSSINGAREACOLOR 100, SELECTIONAREAOPACITY 25)
  and the Selection Preview Filter (PREVIEWFILTER 3: 1 objects on locked
  layers, 2 xrefs, 4 tables, 8 multiline text, 16 hatches, 32 groups).

As in AutoCAD these are typed, not on the dialog: HIGHLIGHT (0 shows only
the grips of selected objects; not saved, so every session starts
highlighted), SELECTIONEFFECT (1 the selection colour, 0 dashed lines with
fills outlined along their real boundary) and PREVIEWEFFECT (the preview
dashed 0, thicker 1 or both 2).

Left for later, as CADCraft lacks what they control: lasso, grip tips and
menus, group grips, grips within blocks, associative hatch selection,
command and property preview, PICKDRAG, PICKAUTO and PROPOBJLIMIT.

DisplayList::fill_outlines returns the boundary of the region a filled
primitive covers as closed polylines (edges only one triangle has; the
slab filler's horizontal cuts, which need not line up between trapezoids,
are split at every x on their line and kept where an odd number cover
them), falling back to the bounding box over 600,000 vertices. The GPU
and CPU highlight paths and the drawing comparison overlay share the same
looks.

The new labels have Ukrainian rows (the required catalog); other
languages fall back to English.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: EP995LAB <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 06:12:48 -04:00
e74b276dfd In-place MTEXT editor with formatting toolbar and indent/tab ruler (#532) (#792)
The MTEXT text entry opens a text box on the canvas at the text's location and
width instead of a dialog: font, height, bold, italic, underline, overline,
colour, paragraph alignment, lists, tab and special characters, plus a ruler for
the paragraph's indents and tab stops. Text, per-character formats and paragraph
properties live in engine::mtext_edit (parse and write MTEXT codes, opaque codes
kept verbatim), tested headlessly.

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 06:07:55 -04:00
d826450e03 Block Editor on the current drawing; a drawing with authoring elements offers it (stacked on #833) (#885)
* Block Editor: BEDIT, BSAVE, BSAVEAS and BCLOSE for static blocks

BEDIT opens a block definition in the drawing's own tab with its base point
at the origin, and the block's objects are edited with the ordinary commands.
BSAVE writes them back into the definition, so every reference in the drawing
updates (one undo step in the drawing); BSAVEAS saves under a new name;
BCLOSE goes back to the drawing, asking whether to save unsaved changes.

- The drawing waits untouched while the editor is open (blockedit.rs): its
  undo history, selection and views come back on BCLOSE. Objects left
  unchanged are saved back exactly, so no coordinate drifts.
- Files save the drawing outside the editor; autosave keeps the editor's
  unsaved changes in the block; the close/quit prompt's Save puts them into
  the block first.
- Right-click a selected block reference: Block Editor. In the editor the
  canvas is tinted, the base point is marked, and a bar offers Save Block,
  Save Block As and Close Block Editor (also in the right-click menu). The
  Block tool set's Block Editor button is now live. BLOCKEDITOR sysvar.
- Layout and paper-space commands are refused while the editor is open; a
  block can't be saved to contain a reference to itself.
- Dynamic blocks: their data is kept; the editor says it can't edit their
  parameters yet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Edit Block Definition dialog; BEDIT can start a new block

BEDIT typed or started from a menu or the Tool Sets with no block reference
selected opens the Edit Block Definition dialog: the drawing's blocks sorted
by name, a preview and the description. OK opens the Block Editor on the
block named; a name no block has yet starts a new block, which BSAVE creates
(`bedit {name, create: true}`). A selected block reference still opens at
once, and JSON calls never open the dialog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Edit Block Definition: centred window, list box; gaps.md F3

The dialog opens centred on the window, the blocks are a full-width list
in a list box of fixed height, and gaps.md F3 mentions the dialog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* BEDIT: find dynamic block data on the block's BLOCK_RECORD

A dynamic block's ACAD_ENHANCEDBLOCK dictionary hangs off its BLOCK_RECORD
table entry, not the BLOCK entity, so the "parameters are kept" note never
showed for AutoCAD files. Regression test with the data laid out as an
AutoCAD DXF has it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* BEDIT: a dynamic block reference opens its dynamic block

A dynamic block reference with values of its own shows an anonymous block
(`*U12`), so the right-click Block Editor (and BEDIT on a picked or selected
reference) refused it and asked for a name. The anonymous block's record
names its dynamic block in AcDbBlockRepBTag xdata; BEDIT now opens that, as
AutoCAD does. Regression test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Block Editor on the current drawing; opening a drawing with authoring elements offers it

A dynamic block's own drawing (model space with authoring elements: its
block record's ACAD_ENHANCEDBLOCK) opened from the UI asks, as AutoCAD does,
"<file> contains authoring elements. Open in Block Editor?" Yes / No /
Cancel. Yes opens the Block Editor on <Current Drawing>: the drawing's own
objects, edited where they are; BSAVE keeps them, BCLOSE without saving puts
the drawing back. <Current Drawing> heads the Edit Block Definition list.
BSAVEAS is not available there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 06:02:48 -04:00
Brandon ThomasandClaude Opus 5.5 d80920cc25 docs: drop stale F1, F2 and F13 rows a merge brought back
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 06:00:42 -04:00
43972c82e8 Edit Block Definition dialog; BEDIT can start a new block (stacked on #824) (#833)
* Block Editor: BEDIT, BSAVE, BSAVEAS and BCLOSE for static blocks

BEDIT opens a block definition in the drawing's own tab with its base point
at the origin, and the block's objects are edited with the ordinary commands.
BSAVE writes them back into the definition, so every reference in the drawing
updates (one undo step in the drawing); BSAVEAS saves under a new name;
BCLOSE goes back to the drawing, asking whether to save unsaved changes.

- The drawing waits untouched while the editor is open (blockedit.rs): its
  undo history, selection and views come back on BCLOSE. Objects left
  unchanged are saved back exactly, so no coordinate drifts.
- Files save the drawing outside the editor; autosave keeps the editor's
  unsaved changes in the block; the close/quit prompt's Save puts them into
  the block first.
- Right-click a selected block reference: Block Editor. In the editor the
  canvas is tinted, the base point is marked, and a bar offers Save Block,
  Save Block As and Close Block Editor (also in the right-click menu). The
  Block tool set's Block Editor button is now live. BLOCKEDITOR sysvar.
- Layout and paper-space commands are refused while the editor is open; a
  block can't be saved to contain a reference to itself.
- Dynamic blocks: their data is kept; the editor says it can't edit their
  parameters yet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Edit Block Definition dialog; BEDIT can start a new block

BEDIT typed or started from a menu or the Tool Sets with no block reference
selected opens the Edit Block Definition dialog: the drawing's blocks sorted
by name, a preview and the description. OK opens the Block Editor on the
block named; a name no block has yet starts a new block, which BSAVE creates
(`bedit {name, create: true}`). A selected block reference still opens at
once, and JSON calls never open the dialog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Edit Block Definition: centred window, list box; gaps.md F3

The dialog opens centred on the window, the blocks are a full-width list
in a list box of fixed height, and gaps.md F3 mentions the dialog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* BEDIT: find dynamic block data on the block's BLOCK_RECORD

A dynamic block's ACAD_ENHANCEDBLOCK dictionary hangs off its BLOCK_RECORD
table entry, not the BLOCK entity, so the "parameters are kept" note never
showed for AutoCAD files. Regression test with the data laid out as an
AutoCAD DXF has it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* BEDIT: a dynamic block reference opens its dynamic block

A dynamic block reference with values of its own shows an anonymous block
(`*U12`), so the right-click Block Editor (and BEDIT on a picked or selected
reference) refused it and asked for a name. The anonymous block's record
names its dynamic block in AcDbBlockRepBTag xdata; BEDIT now opens that, as
AutoCAD does. Regression test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 06:00:15 -04:00
767576ec9e Block Editor: BEDIT, BSAVE, BSAVEAS and BCLOSE for static blocks (#824)
* Block Editor: BEDIT, BSAVE, BSAVEAS and BCLOSE for static blocks

BEDIT opens a block definition in the drawing's own tab with its base point
at the origin, and the block's objects are edited with the ordinary commands.
BSAVE writes them back into the definition, so every reference in the drawing
updates (one undo step in the drawing); BSAVEAS saves under a new name;
BCLOSE goes back to the drawing, asking whether to save unsaved changes.

- The drawing waits untouched while the editor is open (blockedit.rs): its
  undo history, selection and views come back on BCLOSE. Objects left
  unchanged are saved back exactly, so no coordinate drifts.
- Files save the drawing outside the editor; autosave keeps the editor's
  unsaved changes in the block; the close/quit prompt's Save puts them into
  the block first.
- Right-click a selected block reference: Block Editor. In the editor the
  canvas is tinted, the base point is marked, and a bar offers Save Block,
  Save Block As and Close Block Editor (also in the right-click menu). The
  Block tool set's Block Editor button is now live. BLOCKEDITOR sysvar.
- Layout and paper-space commands are refused while the editor is open; a
  block can't be saved to contain a reference to itself.
- Dynamic blocks: their data is kept; the editor says it can't edit their
  parameters yet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* BEDIT: find dynamic block data on the block's BLOCK_RECORD

A dynamic block's ACAD_ENHANCEDBLOCK dictionary hangs off its BLOCK_RECORD
table entry, not the BLOCK entity, so the "parameters are kept" note never
showed for AutoCAD files. Regression test with the data laid out as an
AutoCAD DXF has it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* BEDIT: a dynamic block reference opens its dynamic block

A dynamic block reference with values of its own shows an anonymous block
(`*U12`), so the right-click Block Editor (and BEDIT on a picked or selected
reference) refused it and asked for a name. The anonymous block's record
names its dynamic block in AcDbBlockRepBTag xdata; BEDIT now opens that, as
AutoCAD does. Regression test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 05:58:12 -04:00
3199de4951 Multiline attributes: read, draw and save their text (they were lost on save) (#854)
* Multiline attributes: read, draw and save their text

A multiline attribute (DXF attribute type 2, or 4 for a definition) has its
text as embedded MTEXT after its own groups (`101 Embedded Object`); its own
group 1 is empty. CADCraft read only the own groups, so the attribute drew
nothing, and a save wrote it back empty: its text was lost.

- `Attrib::multiline` keeps the multiline text; `text.value` holds its plain
  text, which ATTEDIT and the other value editors change; an edited value
  replaces the contents (`Attrib::multiline_text`).
- DXF read: ATTRIB and ATTDEF split at the marker; the MTEXT parsing is
  shared with MTEXT entities (`mtext_from`).
- Drawn as multiline text; ZOOM Extents counts it.
- DXF write (2007 and later; earlier versions get one line): attribute type,
  alignment point and the embedded MTEXT, only what differs from unrotated
  text, as AutoCAD writes it.
- INSERT and ATTSYNC give references the definition's multiline text placed
  with them; MOVE/ROTATE/SCALE, Properties rotation and scale, annotative
  scaling and FLATTEN carry it.
- Tests: synthetic multiline ATTDEF and ATTRIB (read, drawn, saved, edited,
  pre-2007 save, damaged embedded groups); engine INSERT, MOVE, ATTEDIT and
  ATTSYNC.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Multiline attributes: read MTEXT direction from its own groups (#667, as #834 does)

#834 moves these lines' fix into the MTEXT reader; the same fix in the shared
mtext_from keeps the two compatible.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 05:54:25 -04:00
48352f2baf Dynamic block grips: show and drag a dynamic reference's grips (stacked on #878) (#883)
* Dynamic blocks: read parameters and actions from AutoCAD files

A dynamic block's evaluation graph (kept as foreign data since #528) is read
into a typed model, `cadcraft_doc::dynblock`, without changing the kept
data: parameters (linear with value sets, point, visibility states,
lookup, base point; polar, XY, rotation, alignment and flip by geometry)
and actions (stretch with its frame and moved vertices, lookup tables;
move, scale, rotate, flip, array, polar stretch by kind), each with its
graph id and the parameters it follows.

- `blocks.dynamic {name}` returns the model as JSON (handles as hex);
  `blocks.list` marks dynamic blocks.
- Counts and values come from the file: capped by the groups present,
  non-finite numbers and missing members skipped.
- Tests: a synthetic DOOR block (visibility, linear list, stretch, lookup),
  its survival through a save, damaged data, and the engine commands.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic block references: their values and custom properties

A dynamic block reference's extension dictionary says which dynamic block it
shows (AcDbBlockRepresentation ▸ AcDbRepData) and the values it was given
(AppDataCache ▸ ACAD_ENHANCEDBLOCKDATA, one XRECORD per parameter graph id).
`dynblock::reference` reads them; a reference to the dynamic block itself
has the block's values.

- `dynblock::custom_properties`: the Properties palette's Custom section, in
  parameter order, only parameters shown in Properties: visibility state,
  lookup value worked out from the lookup tables (their "no match" name, e.g.
  Custom, when the inputs match no row), distances and point positions in
  drawing units (block units × the reference's scale), with the choices of
  each (states, lookup values, a distance's list).
- Properties: a dynamic block reference shows its dynamic block's name
  instead of its anonymous block's, and a read-only Custom section.
- `blocks.reference {handle}` returns the definition, values and properties.
- Tests: synthetic references (own values, an anonymous block scaled 2, a
  width no lookup row has), survival through a save, the engine command.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic block references: change the visibility state

Choosing a visibility state (or a lookup value whose tables set only
visibility states) in Properties ▸ Custom, or `blocks.reference.set
{handle, name, value}`, changes a dynamic block reference as AutoCAD does:

- The reference gets a new anonymous block: its current one with the
  state's objects shown and the parameter's other objects hidden (matched
  to the dynamic block's objects by their AcDbBlockRepETag element tags).
  The old anonymous block goes when nothing else uses it.
- AutoCAD's link data is written: the new block's record carries
  AcDbBlockRepBTag (the dynamic block's record), copied objects keep their
  element tags, and the reference's AcDbBlockRepresentation data records the
  value (an existing value record is updated; a new one copies another
  reference's record for the same parameter when there is one).
- An attribute whose definition the state hides is written hidden (60).
- Changes CADCraft can't make yet (distances, positions, lookups that set
  them) are refused with a message and change nothing; Properties shows
  only what can change as a dropdown (`CustomProperty::editable`).
- Tests: the synthetic DOOR references (own values and an anonymous block),
  survival through a save, refusals, and the engine command with undo.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic block references: distances, positions, stretch and move actions, lookup chaining

Linear distances (snapped to their value sets) and point positions can be
changed; the stretch and move actions that follow them run (listed vertices
or whole objects, multiplier and angle offset), parameters in an action's
selection are carried along, associative hatches refill from their
stretched outlines and the reference's attributes move with their
definitions. Lookup tables run both ways (a changed input gives its table's
lookup value, which sets the other tables' inputs). Connections are read in
either group order. Checked against AutoCAD's own references (*U10, *U12).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic block references: record changes in the history as AutoCAD does; full value data for new references

A changed reference's ACAD_ENHANCEDBLOCKHISTORY says what was done to it: a
visibility state or lookup value by property name, a distance or position as
the drag of its grips (GRIPLOC, how far each grip is from where the dynamic
block has it). A reference that had no data gets a record for everything
other references of the same dynamic block have, with its own values.
Templates come only from references of the same dynamic block; anonymous
copies have no units; boundary reactors follow the copies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic block history: record only the grips a change moves

AutoCAD's history holds the user's own drags; a grip that only moved with
another parameter (a stretch carrying it) gets no entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic block history: a distance by name, as typed in Properties

AutoCAD records a distance typed in Properties as the parameter's name and
value in block units (1071 id, 300 Distance2, 40 40.0), replacing drags of
its grips; positions stay grip drags.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* DWG: dynamic block lookup parameters keep their name and lookup action

AutoCAD writes a lookup parameter's subclass marker as
AcDbBlockLookUpParameter; the DWG bridge reads AcDbBlockLookupParameter and
matches it exactly, so the parameter's name (303) and lookup action (94)
were lost in DWG files and AutoCAD's lookups stopped working (a visibility
change no longer resized the block). The bridge DXF now uses its spelling.

Found by driving AutoCAD on CADCraft's output.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic blocks: carried parameter points are in a stretch frame where the block has them

AutoCAD evaluates actions from the dynamic block itself, so a parameter point
another action has moved out of a stretch frame still follows it. Testing the
current position instead changed a stretched label's width when its
visibility changed (Distance2 183.75 became 184.45; AutoCAD keeps 183.75).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic block grips: show and drag a dynamic reference's grips as AutoCAD does

A selected dynamic block reference shows its grips: an arrow at each end of a
distance, a square at a position, a down-pointing triangle for a visibility
or lookup list, only those its visibility state lists. Dragging an arrow
changes the distance along the parameter (snapped to its values; a typed
number sets it), a square moves the position, a list grip offers its
choices; actions and lookups follow, with a live preview.

doc: dynblock::grips, drag_grip; grip objects by graph id.
engine: blocks.reference.grips, blocks.reference.grip, Session::start_dyn_grip.
ui: canvas dynamic grips (GRIPDYNCOLOR-like token), list popup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Tests: read the shared DOOR fixture with LF line ends whatever the checkout gives

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic blocks: carried parameter points are in a stretch frame where the block has them

AutoCAD evaluates actions from the dynamic block itself, so a parameter point
another action has moved out of a stretch frame still follows it. Testing the
current position instead changed a stretched label's width when its
visibility changed (Distance2 183.75 became 184.45; AutoCAD keeps 183.75).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Dynamic grips: Ukrainian labels for the new commands; copy the hot grip instead of cloning

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 05:47:27 -04:00
e773d93164 HATCHEDIT: refuse an explicit bad scale or angle before editing anything (#724) (#777)
* HATCHEDIT: refuse an explicit bad scale or angle before editing anything (#724)

run_hatchedit read `scale` and `angle` leniently (a non-positive scale or a
non-numeric value was skipped), so such a request reported success, added an
undo step and cleared the redo history, applied its valid companion property
or recreated the boundary. HATCH already refuses a non-positive scale. The
numbers are now checked first with the same size_param rule; `angle` must be
a number; absent and null still mean "leave it".

* Docs: update the progress docs for this change

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 05:37:45 -04:00
8166e4a809 AutoLISP: load and run .lsp routines (fixes #758) (#855)
* AutoLISP: load and run .lsp routines (#758)

CADCraft runs AutoLISP routines. `.lsp` files load with APPLOAD, (load)
or by dropping them on the window, and at startup from the support folder
(acad.lsp once per session or per drawing with ACADLSPASDOC, acaddoc.lsp
per drawing, then S::STARTUP; never from a drawing's own folder).
(defun c:NAME …) defines commands, (autoload) defers loading to first
use, and (expression) and !variable work at the command line and at
prompts. docs/autolisp.md lists what is supported.

The interpreter (crates/engine/src/lisp) reads source with its own
stack and evaluates on an explicit-stack machine, so a routine can stop
for input (getpoint, pause in command) and carry on later on one thread,
deep recursion is an error rather than a crash, and long loops run a
slice per frame (Esc stops them). It follows AutoLISP: 32-bit wrapping
integers, dynamic scope, *error*, vl-catch-all-apply, AutoLISP's error
wording. About 200 built-ins cover the language, command (with pause and
commands left open), entity lists (entget/entmake/entmod/entdel/entlast/
entnext/handent, through the DXF reader and writer's groups, exposed by
cadcraft-io and handed to the engine through a hook), ssget with modes
and filters, tables, system variables, get… input with initget, and
units. A routine is one undo step; each drawing has its own symbols.

Also: keywords typed as _KEYWORD match at prompts; TEXT run by a
routine's command takes one line; DIMENSION records carry their
measurement (42); Prompt::raw shows a get… prompt as written; the
interface asks Session::prompting() (a command's or a routine's prompt)
where it asked whether a command runs, so clicks, right-click and Delete
work at a routine's prompts; getcorner and based get… prompts drag a
rubber band.

Tests: the language, errors, startup files, interactive input, hostile
input (lisp/tests.rs); the #648 corpus, 18/18, when CADCRAFT_LISP_CORPUS
points at it (tests/lisp_corpus.rs); drop, frames and canvas clicks in
the app (ui-egui/tests/lisp_files.rs).

* AutoLISP: bound memory, drop nested values iteratively, guard the per-frame tick

- Cons cells drop with a work list over both car and cdr (and function
  bodies), so a list nested a million deep in its car no longer overflows
  the stack when it goes away.
- Memory caps so a runaway routine fails with a LISP error instead of an
  uncatchable out-of-memory abort: at most 5,000,000 live cons cells
  (counted in Value::cons / Drop, checked every step; cells the drawing's
  variables already held at the run's start don't count against it), no
  string a built-in makes longer than 8 MiB (strcat checks before joining,
  printed text is cut), and a 2 GiB per-run budget for new strings,
  printed text and files read.
- Session::lisp_tick runs under catch_unwind, and so does every slice of
  the LISP machine: a panic ends the routine with "internal error", puts
  the drawing back and leaves the session usable.
- Regression tests for each (deep car nesting, nested functions, the cell
  cap, string length and budget, a panicking built-in in a blocking run
  and in a ticked one).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 05:33:57 -04:00
64b7825d08 Property-based and hostile-input tests for geometry numerics (gaps.md S6) (#733)
* Property-based and hostile-input tests for geometry numerics (gaps.md S6)

A deterministic xorshift64* generator (no new dependency) drives a few
thousand cases per property in crates/geom/src/property_tests.rs:

- line/line: hits lie on both lines, parameters reproduce the point,
  swapping the operands gives the same point, finite results agree with
  the infinite parameters
- line/circle and circle/circle: hits lie on both curves, results are
  symmetric, properly crossing circles match the closed form; hostile
  inputs (NaN, infinities, subnormals, raw bit patterns, 1e300) never
  panic and the guarded functions return only finite hits or none
- segments: extending only adds hits
- closest points: fixed point of closest, perpendicular inside the
  segment, endpoints outside
- arcs: at(0)/at(1) are the endpoints at radii from 1e-6 to 1e6; the
  three-point arc keeps {start, end} = {p1, p3} and contains p2 (the
  CCW-only storage swaps the pair when p2 is on the clockwise side, as
  DXF stores arcs) and matches the circumradius closed form; collinear
  inputs are rejected
- bounds and vector identities
- known answers at 1e6-1e7 coordinates hold to 1e-9 relative

gaps.md S6 removed and geometry-parity.md's Tests section updated in
the same change.

* docs: drop the sentence the new numerics tests make stale

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 05:16:44 -04:00
Brandon ThomasandClaude Opus 5.5 bb7e1046be docs: gaps F1/F2 rows after the STRETCH and REFEDIT merges (one row each)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 04:47:56 -04:00
f807e65e30 STRETCH: polygon and fence selection (#892)
At "Select objects", CP and WP take a polygon (Enter closes it) whose
inside marks the vertices that move, as a crossing window does; F takes a
fence, and what it crosses moves whole, like objects picked one at a
time. BOX works too, Undo takes back the last point, and the polygon or
fence is drawn while its points are placed.

The JSON form takes `polygon` or `fence` in place of `window`; both are
capped at the selection modes' point limit and refuse non-finite points.

Co-authored-by: Chanthawat Kiriyadee <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 04:47:19 -04:00
69f2b9724a Save xref layer overrides with the host, in DXF and DWG (fixes #886) (#889)
A colour or freeze set in the host on an xref's layers (xref|layer) was lost on save:
the saved view dropped every xref-dependent symbol, layers included, and the DWG save goes
through the same DXF tags. The reader already keeps such layers when the xref loads.

The xref's layers are now written as xref-dependent LAYER records (DXF Reference, LAYER
group 70: 16 externally dependent, 32 resolved), as AutoCAD does with VISRETAIN = 1, with
the xref linetypes they use (same flags): without them a DWG falls back to Continuous.

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 04:46:45 -04:00
5fd23f75fc Command line: a bare system variable name runs SETVAR on it (part of U10) (#799)
* Command line: a bare system variable name runs SETVAR on it (part of U10)

Typing ORTHOMODE, PICKADD and the like at the command line returned
"unknown command". Session::start now falls back to SETVAR with the name
already entered when the text is not a command but is a known variable;
dash, dot and underscore prefixes never mean a variable, and real
commands still win. Read-only variables print their value, and the
transparent form ('OSMODE) resumes the interrupted command.

Checked: fmt, clippy, test, assets, layers and wasm pass; also driven
through the control channel in the running app.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* Tests: every variable name, command collisions and hostile input for bare SETVAR

Covers each variable SETVAR lists: a name that is not a command starts SETVAR
(or prints a read-only value), a name that is also a command keeps the command,
and odd input (lone apostrophes, NUL, wide characters, a 1 MiB name) never
panics and leaves the session usable. Disabling the fix makes all three new
and old tests fail.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

---------

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 04:46:00 -04:00
Valentin KleinandClaude Sonnet 5.5 9464945f82 REFEDIT, REFSET and REFCLOSE: edit block references in place (#818)
* REFEDIT, REFSET and REFCLOSE: edit block references in place (#813)

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* Move REFEDIT translation lines into the middle of the catalogs (avoids PR merge conflicts)

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

---------

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
2026-10-13 04:37:31 -04:00
Valentin KleinandClaude Sonnet 5.5 3dd678ff3d MLEADEREDIT, MLEADERALIGN, MLEADERCOLLECT (#816)
* MLEADEREDIT, MLEADERALIGN and MLEADERCOLLECT (#812)

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* Move MLEADER translation lines away from the end of the catalogs (avoids PR merge conflicts)

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

---------

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
2026-10-13 04:37:26 -04:00
Valentin KleinandClaude Sonnet 5.5 ea768ee92a docs: gaps.md FF14 lists what is left of SVG import (#814)
SVG import landed in #636 but its row still said it was missing.

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
2026-10-13 04:37:21 -04:00
5183f63bc5 Hatch and Gradient dialog (create and edit) (#753)
* HATCH/GRADIENT: points and objects in one call; GRADIENT name and centered

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* HATCHEDIT: gradient parameter (make or change a gradient fill)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* HATCHPICK: pick hatch boundaries, an origin or hatches for the dialog

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hatch dialog: state, defaults and the OK command call

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hatch dialog: pick points, objects, origin or hatch and come back

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hatch and Gradient dialog window (pattern swatches, gradient, origin, boundaries, options)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* HATCH, GRADIENT and HATCHEDIT open the hatch dialog; double-click a hatch to edit; HPDLGMODE

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hatch dialog: keep the window to its content (OK and Cancel stayed off screen); double-click test

A vertical separator between the two columns took the height available, so the window grew
every frame until its buttons left the screen. Found in the running app.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Docs: hatch dialogs in gaps, target-app parity and UI parity

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hatch dialog review fixes

- Double-clicking a hatch at its centre opened the dialog and also started a grip edit.
- Edit mode no longer clamps an out-of-range scale or angle into a change nobody made.
- HPORIGINMODE outside 1-5 means the current origin, as HATCH reads it.
- Picks made in one space are dropped when the dialog comes back in another (layout tab).
- A gradient hatch's Pattern tab offers the current pattern (HPNAME), not SOLID.
- Ukrainian: "Default to boundary extents" said "outside the boundary".

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 04:34:09 -04:00
LionandClaude Opus 5.5 935cb98eed SCRIPT command: run .scr files from the command line, menus and JSON (gaps.md E2) (#693)
Squash of PR #693 by @Lion-1209.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 03:13:51 -04:00
Valentin KleinandClaude Opus 5.5 3aeedf4856 docs: refresh gaps.md and parity docs for landed features (#625)
Squash of PR #625 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 20:11:37 -04:00
Gert Pellin - GPEandClaude Opus 5.5 f7c6293d09 feat: SVG import — read SVG and SVGZ into native entities (#636)
Squash of PR #636 by @switch87.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 20:11:22 -04:00
arekownandClaude Opus 5.5 d7f3e48362 Add German (de) interface language (#573)
Squash of PR #573 by @arekown.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 14:36:40 -04:00
Valentin KleinandClaude Opus 5.5 d0dd5cfe16 Support files: DWT templates for NEW/QNEW, SAVEAS .dwt, user alias files (fixes #552) (#562)
Squash of PR #562 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 13:12:32 -04:00
Valentin KleinandClaude Opus 5.5 0135e793f7 Constraints: CONSTRAINTINFER while drawing and editing, DOF status on the canvas (fixes #546) (#555)
Squash of PR #555 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:48:06 -04:00
Valentin KleinandClaude Opus 5.5 9ca0dcf7d4 Tables: table breaking and CSV data links (fixes #534) (#553)
Squash of PR #553 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:47:39 -04:00
Valentin KleinandClaude Opus 5.5 a4504cc098 DXF: keep what we don't model on save — unknown entities and objects, foreign xdata, extension dictionaries, reactors, colour books (fixes #528) (#545)
Squash of PR #545 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:46:58 -04:00
Valentin KleinandClaude Opus 5.5 afae36cb60 REGION: region entity, UNION/SUBTRACT/INTERSECT, MASSPROP (fixes #510) (#526)
Squash of PR #526 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:12:09 -04:00
Valentin KleinandClaude Opus 5.5 bc7744b27b Xrefs: External References palette and XREF (fixes #525) (#533)
Squash of PR #533 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:11:52 -04:00
Valentin KleinandClaude Opus 5.5 78944a8341 DXF: reopen-and-save keeps one *D block per dimension and stable handles (fixes #495) (#508)
Squash of PR #508 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:47:11 -04:00
Valentin KleinandClaude Opus 5.5 7ecac5fb66 Tiled model viewports: VPORTS, saved viewport configurations, DXF VPORT table (part of #463) (#504)
Squash of PR #504 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:43:11 -04:00
Valentin KleinandClaude Opus 5.5 b4b62e4cf0 FILLET and CHAMFER with polyline segments (fixes #466) (#502)
Squash of PR #502 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:42:41 -04:00
Valentin KleinandClaude Opus 5.5 c0a19dfb9e FILLET with elliptical arcs and splines (part of #466) (#470)
Squash of PR #470 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:41:59 -04:00
Valentin KleinandClaude Opus 5.5 ea0f866c7c EXTEND, JOIN and LENGTHEN on elliptical arcs and splines (part of #466) (#469)
Squash of PR #469 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:41:45 -04:00
Valentin KleinandClaude Opus 5.5 d6aa802c75 Hatch: HPGAPTOL, pattern origin, separate hatches, island style; HATCHEDIT options and -HATCHEDIT (fixes #451) (#457)
Squash of PR #457 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:29:05 -04:00
Valentin KleinandClaude Opus 5.5 61812b25de OVERKILL: tolerance, ignored properties, overlapping and end-to-end lines and arcs, polyline segments (fixes #439) (#452)
Squash of PR #452 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:28:18 -04:00
Valentin KleinandClaude Opus 5.5 6f1ca71e98 PolarSnap, relative polar angles, Extension on ellipse and spline ends, tracking options in Drafting Settings (fixes #441) (#450)
Squash of PR #450 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:28:02 -04:00
Valentin KleinandClaude Opus 5.5 9427b77209 Temporary tracking point TT and tracking TK at every point prompt (part of #441) (#445)
Squash of PR #445 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:23:45 -04:00
Valentin KleinandClaude Opus 5.5 25c9658908 Select objects: BOX, AUto, SIngle, Multiple, Group and Undo keywords (fixes #428) (#438)
Squash of PR #438 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:23:03 -04:00
Valentin KleinandClaude Opus 5.5 b3a73626b2 SAVEAS version and binary DXF, default save version (fixes #421) (#431)
Squash of PR #431 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:19:47 -04:00
Valentin KleinandClaude Opus 5.5 9e3379882f TOLERANCE: GD&T feature control frames (fixes #420) (#427)
Squash of PR #427 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:19:32 -04:00
Valentin KleinandClaude Opus 5.5 475a70e573 Groups: PICKSTYLE group selection, Ctrl+Shift+A and -GROUP options (fixes #417) (#424)
Squash of PR #424 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:19:04 -04:00
Valentin KleinandClaude Opus 5.5 64eed455e5 Autosave (SAVETIME, SAVEFILEPATH) and Drawing Recovery after a crash (fixes #406) (#416)
Squash of PR #416 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:18:49 -04:00
Valentin KleinandClaude Opus 5.5 d74f7a4108 Right-to-left drawing text: bidi and Arabic-script shaping (Persian, Arabic, Urdu) (#315)
Squash of PR #315 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 11:13:29 -04:00
Valentin KleinandClaude Opus 5.5 fc4220e9b7 Plot styles: named styles (STB), PLOTSTYLE, STYLESMANAGER and table files (part of #410) (#418)
Layers and objects get a PlotStyle property (ByLayer/ByBlock/name) applied
through named tables (built-in default.stb and monochrome.stb, our own
definitions). DXF keeps the names in the ACAD_PLOTSTYLENAME dictionary of
ACDBPLACEHOLDER objects with group 390 on layers and objects; tables kept in
the drawing travel in a CADCRAFT_PLOTSTYLES XRECORD. PLOTSTYLE sets an object's
or the current (CPLOTSTYLE) style; STYLESMANAGER lists, creates, edits, deletes,
loads and saves tables in our own JSON format (docs/plot-styles.md). A Plot
Style Manager dialog edits a table's styles and the layout's table; the
Properties palette shows the current plot style and table.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 19:21:24 -04:00
Valentin KleinandClaude Opus 5.5 87d1e28345 MLEADER: spline and no-leader types, block content (fixes #408) (#419)
MLEADER's Options answered "not available yet" for Leader type sPline and
None and for Content type Block. Spline leaders now draw as smooth curves
through their points (DXF LEADER path type 72 = 1); None places only the
content at one picked point; Block asks for a block name and how it
attaches (Center extents or Insertion point) and inserts it at the end of
the landing. MLeader gains `spline` and `block` (serde defaults); DXF stays
LEADER + MTEXT/INSERT. PURGE, RENAME, copy/paste, FLATTEN, grips and
extents follow the block content. The choices are remembered like the
other MLEADER options. docs/gaps.md: top-10 row 5, F1 and F10 narrowed.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 19:20:02 -04:00
Valentin KleinandClaude Opus 5.5 ee34f423cc Plot styles: colour-dependent tables apply to PDF plots and layouts (part of #410) (#412)
A PlotStyleTable model in doc with our own built-in tables (default.ctb keeps
object properties, monochrome.ctb prints black, grayscale.ctb prints the grey of
each colour's luminance). The render builder resolves each object's style by its
index colour (true colours by the nearest index) and applies colour, screening,
linetype and lineweight. PLOT/EXPORTPDF use the page setup's table (or the
plotStyleTable option); layouts with Display plot styles (PAGESETUP
displayPlotStyles, DXF plot flag 2) show it on screen and in PNG/SVG exports.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 19:19:48 -04:00
Valentin KleinandClaude Opus 5.5 f537377355 AUDIT and RECOVER: find and fix database errors, open damaged drawings repaired (part of #406) (#411)
AUDIT checks for missing layers, linetypes, styles and block definitions,
current-style variables naming missing entries, invalid geometry (NaN or
infinite numbers, zero-length lines, zero-radius circles and arcs),
block definitions that contain themselves, duplicate handles, and
references to missing objects (dimension associations, groups,
constraints, viewport layer lists). It asks "Fix any errors detected?
[Yes/No] <N>:"; the JSON form `audit {fix?}` returns a structured
report. RECOVER opens a file through the read hook and applies AUDIT's
fixes. A question prompt now shows its answers without "or".

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 19:19:44 -04:00
Valentin KleinandClaude Opus 5.5 242a00923d Object snap overrides at point prompts, Shift/Ctrl+right-click snap menu, Tab cycling (fixes #393) (#409)
Typed END, MID, CEN, GCEN, NOD, QUA, INT, APP, EXT, INS, PER, TAN, NEA,
PAR (any start of the word from three letters, with `_`/`'` prefixes)
and NON push a one-pick snap frame on the point-modifier layer: the
prompt reads "Endpoint of:", a typed point is snapped with that mode
alone within the aperture (or rejected with "No Endpoint found for
specified point."), and the canvas snaps the cursor with it
(Session::snap_override). A command's own keyword still wins (ARC/ZOOM
`cen`); `_cen` always snaps. Overrides nest with FROM and the filters.

Shift+right-click or Ctrl+right-click on the canvas opens the object
snap menu (From, Mid Between 2 Points, Point Filters, the snap modes,
None, Osnap Settings...), whose items type the same overrides. Tab over
a snap marker with nothing typed cycles the candidate snaps
(pointmod::snap_candidates: the running snap first, then the closest of
each mode).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:44:21 -04:00
Valentin KleinandClaude Opus 5.5 2f9ebc6f63 PLINE arc options and Length, XLINE polyline segments (fixes #397) (#407)
PLINE arc mode answered "not available yet" for Angle, CEnter, Direction,
Radius and Second pt, and line mode for Length. Each now asks its questions
(included angles and directions read the drawing's angle settings) and adds
the segment. XLINE's options were already in draw2's XlineM2; the dead
keyword stub in draw.rs's RAY machine goes, and Offset/Reference use the
polyline segment picked rather than the first one.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:44:08 -04:00