* 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]>
* 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]>
* 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]>
* 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]>
---------
Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* 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]>
* 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]>
---------
Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* 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]>
* 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: a work budget on the record cursor, so counts promising missing groups can't make nested loops rescan a hostile record (with 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]>
* 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]>
A hatch inside a block was written with no source objects (the writer looked
for them in model and paper space only), and no boundary object carried the
hatch as a persistent reactor. AutoCAD keeps the two linked both ways; without
it a stretched dynamic block in AutoCAD moved the outline but not the fill.
Co-authored-by: Cody Vogt <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
A layout VIEWPORT's frozen layers were saved only as CADCraft's own
1001 CADCRAFT / 1000 VPFROZEN xdata. Other programs ignore that, so in
any third-party viewer every viewport showed every layer.
The writer now also emits the standard group 341: one soft pointer per
frozen layer to its LAYER table record handle, after 90/340 and before
the xdata. A frozen name with no LAYER record is skipped for 341 only
(the xdata still carries it). The reader keeps preferring the xdata; a
VIEWPORT without it (a file from another program) resolves its 341
handles through the LAYER records' group-5 handles, ignoring unknown
handles and keeping the 341 order.
Not covered: the DXF-to-DWG converter does not carry group 341 yet, so
a DWG keeps per-viewport freezing only through the CADCraft xdata.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Other applications' xdata on an entity was kept beside the drawing,
keyed by handle, so a copy (new handle) lost it: a copied hyperlink
(PE_URL) had no link, third-party tags stayed on the original only.
In AutoCAD xdata is part of the object.
Move entity xdata onto the entity as Common::xdata (the same raw
groups); every copy path already clones Common. The DXF reader puts it
there (handle references still noted for remapping), the writer takes
it from the entity, PDF hyperlinks come from the entities. EXPLODE's
pieces of an object don't inherit its xdata. Table record and block
xdata stay in Foreign.
Co-authored-by: Claude Opus 5.5 <[email protected]>
The Model layout was written with the default page setup, whose "layout"
plot area is plot type 5, "Layout information" (DXF Reference, PLOTSETTINGS
group 74). That means nothing for model space: opened in AutoCAD 2027, a
drawing saved by CADCraft had a blank "What to plot", no preview and no scale
in the Plot dialog for Model. Model space now gets what PLOT does for it: the
drawing extents scaled to fit (74 = 1, 75 = 0). DWG saves go through the same
writer; the reader already ignores the Model layout's plot settings.
Co-authored-by: EP995LAB <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
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]>
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, geometry, hardware, UI and localization parity docs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>