Commit Graph
42 Commits
Author SHA1 Message Date
MarekSpodBiedry 932ba8cc58 Respect locked layers in Clear, Cut, Stretch, Trim, Extend, Fillet, Explode and SplineEdit (#87) (#196) 2026-10-10 14:36:48 -04:00
MarekSpodBiedry 6957ef8756 Keep the active drawing when closing an earlier inactive one (#76) (#192) 2026-10-10 14:36:37 -04:00
MarekSpodBiedry 5a214a0cac Reject layer.set atomically: a refused call no longer applies other changes (#71) (#188) 2026-10-10 14:36:18 -04:00
3e0adadf94 Dynamic Input: pointer input boxes with Tab, comma and relative entry (#60)
* Dynamic Input: pointer input boxes, Tab separator, relative entry

While a command asks for a point and Dynamic Input is on, the cursor
tooltip shows a pair of value boxes, following AutoCAD's pointer input:

- The first point of a command is X and Y, absolute.
- Second and next points default to polar (distance and angle) measured
  from the last point, so no `@` is needed.
- Typing `,` after a value locks it as X and moves to Y; `<` locks it as
  the distance and moves to the angle. A locked value holds the cursor and
  shows a lock.
- Tab types the separator for the format on show, so `40` Tab `90` is 40
  units at 90 degrees, and with the Cartesian setting `40` Tab `40` is 40
  over and 40 up.
- Enter or Space sends the entry with `@` in front, filling an empty box
  from the cursor. A plain number and Enter is still direct distance entry;
  `@`/`#` entries, keywords and command names go through untouched.

The command-line text is the single source of truth: the boxes are a view
of it, so the command line always shows exactly what will be sent
(`40,`, `40<90`) and Backspace simply edits it.

Tab is taken from egui in the raw input hook (desktop and web) while no
widget has focus. Otherwise egui moves keyboard focus to the next button on
Tab and the command line stops receiving keystrokes.

Two new settings mirror AutoCAD's: DYNPIFORMAT (1 = second points start
Cartesian) and DYNPICOORDS (1 = absolute), through SETVAR and in Drafting
Settings under Dynamic Input. The tooltip uses the theme tokens, matching
the command line.

Scripts, the control channel and MCP never pass through the boxes and keep
the plain command-line rules. The parsing lives in a small pure module
(dyninput.rs) with unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr

* Dynamic Input tooltip: border colour from theme tokens

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-10 13:21:39 -04:00
kaffeochknivarandClaude Opus 5.5 a43e88e17a UNITS opens a Drawing Units dialog; SETVAR prompts at the command line (#30) (#38)
Typing UNITS ran the JSON form with no parameters, which changed nothing
and printed nothing. Typed or menu-invoked UNITS (no parameters) now opens
a Drawing Units dialog like QSELECT does: length type and precision, angle
type and precision, insertion units and a sample output. OK applies the
values through the `units` command; JSON calls run the command as before.

SETVAR had no prompts either, so `SETVAR LUNITS 2` ran with no name and
the remaining inputs were dropped. Space acting as Enter was not the
cause. SETVAR now asks "Enter variable name or [?]" and then "Enter new
value for NAME <current>"; `?` lists the variables, unknown names end the
command, and a value of the wrong type re-prompts.

sysvars::set now refuses variables that `get` derives from state
(DWGNAME, DBMOD, EXTMIN, ...) and keeps integer header variables integer,
so a typo can no longer store text in LUNITS.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:16:16 -04:00
5c2a18638e DIMANGULAR: accept a circle (#20)
* DIMANGULAR: accept a circle

The first prompt offers "arc, circle, line" but picking a circle was
rejected. Picking a circle now uses its center as the angle vertex and
the picked point as the first endpoint, then asks for the second
endpoint.

* cargo fmt

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-10 13:15:47 -04:00
kaffeochknivarandClaude Opus 5.5 d144d7f371 SETVAR DIM* variables become style overrides for new dimensions (#66) (#129)
`SETVAR DIMSCALE 24` was stored in the header but never reached new
dimensions: they were created with no overrides, and the header DIMSCALE
was only a fallback for styles whose scale is zero.

As in AutoCAD, the header DIM* variables that differ from the current
dimension style are now its overrides, and new dimensions take them on
(`Drawing::dim_overrides`). Existing dimensions keep their size. Making a
style current, or editing the current style, sets the variables back to
the style's values (`Drawing::sync_dim_vars`), so
`dimstyle {"name": "Standard", "DIMSCALE": 24}` still works.
GETVAR/SETVAR read DIM* variables the header doesn't carry from the
current style.

Dimension extents now use the same scale as the geometry (the style with
the dimension's overrides) instead of multiplying by the header DIMSCALE.
A DIMSCALE of zero (AutoCAD: scale to the layout viewport) falls back to
the drawing's DIMSCALE and then 1 instead of 1e-9.

Reading DXF, $DIMDSEP becomes the separator character, as the DIMSTYLE
table already is, so AutoCAD's header variables don't turn into spurious
overrides.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:15:33 -04:00
by TruittandClaude Opus 5.5 d87950afe8 Press-and-drag selection windows (PICKDRAG = 2) (#63)
Selection windows only worked click-then-click: the canvas reacts to clicks,
and a press that moves before release is a drag, not a click, so a drag was
ignored entirely.

Now, while objects are being selected (no command running, or a command
asking for objects), a drag opens the window where the button went down and
letting go closes it, as with AutoCAD's default PICKDRAG = 2. Dragging right
is a window, dragging left a crossing window, Shift still removes, and
click-click works as before. Drags that start on a grip or while a command
asks for a point are left alone.

Session::begin_window opens the window at the drag's start, even over an
object (a click there would pick the object instead), with an engine test.


Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:15:20 -04:00
Brandon ThomasandClaude Opus 5.5 8fceaaff3a Fix the arc-length DWG round-trip test build; cargo fmt
The squash for #159 picked up an earlier revision of the #140 test, which
called as_bytes() on a Vec<u8>. Also apply rustfmt to code from #120 and #155.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:04:59 -04:00
3e7e0bdf2a Linux: default to XWayland for file drag-and-drop on Wayland desktops (#159)
* DIMANGULAR: accept polyline segments (fixes #10)

Picking a side of a rectangle or a PLINE segment was rejected with
"Select a line or an arc." because only LINE and ARC entities were
accepted. The picked object is now resolved to the segment under the
cursor: straight polyline segments work like lines and arc segments
like arcs.

* Redraw edits made while a command is still running (fixes #4)

LINE and PLINE add geometry on every point, but the canvas only rebuilt
its display list when the drawing revision or the document pointer
changed. The revision was bumped when a command finished, and the
pointer changed only on the first copy-on-write edit, so only the first
segment appeared until the command ended. Session::feed now bumps the
revision after each input once the running command has changed the
drawing.

* Fix #14: include DWG in the Open drawing filter

* Blocks window: click a block to insert it (#8)

Each block name in the Blocks window is now a button. Clicking it starts INSERT with that block already chosen, so the command line asks for the insertion point.

* SCALE: Reference option (#17)

SCALE > Reference printed "not available yet". It now asks for the reference length (typed, or two points) and the new length (typed, or a point measured from the base point), and scales by new / reference. Zero, negative or non-finite lengths are rejected and asked again.

* Print and Ctrl+P ask where to save the PDF (#5)

PLOT/EXPORTPDF started from the menu, the toolbar, Ctrl+P or the command line had no path,
so the PDF was built in memory and dropped. They now open the save dialog (PDF filter first)
and write the file there. JSON calls from scripts, the control channel and MCP still never
open a dialog. Ctrl+P was shown in the menu but not bound; it is now.

* Scripts: split a line at keyword prompts so POLYGON 6 200,20 I 15 works (#43)

* Layer Properties Manager: stop the window growing every frame (fixes #29)

The vertical separator between the filter tree and the table takes all the
height it is offered, and a resizable window offers its previous height, so
the window grew 30px per frame until it filled the screen. Cap the row at the
table's fixed height. Regression test runs the dialog headless for 30 frames
and checks the height stays put.

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

* ui.render: fit the drawing and return pngBase64 like the headless render (#49, #50)

* Linux: default to XWayland for file drag-and-drop on Wayland desktops

* Fonts: per-character fallback, FONTALT and .ttc for CJK text in SHX styles (#130)

CJK text in a style that uses an SHX font (romans.shx + chineset.shx big font)
drew every ideograph as an empty box: the SHX name resolves to our stroke font,
which has no CJK glyphs, and the font scan skipped .ttc collections, where most
CJK system fonts live.

- Per-character fallback: a character the text's font lacks (stroke font or
  TrueType) is drawn from an installed font that has it, checked by cmap.
  Wide-coverage fonts go first (Noto Sans CJK, Source Han, WenQuanYi, Droid
  Sans Fallback, PingFang, Microsoft JhengHei/YaHei, MS Gothic, ...), regular
  weights before others. Fallback glyphs are sized like TrueType text (cap
  height = text height). Lookups are cached; the search is bounded (24 files,
  160 MiB, 256 faces per character, files over 96 MiB never read) and skips
  spaces, controls and private-use code points. No fallback: the box as before.
- FONTALT (sysvar, seeded from CADCRAFT_FONTALT): font used for a style font
  that can't be found (an SHX file, a missing TTF). Empty = our stroke font.
- FONTFALLBACK (sysvar, seeded from CADCRAFT_FONTFALLBACK): fonts tried first
  for missing characters, e.g. "Noto Sans CJK TC".
- .ttc/.otc are scanned; TextFont::Outline carries a ttf::Face (bytes + face
  index). Fonts are also found by family name among files with a similar
  stem, so "Noto Sans CJK TC" picks face 3 of NotoSansCJK-Regular.ttc.
- Cap height falls back to the height of "H" when OS/2 has none (DejaVu
  Sans), which fixes ttf_tests::system_font_outlines_when_available and
  render's truetype_text_is_filled_or_outlined on machines without Arial.
- README: a Fonts section for the two settings; ROADMAP M4 note.

Native SHX / big-font rendering stays on the roadmap (M4).

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

* Fonts: cap the total bytes of system font files loaded from disk

A drawing naming many text styles could otherwise load every installed font
into memory through family-name lookups.

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

* Pan with a two-finger trackpad swipe; keep wheel and pinch zoom

Trackpad two-finger swipes arrive as scroll events, and the canvas turned
every scroll into a zoom, so trackpad users had no realtime pan (middle-drag
needs a middle button).

Scroll now follows the device: precise point deltas (trackpad, Magic Mouse)
pan the view by x and y; notched line deltas (mouse wheel) zoom about the
cursor as before. Pinch and Cmd+scroll still zoom through zoom_delta, and
middle-drag still pans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr

* Add Tru10 to contributors/people.toml

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr

* Trackpad pan: only on macOS, where point deltas mean a trackpad

Browsers and Wayland smooth-scroll mice report ordinary wheels in points too,
so the wheel keeps zooming there.

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

* Keep arc-length dimensions as arc-length through DXF save and reopen (#100)

* PLINE: Halfwidth is centre-to-edge, and Width answers taper the next segment (fixes #83, #104)

Interactive PLINE kept one width value: Halfwidth answers were stored as
the total width, and the ending-width answer overwrote the starting one,
so a taper became a constant width. The next segment's starting and
ending widths are now tracked separately, Halfwidth answers are doubled,
and each new segment (including the closing one) gets its widths on its
first vertex; later segments are uniform at the ending width, as in
AutoCAD. Uniform polylines are still stored as const_width, so their DXF
output is unchanged.

Fixes #83. Fixes #104.

* Arc-length dimensions: write a standard aligned record plus CADCraft xdata

DXF has no arc-length DIMENSION type, so write type 1 and mark the kind and
arc centre in CADCRAFT xdata (like ASSOC). Other programs read an aligned
dimension; CADCraft restores the arc length, through DXF and DWG.

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

* Desktop: write log records to standard error and a rotating log file

The desktop app installed no logger, so every log::warn!/log::error!/
log::info! in the workspace was discarded: a broken app icon, the GPU
canvas set-up, the engine's command-line echo at debug level. A launch
from a desktop menu has no terminal either, so there was nothing to
attach to a bug report.

apps/cadcraft/src/logging.rs (no new dependency):
- standard error plus <settings dir>/logs/cadcraft.log; each launch
  rotates the previous log to cadcraft.1.log and .2.log, so the log of a
  run that crashed survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
  (bounded) and written once the file is attached
- default levels: info for cadcraft*, warn for everything else;
  RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
  error as the only sink; a poisoned lock is tolerated

CADCraft had no per-user directory yet. config_dir in main.rs adds one:
CADCRAFT_CONFIG_DIR, else $XDG_CONFIG_HOME/cadcraft or ~/.config/cadcraft
(Linux, BSDs), ~/Library/Application Support/CADCraft (macOS),
%APPDATA%\CADCraft (Windows).

The logger is installed first in main and the file is opened after
argument parsing (--version leaves no file). The control server's
eprintln!s now go through the logger. A --control or
CADCRAFT_CONTROL_PORT value that is not a port is now logged instead of
silently leaving the control channel off. Documented in README.md
(Quick start > Logs).

---------

Co-authored-by: SBaksa <sabaksa11@gmail.com>
Co-authored-by: DWAA1660 <dwatnip123@gmail.com>
Co-authored-by: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com>
Co-authored-by: lloyd <34131158+LloydNicholson@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: vangel76 <vangel1976@googlemail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Tru10 <133776756+Tru10@users.noreply.github.com>
Co-authored-by: Gert Pellin <gert@pellin.be>
2026-10-10 12:58:26 -04:00
MarekSpodBiedry 415da86d39 Scripts: split a line at keyword prompts so POLYGON 6 200,20 I 15 works (#43) (#45) 2026-10-10 12:57:42 -04:00
MarekSpodBiedry a1df2f2c8c SCALE: Reference option (#17) (#27)
SCALE > Reference printed "not available yet". It now asks for the reference length (typed, or two points) and the new length (typed, or a point measured from the base point), and scales by new / reference. Zero, negative or non-finite lengths are rejected and asked again.
2026-10-10 12:57:31 -04:00
Books 093c87d08a DIMANGULAR: accept polyline segments (fixes #10) (#15)
Picking a side of a rectangle or a PLINE segment was rejected with
"Select a line or an arc." because only LINE and ARC entities were
accepted. The picked object is now resolved to the segment under the
cursor: straight polyline segments work like lines and arc segments
like arcs.
2026-10-10 12:57:13 -04:00
Books 698f2c8c36 Redraw edits made while a command is still running (fixes #4) (#22)
LINE and PLINE add geometry on every point, but the canvas only rebuilt
its display list when the drawing revision or the document pointer
changed. The revision was bumped when a command finished, and the
pointer changed only on the first copy-on-write edit, so only the first
segment appeared until the command ended. Session::feed now bumps the
revision after each input once the running command has changed the
drawing.
2026-10-10 12:57:07 -04:00
Books 3c577968f6 ARC: refuse zero-radius arcs instead of adding them (fixes #85) (#123)
The `arc` command checked its three-point form for collinear points but
added the arcs built by its start/center/end and center/radius forms
unchecked, so radius 0 (or a start point on the center) added a
degenerate Arc, dirtied the drawing and recorded an undo step. Every
form now goes through the existing finite_arc check and fails with the
same "these values do not define an arc" error the arc.* variants and
the interactive ARC already use, leaving the drawing unchanged.

Fixes #85.
2026-10-10 12:26:20 -04:00
MarekSpodBiedry 3e0de7e4eb Keep a free measured origin in place when its source moves (#109) (#152) 2026-10-10 12:26:01 -04:00
Valentin KleinandClaude Opus 5.5 6c145ed733 Menu items open their missing dialogs; COUNT shows its result; New Layout asks for a name (#126, part 1) (#180)
* Menu items open their missing dialogs; COUNT shows its result; New Layout asks for a name (#126)

Eight menu items ran their JSON form with no parameters and changed nothing.
Typed or chosen from a menu they now do what the menu promises; JSON calls
still run the command and never open a dialog.

- New crates/ui-egui/src/managers.rs: Linetype Manager (load from the
  library, set current, global scale, pattern preview), Point Style
  (PDMODE tiles drawn in code, size relative/absolute), Table Style and
  Multileader Style (list, set current, new, edit properties), Page Setup
  Manager (per layout: paper, orientation, plot area, fit/scale, center,
  lineweights) and Constraint Settings (infer, bars, transparency, dynamic
  constraints, AutoConstrain types and tolerances). All changes go through
  their commands (app.run), so they are undoable and scriptable.
- menus::run_ui_command routes LINETYPE/LT, DDPTYPE, TABLESTYLE/TS,
  MLEADERSTYLE/MLS, PAGESETUP and CONSTRAINTSETTINGS/CSETTINGS without
  parameters to the dialogs; dialogs::show dispatches them.
- COUNT returns a `message` (per-block counts and total) that the command
  line shows; Session::start echoes multi-line messages line by line.
- LAYOUT.NEW from the menu or command line prompts
  "Enter name of new layout <LayoutN>:"; Enter takes the default, a taken
  or invalid name is reported and asked again.

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

* Move COUNT test away from other PRs' hunks to avoid merge conflicts

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:15:42 -04:00
Valentin KleinandClaude Opus 5.5 8d71f7de4a Properties palette: every control acts or is visibly unavailable (#182)
* Properties palette: every control acts or is visibly unavailable (#6)

Several Properties palette controls looked clickable but did nothing: most
current-property rows with nothing selected (linetype, lineweight, thickness,
text/dimension/multileader/table style), the selection-type drop-down, a
circle's circumference and area, a line's length and angle, a dimension's
style, and the All/My switch.

- Nothing selected: Linetype runs `linetype {current}`, Lineweight `lweight`,
  Thickness `setvar THICKNESS`, the style rows `style.current`,
  `dimstyle.current`, `mleaderstyle` and `tablestyle`. Transparency,
  Annotation scale and Plot style(s) have no command yet: greyed with a
  "Not available yet" tooltip.
- Header: the type drop-down narrows the selection to one object type
  (`select {handles}`), plus Toggle PICKADD (`setvar PICKADD`), Select Objects
  (`select`) and Quick Select buttons, with icons drawn in code.
- Geometry: circle circumference/area, line length/angle and dimension style
  are editable through `properties.set` (new keys `circumference`, `area`,
  `dimStyle`); computed values (line deltas, polyline length/area, block
  name) are greyed read-only.
- PICKADD now changes clicking with no command running: off, each pick
  replaces the selection and Shift adds.
- Palette text fields keep typed text while the window itself is unfocused
  (they read focus from egui memory, not `Response::has_focus`).

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

* Move palette tests away from other PRs' hunks to avoid merge conflicts

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:15:34 -04:00
Valentin KleinandClaude Opus 5.5 306ba991a5 Selection commands prompt "Select objects" without a pickfirst selection (#126) (#176)
Edit > Cut / Copy / Copy with Base Point, View > Zoom > Object, Dimension >
Update, Tools > Draw Order > Bring to Front / Send to Back and the Format >
Layer Tools LAYCUR, LAYFRZ, LAYOFF, LAYLCK and LAYULK did nothing when started
from a menu, toolbar or the command line with nothing selected.

- machines::SelectRun: "Select objects:", then run the command's JSON body on
  the picked handles. With a pickfirst selection it runs at once, as before.
  It replaces the DIMREASSOCIATE/DIMDISASSOCIATE SelectThen in annotate.rs and
  calls the command body directly, so the command records one undo step.
- LAYFRZ/LAYOFF/LAYLCK/LAYULK pick one object ("Select an object on the layer
  to be frozen" etc.).
- COPYBASE asks for the base point first, then for objects (skipped with a
  pickfirst selection).
- Objects picked at the prompt are deselected afterwards.
- JSON/programmatic calls are unchanged and never prompt.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:15:26 -04:00
Valentin KleinandClaude Opus 5.5 7666c1cd68 LINE: deferred tangent/perpendicular for the first point (fixes #69) (#168)
TAN and PER snaps need the other end of the line, so the first point of a
LINE (no base point yet) could never snap tangent: a belt line tangent to
two circles was impossible to draw.

Now, as in AutoCAD, a point prompt can accept *deferred* snaps:

- snap: with no base and the new `deferred` flag, osnap offers "Deferred
  Tangent" (circles, arcs) and "Deferred Perpendicular" (circles, arcs,
  lines, xlines, rays) where no other snap wins. The hit carries the curve
  (`SnapHit::deferred`, `Deferred`, `SnapCurve`).
- `resolve_deferred` solves the line once the other end is known: tangent
  or perpendicular from a point, tangent-to-tangent (common tangents of two
  circles, new `Circle::common_tangents` in geom) and the perpendicular
  mixes. Among several solutions the one nearest the picks wins. No
  solution (point inside the circle, concentric or nested circles,
  non-finite input) is `None`, never a panic.
- prompt: `Prompt::deferred()` opts a prompt in; `Input::Deferred` reaches
  only such prompts, everywhere else the session turns it into the point it
  was picked at, so other commands snap as before.
- LINE opts in for its first point (and the next one while the first is
  still deferred). No solution reports and re-prompts; Undo drops the
  deferred point. The rubber band shows the resolved line, also the
  tangent-to-tangent one while hovering the second curve
  (`Session::cursor_deferred`, set by the canvas).
- canvas: passes the prompt's flag to osnap and sends a deferred pick as
  `Input::Deferred`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:15:18 -04:00
Valentin KleinandClaude Opus 5.5 4d61f57eec HATCH/GRADIENT/BOUNDARY: islands made of separate lines (fixes #106) (#163)
Point picks found the outer boundary by tracing the planar graph of all
curves, but islands only came from single closed objects (circles, closed
polylines, full ellipses, closed splines). A square drawn as four LINEs
inside a rectangle was ignored: HATCH drew through it, GRADIENT filled it
and BOUNDARY created only the outer polyline.

- geom::region::boundary_at returns the outer loop plus islands: every
  connected group of curves inside the face that doesn't touch its
  boundary and doesn't surround the pick point. Each island is the
  outline of the group's outside, traced with the same sharpest-turn
  walk from its leftmost vertex after dangling ends are pruned. Islands
  report which input curves they came from. Traces are bounded by the
  group's edge count and islands are capped at MAX_ISLANDS.
- loops_at (shared by HATCH, GRADIENT and BOUNDARY) adds those islands
  when every curve in the group is an open LINE, ARC, elliptical arc,
  open polyline or open spline. Groups containing a closed object keep
  the existing per-object islands, so closed-polyline and circle islands
  are unchanged (bulges kept).
- Tests: geom region tests for separate-line islands, dangling ends,
  open and attached curves, nesting and hostile input; engine tests for
  HATCH (rendered pattern stays out of the hole), GRADIENT (375 filled,
  hole clear) and BOUNDARY (two polylines) with four-line and
  closed-polyline islands.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:15:09 -04:00
Valentin KleinandClaude Opus 5.5 7497618f76 fix(engine): programmatic undo/redo ends a running command first (#178)
* Programmatic undo/redo ends a running command first (#177)

`undo` or `redo` run through engine.execute, MCP, the control channel or a
script while an interactive command was still running (e.g. TEXT waiting for
another line) swapped the drawing under that command. Interactive commands
push their undo step only when they end, so `undo` popped the previous
command's step instead: it removed the earlier geometry, dropped the running
command's entities, and left the command running from a stale state.

Both commands now first end the running command the way Esc does, exactly as
Session::start already does for Cmd+Z, the menu and the command line. The
command's work so far becomes one undo step, and `undo` then removes that step.

Regression test: programmatic_undo_during_a_command_ends_it_first.

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

* Move test away from other PRs' hunks to avoid merge conflicts

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:14:53 -04:00
Valentin KleinandClaude Opus 5.5 3fe93806b7 Entity transparency: set it, report it, round-trip DXF group 440 (#174)
properties.set documented `transparency` but never read it, nothing reported it,
and DXF dropped group 440. Fixes #132.

- properties.set takes "ByLayer", "ByBlock" or a percentage 0..90 (a number or
  "50"/"50%"); anything else (out of range, NaN, junk) is an error and leaves
  the selection unchanged.
- properties and drawing.inspect / drawing.entities report it in the same form.
- Properties palette: the Transparency row is a menu (ByLayer, ByBlock, a
  0..90 percentage) that acts through properties.set.
- DXF: entities write 440 unless ByLayer (ByBlock 0x01000000, a percentage its
  alpha | 0x02000000, the alpha mapping layers use); the reader parses it back,
  any other value reads as ByLayer.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:14:37 -04:00
MarekSpodBiedry df1c074fff Keep an ordinate dimension's datum fixed when its feature moves (#105) (#157) 2026-10-10 12:14:00 -04:00
MarekSpodBiedry 947535158a Store canonical autoTypes names so accepted spellings take effect (#95) (#156) 2026-10-10 12:13:53 -04:00
MarekSpodBiedry 0af1528f4c AREA of a normal hatch no longer counts unfilled islands (#91) (#155) 2026-10-10 12:13:47 -04:00
MarekSpodBiedry 9882b85a41 Reject nonpositive MTEXT height on creation (#84) (#154) 2026-10-10 12:13:39 -04:00
MarekSpodBiedry bd46065c27 HATCHEDIT: clear the gradient when a named pattern is chosen (#107) (#151) 2026-10-10 12:13:31 -04:00
MarekSpodBiedry 52f05f1514 Fence selection now includes crossed XLINE and RAY objects (#92) (#149) 2026-10-10 12:13:24 -04:00
MarekSpodBiedry 74cbdf7e2f Keep selected objects outside the joined chain when using JOIN (#86) (#148) 2026-10-10 12:13:16 -04:00
MarekSpodBiedry 24f074a9ad Reject deleting a dimension that a remaining dimension refers to (#94) (#145) 2026-10-10 12:12:58 -04:00
MarekSpodBiedry 279c1bed6a Convert typed ellipse arc angles to ellipse parameters (#82) (#143) 2026-10-10 12:12:41 -04:00
MarekSpodBiedry 3ef52e31d8 Apply DIMOVERRIDE text together with dimension variables (#108) (#141) 2026-10-10 12:12:33 -04:00
MarekSpodBiedry fadbea5dfe Keep elevations when EXPLODE splits a 3D polyline (#88) (#138) 2026-10-10 12:12:16 -04:00
Brandon Thomas 1c86787459 Merge branch 'worktree-agent-a2f28e507557a5b11' 2026-10-06 02:12:34 -04:00
Brandon ThomasandClaude Opus 5.5 9646f20b73 Layer Properties Manager, QSELECT + Quick Properties, constraint display, Parameters Manager
- Layer Properties Manager: filter tree (All / All Used Layers) with search, sortable table
  (Status, Name, On, Freeze, Lock, Plot, Color, Linetype, Lineweight, Transparency,
  New VP Freeze, Description; VP Freeze / VP Color inside an MSPACE viewport), inline
  rename and description, ACI + true-colour picker, linetype picker with Load from the
  library, lineweight picker, layer states save/restore/delete.
- Engine: qselect rewritten ({type, property, operator =,!=,>,<,*, value, mode new|append|
  exclude, applyTo}) + qselect.info; layerstate.list/delete/rename; layer list reports
  used layers and states; Layer.vp_freeze_new (DXF flag 2, applied by MVIEW) and
  layer.set newVpFreeze; per-viewport layer colours (Viewport.layer_colors, vplayer
  colors, honoured by the renderer); vplayer defaults to the active viewport; QPMODE
  setting/sysvar/toggle; properties.set length/angle for lines.
- Canvas: constraint bars with code-drawn glyphs and coincident markers, dynamic
  dimensional constraints (aligned/horizontal/vertical/radius/diameter/angular) with
  name=value labels and lock icon, honouring CONSTRAINTBAR/DCDISPLAY; cached per
  drawing revision; double-click a label to edit its expression (parameters).
- Quick Properties palette near the selection (status-bar toggle), Quick Select dialog,
  Parameters Manager (edit, add user parameter, delete).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 02:11:43 -04:00
Brandon ThomasandClaude Opus 5.5 2287134dbd Incremental R-tree: patch only changed store chunks after an edit
A pick after one edit at 200k entities drops from ~110 ms (full rebuild) to ~4 ms;
steady-state picks stay at ~1 us. Adds a parity test against fresh builds and a
perf row for the incremental path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:36:17 -04:00
Brandon ThomasandClaude Opus 5.5 f9aa1b279a M4 annotation depth + M9 parametric constraints
Annotation (M4):
- TrueType text from system fonts (filled/outlined), shared placement for all text
- MTEXT formatting codes: fonts, heights, colours, under/overline, stacks, wrapping
- Full DIMSTYLE variables, per-dimension overrides, all arrowheads and ticks,
  unit formats, tolerances, limits, alternate units, colours by role
- Associative dimensions that follow geometry; DIMREASSOCIATE/DIMDISASSOCIATE
- STYLE/DIMSTYLE management commands, DIMTEDIT, DIMSPACE, TABLE create/edit/merge

Parametric (M9):
- New cadcraft-constraints crate: weighted damped Gauss-Newton solver, conflict
  and redundancy detection, expressions, AutoConstrain inference
- All GC*/DC* commands, GEOMCONSTRAINT/DIMCONSTRAINT, PARAMETERS, DELCONSTRAINT,
  constraint bar / display settings; re-solve after every edit

Also: MODEL/PAPER status bar toggle, cargo xtask alias, roadmap update
(233/491 menu items, 290 commands).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:27:38 -04:00
Brandon ThomasandClaude Opus 5.5 c35fa82373 MSPACE/PSPACE: work in model space through layout viewports
Double-click a viewport (or MS) to edit model space inside it; the screen view maps
consistently so picks, snaps, grips and previews work in model units, and zoom/pan
change the viewport's view (or the sheet when the viewport is locked). PS or a
double-click outside returns to paper space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:23:57 -04:00
Brandon ThomasandClaude Opus 5.5 3da0219ac9 M1/M2/M6/M10: draw/modify depth, grips API, layouts/viewports/PLOT to PDF, GPU canvas, R-tree index
- All 2D Draw-menu items: ARC variants, CIRCLE TTR/TTT, DIVIDE/MEASURE, REVCLOUD, WIPEOUT, 3DPOLY,
  MLINE, HELIX, SPLINE CV, CENTERMARK/CENTERLINE, XLINE and RECTANG options.
- PEDIT, SPLINEDIT, LENGTHEN, ALIGN, BLEND, REVERSE, NCOPY, CHSPACE, FLATTEN, ARRAYPATH, TEXTEDIT;
  FILLET with arcs and polylines; TRIM ellipses and splines; grip editing commands.
- Layouts, MVIEW/VPORTS, viewport scale/lock/layers, PAGESETUP, PLOT/EXPORTPDF with our own PDF
  writer; paper-space rendering through viewports.
- GPU canvas (wgpu batches, CPU fallback); rstar spatial index for pick/select/osnap;
  cadcraft-cli perf harness.
- CAL, FIND/replace, MASSPROP, MEASUREGEOM; TrueType outlines in cadcraft-fonts; contributors
  standard (xtask contributors, About window credits); docs/mcp.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:40:03 -04:00
Brandon ThomasandClaude Opus 5.5 cb68bed225 M4/M5: dimension commands, multileader, hatch boundary detection, blocks
DIMLINEAR/ALIGNED/RADIUS/DIAMETER/ANGULAR/ARC/ORDINATE/CONTINUE/BASELINE, QDIM, MLEADER, LEADER;
HATCH/GRADIENT/BOUNDARY/REGION with pick-point boundary tracing (planar face walk with islands);
BLOCK/INSERT/ATTDEF/ATTEDIT/WBLOCK/PURGE/BATTMAN; dimension text gaps and horizontal text;
DXF header writer whitelist (correct group codes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:31:44 -04:00
Brandon ThomasandClaude Opus 5.5 d9ab00c53c M0: CADCraft skeleton and vertical slice
Workspace (geom, dxf, color, doc, fonts, render, io, engine, ui-egui, mcp; apps cadcraft,
cadcraft-cli, cadcraft-web; xtask), AutoCAD-for-Mac style UI with code-drawn icons, command line
with prompts/keywords/AutoComplete, draw and modify commands, object snaps, polar/ortho,
selection, undo, layers and properties palettes, DXF read/write, SVG/PNG export, MCP server,
JSON control channel, CLI, wasm build, README, ROADMAP, AGENTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:22:25 -04:00