Commit Graph
131 Commits
Author SHA1 Message Date
NetterSilverandClaude Haiku 5.5 94aad92f1d Scale SVG export precision and point size to the drawing's extents (#1495)
Found while testing crates/io/src/svg.rs:50-60 (export_list: viewBox, stroke-width, circle r and coordinates all formatted with {:.6})

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-19 02:32:58 -04:00
3fa7275e4f DXF: $ANGBASE is in degrees (#1234)
AutoCAD writes $ANGBASE in degrees (a drawing whose angles run clockwise from
north has 90.0); CADCraft read and wrote it as radians, so such a drawing's
angle base came in as 90 radians (about 116.6 degrees), turning typed angles,
polar tracking and angles shown in Properties. The reader now converts to
radians (the variable's unit), the writer back, and the DWG reader's hand-off
of the value in DXF form gives degrees.

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:31:12 -04:00
Marco Arnulfo 493addc2dd DXF files that declare no units stay unitless when saved again (fixes #1209) (#1210) 2026-10-19 02:31:01 -04:00
aldozarandClaude Opus 5.5 691bdbe0b8 Layouts in millimetres plot in millimetres whatever MEASUREMENT says (#1058) (#1085)
Paper-space units followed the drawing's MEASUREMENT only, so a layout
whose page setup is in millimetres (plot paper units 1), common in
drawings from AutoCAD with MEASUREMENT 0, was taken as inches: its PDF
drew everything 25.4 times too large, off the page.

PageSetup gains `paper_mm`, read from LAYOUT group 72 = 1; 72 = 0 still
leaves the units to MEASUREMENT, since earlier CADCraft versions wrote 0
for every layout. The layout's sheet, its PDF/raster plots, PREVIEW and
the pens of what its viewports show use the layout's units, and the
writer stores 72 = 1 for layouts in millimetres (it wrote 0 always).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:29:03 -04:00
aldozarandClaude Opus 5.5 162d5c6820 DWG: entities of a second layout stay on it in AutoCAD (#1057) (#1080)
acadrust writes every paper-space entity with entity mode 1 ("paper
space"), including those of *Paper_Space0, *Paper_Space1… and relies on
each block's entity list; AutoCAD reads mode 1 as the *Paper_Space
block's, so what was drawn on a second layout (and its viewports, in
drawings from AutoCAD with several layouts) landed on the first one, and
the second stayed empty. The DWG bridge now writes those entities owned
by their block (entity mode 0 with the owner handle), as the public DWG
specification describes; the first layout's entities stay mode 1.

The io tests read CADCraft's DWG back with acadrust (a dev-dependency,
the same version as cadcraft-dwg's) to check the modes as written.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:28:58 -04:00
aldozarandClaude Opus 5.5 632c2d4f1f DIMASSOC: reference polyline vertices by segment, as AutoCAD does (#1049) (#1064)
* DIMASSOC: no standard reference to a polyline's vertex or segment (#1049)

A dimension on a polyline vertex was written with an AcDbOsnapPointRef
naming the polyline but no subentity (groups 73 and 91 both 0); AutoCAD
then resolved it to the polyline's first segment when it regenerated
the dimension, and the value changed without warning (210 became 5).
Such points (LWPOLYLINE, 3D polyline, MLINE, also as the second object
of an intersection) are now left out of the DIMASSOC object, as the
report suggests: AutoCAD sees them as non-associative and the values
stay right. CADCraft keeps every link through its own xdata, and
references to single-curve objects (lines, arcs, circles, points) are
written as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* DIMASSOC: reference polyline vertices by segment, as AutoCAD does

Following the black-box observation of AutoCAD 2026 in the PR review: a
polyline vertex is referenced as an end of one of its segments,
subentity type 73 = 2 (edge) and the 1-based segment in 91 (segment k
runs from vertex k-1 to vertex k; a closed polyline's closing segment is
the last), with osnap 1 / parameter 1.0 for a segment's end and 13 / 0.0
for its start. Vertex 0 is written as the start of segment 1, vertex k
as the end of segment k, falling back to the other neighbour when that
one is an arc; a vertex between two arcs (where AutoCAD writes an angle
parameter, not yet tested) still gets no reference, nor do 3D polylines
and multilines.

Reading uses the segment and osnap for polylines: AutoCAD leaves the
reference point at 0,0, so the nearest-vertex fallback attached
AutoCAD's associative dimensions on polylines to the vertex nearest the
origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:28:52 -04:00
aldozarandClaude Opus 5.5 f0e4fef003 AutoCAD interop: justified text, DIMPOST, current dimension style, *D block colours (#1062)
* AutoCAD interop: justified text, DIMPOST, current dimension style, *D block colours

What AutoCAD takes from CADCraft's DWG/DXF as it is:

- Justified TEXT/ATTRIB/ATTDEF (#1050): group 10 is now where the
  baseline starts, from the alignment point and the text's width, not
  the alignment point itself; AutoCAD drew centred and right-aligned
  text from DWG starting at its alignment point. A stored point close to
  CADCraft's own (AutoCAD's, exact for its fonts) is kept.
- DIMPOST (#1053) is written even when empty: the DWG bridge defaulted a
  missing one to "<>", and AutoCAD then dropped the "R" of radius
  dimensions when it regenerated them.
- The current dimension style (#1051): the DXF header now carries its
  DIM* variables (AutoCAD draws new and updated dimensions from them);
  the DWG bridge points $DIMSTYLE at the style by name (its handle was
  renumbered, so the writer fell back to Standard), and reading a DWG
  gives the current dimension and text styles their names back.
- *D blocks (#1052): lines, arrows, extension lines and text take the
  style's DIMCLRD/DIMCLRE/DIMCLRT instead of ByBlock.

#1054 (group 42 of a rotated linear dimension) was already fixed on
main by #855; acad_interop.rs keeps it covered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Test: angular dimensions store their angle in radians (group 42)

Three-point (60°, 174°) and two-line (120°) angular dimensions, read back
from the DWG as DXF. Already right on main since #855; this guards it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Justified text made here: Left Middle and Left Bottom write their baseline start too

text_insert kept a stored insertion point that was within 0.1 x an
estimated width + 0.5 x height of CADCraft's baseline start, taking it for
AutoCAD's own. The width was estimated from the alignment point's offset,
which for Left Middle is only h/2 (vertical), so a text made here, whose
stored point is the alignment point, passed and kept it: AutoCAD drew it
half a height high (Left Bottom: a third). Reported with a test by
@lee-cyrille-maskell.

A stored point equal to the alignment point is now always stale. Otherwise
the offset is split along and across the baseline: along it the same slack
as before (a tenth of the text's width plus half its height, as fonts and
their substitutes differ most in width), across it a fifth of the height.
Multiline attribute values keep the distance-only check, since their lines
are placed by the embedded MTEXT.

On 12 drawings made in AutoCAD (1,055 justified texts) the insertion points
written are the same as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:28:47 -04:00
Gert Pellin - GPE baa9c00fb7 SVG import: name what was left out instead of dropping it silently (#627) (#1016)
Text, raster images, <use> copies, <style> sheets and rounded rectangle
corners aren't read yet. They were dropped without a word; the reader now
counts them and adds one read note (the channel DWG reading already uses),
which OPEN echoes and returns as a warning. Content that isn't drawn (in
<defs> or hidden) isn't counted.
2026-10-19 02:28:29 -04:00
8cd0d46d58 Zoom and pan regenerate in the background instead of freezing the window (#903)
* Zoom and pan regenerate in the background instead of freezing the window

A rebuild that only the view asks for (a new 2x zoom band, a pan out of the
built area) now runs on a worker thread while the canvas keeps drawing the
previous display list, scaled by the view uniform. The worker builds the list,
converts it to vertex data and creates the GPU buffers, so the frame that swaps
it in only swaps buffers. One build runs at a time; a result from an older
drawing revision is dropped. Edits, the first build and the web build (no
threads) still rebuild synchronously.

ZOOM no longer calls touch(): it changes the view, not the drawing, and the
bumped revision made every typed zoom a full blocking rebuild (wheel zoom
never did). REGEN, REGENALL and layout switches still touch.

ui.screenshot waits (up to 6 s) for a background rebuild so agents capture
the new view; ui.inspect perf reports "building".

On a hatched 12k-entity DWG (~340k primitives) the longest UI stall per zoom
went from 2-4.5 s to 26-64 ms.

Also: cargo fmt of a comment in engine/src/prompt.rs that was failing the fmt
gate on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cancel a background rebuild when the view moves on

A view that needs a different display list now cancels the build in flight
and starts one for itself, instead of waiting for the outdated build to land
and only then starting the next. A long scroll through many zoom bands builds
only for where it stops: the list lands about one build after the last step
(2.0-2.3 s on a hatched 12k-entity DWG) instead of up to two, and an outdated
build no longer keeps a core busy.

cadcraft_render::Options gains `cancel` (an Arc<AtomicBool>); a set flag stops
the build at the next entity (block contents included) and the hatch sweep at
its next pattern line, returning a partial list the caller discards. The
canvas's Job sets its flag when dropped, so replacing or dropping a job
cancels it; a cancelled worker sends nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cargo fmt: crates/doc/src/dynblock.rs (fails the fmt gate on main)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 06:58:16 -04:00
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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: EP995LAB <293597958+EP995LAB@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 06:12:48 -04:00
62fb5ae79a Attributes keep Verify, Preset and Lock position (stacked on #854) (#876)
* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* Attributes keep Verify, Preset and Lock position

ATTDEF/ATTRIB flags 4 (verify) and 8 (preset) and Lock position (group 280
after the tag, DXF 2007 and later) were dropped on save. Without Lock
position AutoCAD lets an attribute be dragged out of its reference, and its
dynamic block actions no longer carry it. A reference's attributes take Lock
position from their definitions, as AutoCAD saves them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:56:05 -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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:47:27 -04:00
0d138c2954 Dynamic block references: distances, positions, stretch and move actions, lookups both ways (stacked on #860) (#878)
* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:45:56 -04:00
f8521ab299 Dynamic block references: change the visibility state (stacked on #853) (#860)
* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:44:18 -04:00
73b8edac61 Dynamic block references: custom properties in Properties (stacked on #838) (#853)
* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:42:37 -04:00
fbc7037a10 Dynamic blocks: read parameters and actions from AutoCAD files (#838)
* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:40:12 -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 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:33:57 -04:00
dc92f38772 DXF: count the records the reader can't place; info reports them, convert warns (part of #865) (#872)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:31:58 -04:00
1c4d0a543f Open damaged DWGs partially and say what was left out (#800) (#859)
Bump acadrust to 0.6.4 and read DWG in its failsafe mode: an object
record the reader can't decode is skipped instead of failing the whole
file. The drawing's new read_notes (never saved) say how many records
were left out and of which types; OPEN echoes them, returns them as
`warnings`, and the app shows the first in the status bar. A file whose
objects stream can't be read at all is still an error.

acadrust 0.6.4 writes a merged table cell's anchor with 173=0 and the
span, so the DXF reader now takes a merge from the span alone. Unknown
records the DWG writer can't write back are dropped before writing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:30:18 -04:00
2f5bbbd68d doc: Space::tab_name / layout / is_paper replace repeated hand-written matches (#888)
* doc: Space::is_paper/layout/tab_name helpers replace hand-written matches

Twenty-seven call sites in 15 files across engine, io and ui-egui matched Space by hand to get
"is this a layout", "which layout" or "Model or the layout's name". Route them
through three small helpers on Space instead. No behaviour change.

This also makes adding a space kind a few-file change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* doc: keep Space::Model comparisons that no helper shortens; test the helpers

Only the sites that repeated the same match (tab name, layout name,
paper lineweights) go through the helpers; plain Model comparisons and
plot_window's match on (space, plotArea) stay as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:21:19 -04:00
966356545d ACAD_TABLE from other writers: keep style text height, read cell text height, lay out cell text as MTEXT (#802) (#806)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:18:23 -04:00
d2a9159e51 PDFBOOK: plot sheet frames in model space into one PDF per row of frames (#896)
* PDFBOOK: plot sheet frames in model space into one PDF per row of frames

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* PDFBOOK: no Cmd+Shift+P shortcut (AutoCAD's Quick Properties toggle); simpler uk label

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: simanov <asimfali@mail.ru>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:15:56 -04:00
d0438577ed Fonts: use the TrueType typeface from a style's xdata and find families by first word (#894)
AutoCAD stores a TrueType text style's family (e.g. "GOST type A") in the style's ACAD xdata
and leaves the font file name empty. Styles without a file now take that family.

Family-name lookups also look in files whose name starts with the family's first word, so
"GOST type A" finds GOST.TTF (the stem shares only 4 characters with the family name).

Co-authored-by: simanov <asimfali@mail.ru>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 05:12:33 -04:00
7b767ddab9 Plot: model-space PNG and SVG plots honour fit: false without a scale, at 1:1 like PDF (fixes #713) (#744)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:10:52 -04:00
Brandon ThomasandClaude Opus 5.5 636b041a4e DXF writer: resolve the #827/#871 merge (W keeps both the layer handles and the hatch reactors)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:51:37 -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 <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:46:45 -04:00
28b8160407 DXF: associative hatches in blocks keep their boundary; boundaries carry the hatch reactor (#871)
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 <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 04:46:29 -04:00
5b037b8f92 DXF: write viewport frozen layers as group 341 layer handles too (#827)
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 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 04:46:15 -04:00
mattlafyandClaude Opus 5.5 d134079f11 Xdata travels with its object: COPY, MIRROR, ARRAY and paste keep it (#890)
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 <noreply@anthropic.com>
2026-10-13 04:38:34 -04:00
Marco Arnulfo 50246683c3 DXF: read every ENTITIES section, not only the last (fixes #865) (#869) 2026-10-13 04:38:24 -04:00
Ray caad180e98 fix(dxf): MTEXT direction ignores the embedded object's insertion point (#667) (#834)
An R2018+ MTEXT can carry an embedded object (group 101) in which 10 is the
X-axis direction and 11 the insertion point. AutoCAD writes one for MTEXT with
column settings, and so does the DWG bridge. The reader took the first group 11
anywhere in the entity as the direction, so such text turned towards its own
insertion point. Read 11/21 and 50 from the MTEXT's own groups only.
2026-10-13 04:37:47 -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 <noreply@anthropic.com>

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 04:37:31 -04:00
aldozarandClaude Opus 5.5 c781169b95 Don't give entities made while reading handles the file still uses (#783)
Another program's file can name a $HANDSEED below handles it uses (a DWG
read through the bridge did: its seed was under the handles of its
dimension blocks). The faces a polyface mesh becomes were numbered from the
seed while reading, so they could take handles of records further on in
other lists (another block); within one list duplicates were already
renumbered. A DWG keeps one object per handle, so saving one lost the
duplicates: 15 faces of a block's mesh in a permit set.

Before entities are read, every handle a file uses is now reserved. A file
CADCraft wrote names its own next handle (#495), above which its rebuilt
records are numbered, so it is left to that and resaves stay identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:35:44 -04:00
aldozarandClaude Opus 5.5 f314d3a591 Write an unedited dimension's block as it was read (#782)
Saving regenerated every dimension's anonymous block, even dimensions read
from another program's file and never touched: the definition points went
and other programs then showed CADCraft's drawing of each dimension (a
dimension line split around its text came back whole, for instance).

A dimension still linked to the block it was read with hasn't changed
since: every edit drops the link, and the screen draws that block while it
is there. Its block is now written as read, under its name and handles;
edited and new dimensions are generated as before.

On a 1,013-dimension permit set saved DWG -> CADCraft -> DWG, every
dimension's block now matches the original (before: none).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:35:38 -04:00
aldozarandClaude Opus 5.5 e9d8462828 Keep the ACIS geometry of 3D solids, regions and bodies on save (ACDSDATA) (#781)
From AutoCAD 2013 on, a 3DSOLID, REGION or BODY doesn't carry its ACIS
geometry: the binary SAB lives in the file's data store section (ACDSDATA),
one ACDSRECORD per entity bound by a 320 handle. CADCraft kept those
entities (as unknown entities, with their handles) but dropped the section,
so a drawing saved as DWG or DXF had empty solids, and saving reported them
as not saved.

- doc: Foreign::data_store keeps the section's groups as read;
  data_store_entities() lists the entities it has records for.
- io: the reader keeps the section; the writer puts it back for files of
  2013 and later (and in the DXF the DWG bridge converts, which acadrust
  reads into the DWG), without the records of entities no longer written.
  Nothing is written for older files or when no record's entity remains.
- engine: not_saved_as(d, version): from 2013 a solid, region or body with a
  data store record isn't reported; saves and document.bytes pass the version
  being written.

On a 39,600-object permit set with 625 3D solids and 2 regions, DWG ->
CADCraft -> DWG now keeps all 627 bodies with byte-identical SAB (before:
none), and the save no longer reports them.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:35:31 -04:00
Books 7fcdc6e3bc Active viewport highlight: selection on the GPU, hover cached apart, kept inside a clipped outline (#770)
* Active viewport: redraw while panning and zooming inside it, without rebuilding the layout (#688)

Inside an active MSPACE viewport, middle-drag, trackpad pan and wheel or
pinch zoom change the viewport's view through set_view_quiet, which makes
no new revision. The layout's display list, which draws model space
through each viewport, was cached on (drawing, revision, zoom band), so it
kept showing the old view until a command such as ZOOM rebuilt it: the
viewport froze, or appeared to ignore the zoom.

The active viewport's contents are now drawn apart from the layout's list,
the way model space itself is drawn. They are built once for an area
around the viewport (half its larger side of margin all round), then moved
and scaled to the current view on the GPU and drawn under the layout's own
objects. They are rebuilt when the drawing changes, when the zoom has
moved 2x from the view they were built for, when the view leaves the built
area, and once a zoom comes to rest, so dash lengths and curve detail
follow the viewport's scale again. A pan step takes about 1 ms with 70,000
polylines, where rebuilding the layout takes about 140 ms per frame.

Rectangular viewports are clipped with the scissor rectangle. A clipped
viewport's outline is drawn into a stencil buffer as a triangle fan that
flips a bit (even-odd, as the outline clips on the CPU), and its contents
draw only where the bit is set; the native window now asks eframe for an
8-bit stencil buffer, and every canvas pipeline names its format. Where
there is no stencil buffer (the CPU canvas, the web runner), a clipped
viewport's contents stay in the layout's list, whose cache now follows the
viewport's view.

Render: Options::skip_viewport leaves a viewport's contents out of a
layout, and viewport_content builds them for any area. Viewport contents
are built on all cores, like model space (130 ms to 37 ms for 70,000
polylines).

* Active viewport highlight: selection on the GPU, hover cached apart, kept inside a clipped outline

Inside an active MSPACE viewport the selection highlight was drawn as
dashed egui shapes on the CPU every frame, about 8-13 ms per frame with
7,000 polylines selected, and its geometry was cached together with the
hover, so moving the cursor over objects tessellated the whole selection
again. The selection and the hover are now cached apart, and the
selection goes through the GPU highlight path model space already uses:
about 0.1 ms per frame. In a clipped viewport it is kept inside the
outline with the stencil mask its contents use; before, it was only cut
to the outline's bounding rectangle.
2026-10-13 04:34:40 -04:00
b19188919d DXF/DWG: write the Model layout's plot type as extents, not "Layout information" (#765)
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 <293597958+EP995LAB@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:34:35 -04:00
cyfung1031 bcd6322ee9 Plotting: honor model-space center and X/Y offsets (#751)
* plot: add bounded model-space paper placement

* plot: expose center and paper offset parameters

* ui: add model-space Plot offset controls
2026-10-13 04:34:04 -04:00
CaseyKleinmanandClaude Opus 5.5 f8cb50eaa3 DXF: write kept application data groups as read, not as handle lists (#681)
Squash of PR #681 by @CaseyKleinman.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:11:09 -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 <noreply@anthropic.com>
2026-10-11 20:11:22 -04:00
Valentin KleinandClaude Opus 5.5 89064fd82a Drafting/plot/UI follow-ups: PolarSnap over isometric snap, MVIEW Restore, plot margins, PNG/SVG pens, PE_URL links, OPTIONS settings (part of #626) (#633)
Squash of PR #633 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:11:06 -04:00
STE1200andClaude Opus 5.5 244feb6e89 Render: level of detail and view clipping for pattern hatches on screen (fixes #544) (#638)
Squash of PR #638 by @STE1200.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:10:37 -04:00
aldozarandClaude Opus 5.5 135c928fea Hatches: draw the pattern definition stored in the file; scale library patterns in metric drawings (#643)
Squash of PR #643 by @aldozar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:54:18 -04:00
Valentin KleinandClaude Opus 5.5 9477f37503 File-format follow-ups: version-gated MULTILEADER and DIMSTYLE groups, kept $DWGCODEPAGE, PURGE multiline styles (#632)
Squash of PR #632 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:54:03 -04:00
MuhiandClaude Opus 5.5 2fa967db65 fix(io): write DXF CLASS instance counts and preserve current leader style (#635)
Squash of PR #635 by @The-Great-Nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:53:33 -04:00
PedroGuilleandClaude Opus 5.5 f967dc7aca Persist SNAPUNIT and GRIDUNIT in DXF (fix #607) (#614)
Squash of PR #614 by @PedroGuille.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:28:56 -04:00
MarekSpodBiedryandClaude Opus 5.5 0b5a0a2eb8 Plot: apply an explicit scale to non-fitted layout plots (#490) (#598)
Squash of PR #598 by @MarekSpodBiedry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:04:10 -04:00
Valentin KleinandClaude Opus 5.5 74e72c93f8 Plotters: device configurations with paper sizes and printable areas (fixes #564) (#570)
Squash of PR #570 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:25:00 -04:00
Valentin KleinandClaude Opus 5.5 7a13ca7d05 PDF export: real text in embedded TrueType subsets (part of #560) (#571)
Squash of PR #571 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:24:14 -04:00