Commit Graph
213 Commits
Author SHA1 Message Date
Valentin KleinandClaude Opus 5.5 1961a3186a TRIM/EXTEND: cutting edges, Fence, Crossing, mOde, Project, Edge, eRase, Undo (part of #397) (#403)
The options TRIM and EXTEND list at their prompt all printed "not available
yet". Quick and Standard modes follow TRIMEXTENDMODE, Edge extends lines and
arcs virtually (EDGEMODE), Project is kept in PROJMODE. The JSON forms take
fence, crossing, edgeMode and (TRIM) mode.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:43:28 -04:00
Valentin KleinandClaude Opus 5.5 65db0c2f6a Point prompts: FROM, M2P/MTP, point filters, * prefix and <angle override (fixes #392) (#402)
A point modifier typed at any point prompt pushes a frame on the session
(Session::point_mods) that asks for what it needs ("Base point:",
"<Offset>:", "First point of mid:", ".X of", "(need YZ):") and hands one
point to the command, so every command gets them from the command line
and in scripts, and they nest (FROM then M2P). `*x,y`, `*@` and `@*`
parse as world coordinates (the same as the plain forms: no UCS yet).
`<a` locks the next point to that angle from the base point: a typed
distance follows it, a pick is projected onto it, typed coordinates win.
TK is left to object snap tracking (#379).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:43:24 -04:00
Valentin KleinandClaude Opus 5.5 156bd0304e Dimension text: DIMPOST's suffix also goes on tolerances (fixes #390) (#394)
With DIMTOL on, the DIMPOST suffix was added to the measurement only.
As in AutoCAD, the suffix (what follows `<>`, or the whole value when
there is no `<>`) now also follows each tolerance value: symmetric
(10.0mm±0.1mm) and stacked deviations alike. The prefix stays on the
measurement; limits are unchanged.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:43:20 -04:00
Valentin KleinandClaude Opus 5.5 9e19a10b29 Layouts: a viewport on a frozen layer hides its contents (fixes #395) (#396)
Freezing a viewport's own layer hid only its border; the model space
seen through it still showed on screen and in plots. As in AutoCAD, a
frozen viewport layer now hides the contents too, while turning the
layer off still hides only the border. The check is in the shared
layout path, so display, PDF/SVG/PNG export and plotting agree.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:43:15 -04:00
Nigel Ihza Firdausy 95dc3f22a7 fix(geom): keep finite-scale tolerance and midpoint math stable (#400) 2026-10-10 18:43:10 -04:00
Nigel Ihza Firdausy bc2c80289c fix(select): include solid interiors in crossing-window selection (#401)
* fix(select): include solid interiors in crossing-window selection

* test(select): keep solid crossing regressions in an isolated module
2026-10-10 18:43:06 -04:00
Brandon ThomasandClaude Opus 5.5 964eb0fb40 cargo fmt after the community PR batch
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 18:11:18 -04:00
Valentin KleinandClaude Opus 5.5 d461b95872 ZOOM: Center, Scale, Dynamic and a plain n; All/Extents fit the sheet in paper space (fixes #388) (#391)
Center, Scale and Dynamic all zoomed to the extents, so the point or
factor typed after them ran as a command. Center now asks for the centre
point and a magnification or height (a height, nX or nXP), Scale for a
factor, and Dynamic, which needs a drag, asks for a window. A plain n
(at the first prompt and at Scale) is relative to the limits, as in
AutoCAD: 1 fits them. ZOOM All and Extents with nothing drawn used the
model limits in paper space too; Session::zoom_limits gives the sheet
there.

Builds on #372 (ZOOM inside a viewport).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:11:05 -04:00
Valentin KleinandClaude Opus 5.5 304e56ba84 Attributes keep their own layer, colour, linetype and lineweight (fixes #358) (#359)
`Attrib` gains optional per-attribute properties (`props`), each overriding
the attribute definition's. DXF reading takes them from the ATTRIB's groups
8/6/62/420/370; writing gives each ATTRIB the properties it is drawn with.
The renderer prefers them over the definition's. -ATTEDIT's Layer and Color
options set them; ATTSYNC resets them to the definitions, as in AutoCAD.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:10:40 -04:00
Valentin KleinandClaude Opus 5.5 19f7bc39ed VPLAYER: option prompts at the command line (fixes #381) (#387)
Typed VPLAYER ran its JSON form with no parameters and ended, so its
options ran as commands (F started FILLET). It now loops on
?/Color/Freeze/Thaw/Reset/Newfrz/Vpvisdflt until Enter, asks for layer
names (wildcards, comma lists) and then which viewports to change:
All/Select/Current/Except current (Current by default inside a viewport,
Select by point on the sheet in paper space). Changes go through
viewport.set, layer.new and layer.set; the session is one undo step.
The Model tab refuses it.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:10:35 -04:00
Valentin KleinandClaude Opus 5.5 157ea6290d LAYOUT / -LAYOUT: option prompts at the command line (fixes #378) (#380)
Typed LAYOUT ran its JSON default and ended, so the option and the
layout names typed after it ran as commands. It now asks for an option
(Copy/Delete/New/Template/Rename/SAveas/Set/?, Enter = Set) and the
layout name(s) the option needs, with the current layout as the default.
The options run the layout.* command bodies, so a refused name re-prompts
and the command is one undo step. Template and SAveas report that they
are not available yet and end.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:10:30 -04:00
Valentin KleinandClaude Opus 5.5 1496d704b0 ZOOM inside a layout viewport acts on the viewport; nXP sets its scale (fixes #370) (#372)
In MSPACE, Extents/All/Window/Object fitted the drawing window's aspect and
paper-view height, In/Out/nX zoomed about the screen centre, and nXP was read
as nX. Now ZOOM works on the active (unlocked) viewport's model view: fits use
the viewport's aspect, zooms keep its centre, and nXP sets the viewport scale
to n paper units per model unit. Locked viewports and the Model tab are
unchanged.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:09:31 -04:00
Valentin KleinandClaude Opus 5.5 54d75a1623 Control channel: cmdline.key replies like cmdline.input (fixes #360) (#361)
When Enter was refused (Enter repeats a command that isn't available),
headless failed the cmdline.key call while the app dropped the error,
and pressing Enter in the app's command line echoes it. Both now echo
the error to the history and reply with the command-line state, the
`output` lines the key added and `error` only when Enter was refused,
the shape #351 gives cmdline.input.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:09:26 -04:00
Valentin KleinandClaude Opus 5.5 de8ed87a6e MLEADER remembers the placement order and Options (fixes #362) (#365)
The next MLEADER started again with "arrowhead first", Maxpoints 2 and
a landing line. As in AutoCAD, the order (arrowhead, landing or content
first) and the Options values (Maxpoints, leader landing, content type,
first and second angle) are now kept in Session::last_used for the
rest of the session and are where the next MLEADER starts. JSON calls
keep their documented defaults.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:09:20 -04:00
Valentin KleinandClaude Opus 5.5 8db51c2766 Exploded dimension text and DXF dimension blocks match the drawn text (fixes #367) (#369)
EXPLODE and the DXF *D block took the text height from the bare style's
DIMTXT × DIMSCALE, ignoring the dimension's overrides (SETVAR DIM*, DIMOVERRIDE),
a fixed-height text style and the DIMSCALE 0 fallback (text ~1e-10 high), and
EXPLODE always wrote rotation 0, so aligned text turned horizontal. Both now
use the geometry the renderer draws (dimension_in): its text height, angle and
the overridden text style; EXPLODE adds no MTEXT for suppressed text.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:09:15 -04:00
Valentin KleinandClaude Opus 5.5 409968ecbb DIMTEDIT: command-line prompts (fixes #382) (#383)
Typed at the command line or run from a script, DIMTEDIT ran the JSON
form at once and failed with "select dimensions". It now asks "Select
dimension", then "Specify new location for dimension text or
[Left/Right/Center/Home/Angle]": a point moves the text there (the text
follows the cursor in the preview), Left/Right/Center justify it,
Home restores the default position and Angle asks for the text angle.
A single preselected dimension is taken at once. Each answer runs the
existing JSON form on the picked dimension. DIMTED is its alias.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:09:10 -04:00
Valentin KleinandClaude Opus 5.5 1418130d45 DIMRADIUS/DIMDIAMETER: a location inside the circle puts the text inside (fixes #385) (#389)
Only the direction of the picked dimension line location was used, so
the text always went outside beyond the arrowhead. A location inside
the circle (typed, picked or JSON `at`) now places the text there: the
dimension stores it as its text position, and the dimension line runs
from the text out to the arrowhead; a diameter's spans the circle,
broken around the text. DIMTIH (not DIMTOH) decides whether inside
text is horizontal. Outside locations keep the default placement.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:09:05 -04:00
Valentin KleinandClaude Opus 5.5 2f4ceea7a9 Dimension text: DIMRND rounds only the measurement; no DIMPOST on angles (fixes #373) (#376)
Tolerances and alternate units copied the primary number format, so they were
rounded by DIMRND and took DIMZIN's zero suppression: DIMRND 0.5 turned a
±0.1 tolerance into ±0.0000, and 10.3 in showed as [261.50] mm. Tolerances are
no longer rounded and use DIMTZIN. Alternate units use the new DIMALTRND
(rounding), DIMALTU (unit format) and DIMALTZ (zero suppression). The new
variables can be set with SETVAR, DIMSTYLE and overrides. A DXF keeps them as
DIMSTYLE group codes 148, 273, 284 and 285.

DIMPOST is a prefix/suffix for linear dimensions, so angular dimensions no
longer get it ("90°mm"). A text override still wraps the angle.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:08:59 -04:00
Valentin KleinandClaude Opus 5.5 a119873e20 Angular dimensions keep each leg's extension line; centre mark only outside (fixes #374) (#377)
When the arc location lies outside the sweep from the first leg to the second,
the arc runs from leg 2 to leg 1. The extension lines, DIMSE1/DIMSE2 and
DIMBLK1/DIMBLK2 still took the arc start as leg 1, so each extension line was
sized from the other leg. A short leg got no extension line, a leg already
crossing the arc got one drawn along it, and DIMSE1/DIMSE2 suppressed the
wrong line. The arc's ends are now paired with their legs.

Radius and diameter dimensions drew the DIMCEN mark even when the dimension
line is inside the circle. As in AutoCAD, the mark is now drawn only when the
line is outside: a radius led out to its text. A diameter line runs through
the centre, so it gets no mark.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:08:55 -04:00
Valentin KleinandClaude Opus 5.5 179ecc1f0b DIMBASELINE spacing follows DIM* overrides and DIMSCALE 0 (fixes #364) (#366)
Baseline dimensions were spaced by the bare style's DIMDLI × DIMSCALE, so
SETVAR DIMSCALE/DIMDLI (stored as overrides since #129) had no effect and a
style DIMSCALE of 0 collapsed the spacing to ~0. Use the continued
dimension's style with its overrides and the effective scale (the drawing's
DIMSCALE when the style's is 0), as drawing does; DIMSPACE's default spacing
uses the same scale.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:08:50 -04:00
Valentin KleinandClaude Opus 5.5 7f5b39fb25 Undo/redo of LAYOUT Rename/New keeps the session on a layout that exists (fixes #371) (#375)
Undo and redo swapped the drawing but left the session on the current
layout's name. After undoing a rename or the creation of the current layout,
that layout no longer existed: nothing was drawn and commands failed with
"no such space". Now the session follows a renamed layout to its other name
(same tab order), keeping its paper view, and returns to the Model tab when
the layout is gone.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:08:45 -04:00
MarekSpodBiedry 6d8af2e5a4 Keep egui on the System theme preference so the OS appearance is followed live (#301) (#363) 2026-10-10 18:08:41 -04:00
Nigel Ihza Firdausy 35a8617855 fix(geom): use scale-aware circle-circle intersections (#384) 2026-10-10 18:08:37 -04:00
Nigel Ihza Firdausy 7bb64e5fce fix(geom): preserve boundary vertices far from the origin (#386) 2026-10-10 18:08:32 -04:00
Brandon ThomasandClaude Opus 5.5 1ff426dbe7 PURGE test: add the zero-length line directly
#344 made JSON LINE refuse coincident points; files can still hold them, so
the test that checks PURGE Zero-length adds one to the drawing directly.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 17:45:43 -04:00
Valentin KleinandClaude Opus 5.5 9b7d7bd090 STRETCH: repeat Select objects until Enter, C/W windows, Displacement (fixes #228) (#231)
STRETCH gets its own prompt machine instead of a special case inside the
shared MOVE/ROTATE/SCALE one. "Select objects" now repeats until Enter:
implied windows (crossing right to left), `C` and `W` windows, single
picks and All/Last/Previous (moved whole), Remove/Add. Vertices inside any
of the windows move. Enter or `Displacement` at the base point asks for a
displacement, and Enter at the second point uses the base point as the
displacement, as in AutoCAD. Polygon and fence selection say they are not
available yet instead of being misread.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:39 -04:00
Valentin KleinandClaude Opus 5.5 93152afa8e Copy and paste between drawings; PASTEORIG, PASTEBLOCK; right-click menu (part of #325) (#355)
Copying in one drawing and pasting in another lost what the objects
refer to: a line on a layer the target lacked landed on a new default
layer (colour and linetype gone), and a block reference pointed at a
block the target didn't have. Cmd/Ctrl+C, X and V did nothing on the
desktop, because egui delivers them as clipboard events, not key
presses. PASTEORIG had no checks, and the canvas had no right-click
menu.

- Engine (cmd/clipboard.rs): the session clipboard keeps, with the
  copied objects, the layers, linetypes, text/dimension/multileader/
  table styles and blocks (nested too) they use. Pasting adds the ones
  the target lacks; names it has keep the target's definitions, as in
  AutoCAD; generated (*U, *D) blocks whose name is taken get a fresh
  one; objects get new handles.
- PASTEORIG pastes at the original coordinates, only into a drawing
  other than the one copied from (greyed and refused there, as in
  AutoCAD). PASTECLIP and PASTEBLOCK are greyed with an empty clipboard.
- PASTEBLOCK (Edit > Paste as Block, Cmd+Shift+V): the clipboard
  becomes an A$C… block inserted at a point. AI_DESELECT.
- UI: Cut/Copy/Paste events run the clipboard commands (Shift: Copy
  with Base Point / Paste as Block). Each copy is also put on the system
  clipboard as DXF text, and pasted DXF text that isn't our own last
  copy becomes the clipboard, so another CADCraft window can paste it.
- Right-click on the canvas with no command running opens a shortcut
  menu: Repeat <last>, Clipboard (Cut, Copy, Copy with Base Point,
  Paste, Paste as Block, Paste to Original Coordinates), the
  selection's editing commands and Deselect All, Undo/Redo, Pan/Zoom,
  Quick Select, Find. During a command it is still Enter.
- Ukrainian labels for the new items.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:34 -04:00
Valentin KleinandClaude Opus 5.5 4010b94919 INSERT inserts exploded (*NAME) and drawing files (fixes #340) (#341)
At the block name prompt, *NAME inserts the block's objects: after the
insertion point INSERT asks one scale factor and the rotation, and no
attribute values. A path (C:\parts\bolt.dxf), NAME=path or NAME= reads
the drawing through the io hooks and defines a block named after the
file (or NAME) from its model space, with the file's $INSBASE as the
base point, adding the layers, linetypes, styles and blocks it needs and
keeping this drawing's definitions. A plain path whose block already
exists asks whether to redefine it; NAME=path redefines without asking.
A file that can't be read is reported at the name prompt. The JSON form
takes `file` (optional `name`) and "*NAME".

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:29 -04:00
Valentin KleinandClaude Opus 5.5 dacd8ece49 Control channel: headless and app reply alike to cmdline.input, state and app.open (fixes #351) (#352)
Both backends now return the same command-line state (prompt, running,
keywords, accept, buffer, history, historyExpanded). cmdline.input echoes
a refused line to the history and `output` and also reports it in `error`
on both sides. Headless app.open without a path answers "missing path"
like the app instead of reaching OPEN. Documented in control-protocol.md.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:24 -04:00
Valentin KleinandClaude Opus 5.5 cdd25840cc Control channel: a timed-out request is cancelled, and the reply says whether it ran (fixes #345) (#348)
The control server gave up after 60 s with "timeout" but left the request
queued, so the app ran it later and a client retry applied it twice.
Requests now carry a shared state: on timeout the server cancels a request
the app has not started (reply state "not-run", safe to retry) and the app
skips it; one already started replies state "may-have-run". The MCP bridge
passes that on in its error.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:19 -04:00
Valentin KleinandClaude Opus 5.5 45755697b6 Angle prompts read AUNITS, ANGBASE and ANGDIR (fixes #353) (#354)
ROTATE's angle and the hatch pattern angle are rotations (AUNITS, clockwise
when ANGDIR is 1). The dimension line (Rotated) and dimension text angles are
directions from ANGBASE. CHAMFER's angle and the MLEADER angle constraints are
amounts in AUNITS. The ROTATE and HATCH defaults are offered as they would be
typed.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:13 -04:00
Valentin KleinandClaude Opus 5.5 937dca6590 DWG and DXF keep ANGBASE/ANGDIR, fillet/chamfer, elevation and the snap grid (fixes #349) (#350)
acadrust's DXF writer leaves ANGBASE, ANGDIR, FILLETRAD, CHAMFERA/B,
ELEVATION and THICKNESS out of the HEADER although its DWG reader fills
them in; the DWG bridge now adds them to the DXF it hands our reader.
SNAPBASE/SNAPANG are written to and read from the *ACTIVE VPORT record
(groups 13/23 and 50), where R13+ files keep them.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:07 -04:00
Valentin KleinandClaude Opus 5.5 7bed82838d JSON draw/annotation commands refuse invalid sizes and coincident points (fixes #343) (#344)
CIRCLE/ARC no longer make a negative radius positive; POLYGON refuses a
non-integer sides count and a zero/negative radius or zero-length edge;
DONUT, RECTANG, PLINE, HATCH and TABLE refuse negative or zero sizes
instead of clamping them; LINE drops repeated points like the interactive
command; SPLINE needs two different fit points; DIMRADIUS/DIMDIAMETER/
DIMANGULAR refuse coincident points; DDPTYPE refuses an invalid PDMODE;
DIMSTYLE/DIMOVERRIDE list a non-positive DIMTXT/DIMTFAC or a negative
DIMASZ/DIMSCALE/DIMTSZ as ignored.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:32:01 -04:00
Valentin KleinandClaude Opus 5.5 71706f8e6a TL/ML/BL text is placed by its alignment point (fixes #342) (#346)
For every justification but baseline-left (and aligned/fit) the DXF
Reference makes group 11 the point the text is placed by; group 10 is a
computed point on the baseline. Left-justified text with a vertical
alignment was drawn, measured and picked from group 10 and then shifted
by the alignment again: TL and ML too low, BL too high. The renderer
(fonts::place) and text extents (doc text_box, used for picking) now
place it from the alignment point; a mirrored (extrusion 0,0,-1) TL/ML/BL
text keeps its alignment point when it turns right-justified; and the
Properties position moves justified text by its alignment point.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:31:56 -04:00
Valentin KleinandClaude Opus 5.5 2f7a1e60d7 QDIM: one snap index per call, cap the input (fixes #356) (#357)
With DIMASSOC on, every dimension QDIM added looked up the objects under
its definition points by scanning the whole space, and each dimension
adds an entity, so QDIM over many objects took quadratic time (20,000
lines: 28 s). QDIM now builds the SnapIndex DIMCONTINUE/DIMBASELINE use
(#281) once per call; dimensions are no snap targets, so it stays valid
while QDIM adds them. A call naming more than 100,000 objects, or whose
objects give more than 100,000 distinct points, is refused before
anything is created.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 17:31:50 -04:00
Valentin KleinandClaude Opus 5.5 776d99a1e0 FILLET/CHAMFER: Trim, Multiple, Undo; CHAMFER Angle, mEthod, Polyline (fixes #248) (#251)
* Pick box at object-selection prompts; prompts remember the last option

Part of #34.

The crosshair showed the pick box only at "Select objects" prompts. The
commands that pick one object themselves (FILLET, CHAMFER, OFFSET, TRIM,
EXTEND, BREAK, the dimension and constraint picks...) ask with a plain
point prompt, so the cursor stayed a bare crosshair there and hitting an
object was guesswork. The idle crosshair also showed the box with
PICKFIRST off.

- Engine: Prompt::picks_objects() (a selection prompt, or a point prompt
  that starts with "Select") and Session::picking_objects() (that, or no
  command with PICKFIRST on). The canvas uses it for the pick box and the
  rollover highlight.
- The box is sized from PICKBOX as the pick aperture the click uses
  (PICKBOX x 1.5 px half side), hidden at PICKBOX 0, and drawn with a new
  `crosshair` theme token.
- HATCH/GRADIENT: "Select objects" mode is a real mode now ("Select
  objects or [picK internal point/...]", Enter creates the hatch) and the
  next HATCH starts in the mode used last. Pattern, scale and angle
  sub-prompts show the current value as <default>. At select prompts P, L
  and ALL stay selection modes even when a keyword shares the letter.
- POLYGON offers the last Inscribed/Circumscribed choice, ROTATE the last
  angle, SCALE the last factor (Session::last_used, session only).
- FILLET shows the radius as <default>; CHAMFER asks for the second
  distance too, defaulting to the first (FILLETRAD, CHAMFERA/B).

JSON calls are unchanged: explicit params and documented defaults win.

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

* FILLET/CHAMFER Trim, Multiple, Undo; CHAMFER Angle, mEthod, Polyline (fixes #248)

Trim asks for the trim mode (TRIMMODE); with No trim the fillet arc or
chamfer line is added and both objects are kept. Multiple repeats until
Enter and Undo takes back the last fillet/chamfer of the command. CHAMFER
Angle asks for a length on the first line and an angle from it (CHAMFERC,
CHAMFERD, CHAMMODE = 1), mEthod picks Distance or Angle, and Polyline
chamfers every straight corner of a 2D polyline. The JSON forms take
`trim`, and CHAMFER `length`/`angle` and `polyline`.

Keyword matching now prefers a whole keyword or its capital-letter
shortcut over a prefix of another keyword, so `M` is Multiple rather than
the start of mEthod.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:56:01 -04:00
Valentin KleinandClaude Opus 5.5 c26150ec07 Dimension and multileader options take effect (fixes #243) (#247)
DIMORDINATE Xdatum/Ydatum/Mtext/Text/Angle, DIMRADIUS/DIMDIAMETER
Mtext/Text/Angle, DIMANGULAR Mtext/Text/Angle/Quadrant and DIMARC
Mtext/Text/Angle/Partial were listed but ignored (any DIMORDINATE
keyword other than Xdatum forced a Y datum). DIMLINEAR/DIMALIGNED
Angle rotated the dimension line instead of the text.

A shared DimText handles Mtext/Text (override text, <> = measurement)
and Angle (text rotation) for every placement prompt. Quadrant locks
the measured angle to a picked point's side; Partial dimensions the
part of the arc between two picked points. After Horizontal/Vertical/
Rotated only the text options remain listed.

MLEADER: leader Landing first / Content first switch the placement
order, Enter or Options opens the options loop (Leader type, leader
lAnding, Content type, Maxpoints, First/Second angle). Spline leaders,
leaderless and block content say "not available yet". Angle prompts
take numbers so scripts keep splitting the following tokens.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:55 -04:00
Valentin KleinandClaude Opus 5.5 d433112f1e Draw hatch island styles, backgrounds, two-colour gradients and file pattern definitions (fixes #289, fixes #290) (#302)
- Island detection style (group 75) picks the loops that take part: outer keeps the
  outermost loops and their first islands, ignore only the outermost loops.
- A pattern hatch's background colour is filled under its pattern lines.
- Gradients blend colour 1 to colour 2 (LINEAR, CYLINDER, CURVED, SPHERICAL,
  HEMISPHERICAL and the INV forms) along their angle, shifted when not centred, drawn as
  bounded colour steps; ByLayer / ByBlock gradient colours resolve to the hatch's layer /
  block colour instead of colour 7.
- DXF HATCH pattern definition lines (78, 53, 43-46, 79, 49) of a pattern the library
  doesn't define are kept on the hatch (new `patternLines` field), drawn and written back;
  hostile counts are capped.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:49 -04:00
Valentin KleinandClaude Opus 5.5 3e4eb7a0e9 -LAYER: command-line option loop for layers (fixes #254) (#260)
Typed -LAYER (and LAYER in scripts and headless sessions) now starts the
option loop instead of listing the layers and ending: ?, Make, Set, New,
Rename, ON, OFF, Color (with Truecolor), Ltype, LWeight, TRansparency,
Plot, Freeze, Thaw, LOck, Unlock and Description, until Enter. MATerial,
stAte and rEconcile report "not available yet" without taking the next
token. Every option runs the layer.* command bodies, so the session is
one undo step and refusals match the JSON form. Turning the current
layer off asks first; freezing it is refused.

layer.new and layer.set now reject a linetype that is not loaded (or
ByLayer/ByBlock), and layer.set refuses to rename a layer onto the name
of another existing layer.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:44 -04:00
Valentin KleinandClaude Opus 5.5 2483676ba6 -PURGE: type, names and verify prompts; purge by type (fixes #262) (#263)
Typed -PURGE (PURGE has no dialog yet) now asks for the type of unused
objects, the names to purge <*> and whether to verify each name, instead
of purging everything at once and leaving the rest of the line to run as
commands. Verify Yes confirms each name; No purges every match. Types
CADCraft does not have read their answers and report nothing to purge;
Zero-length geometry and Empty text objects purge at once.

The JSON form takes {type, names}: LA purges only layers. All covers
every named type, so unused text, dimension, multileader and table
styles and empty groups are purged as the params doc promised. Layers and
text styles go through layer.delete and style.delete. Linetypes used
only inside block definitions, or set as CELTYPE, are kept.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:39 -04:00
Valentin KleinandClaude Opus 5.5 7a876c74c9 ATTEDIT prompts at the command line; -ATTEDIT; tags in any case (fixes #273, #274) (#280)
ATTEDIT / EATTEDIT typed at the command line ran the JSON form and failed
with "`handle` is required". It now asks "Select block reference:" and then
"Enter value for <TAG> <current>:" for each attribute (Enter keeps the
value); the answers are applied together as one undo step.

-ATTEDIT is a command of its own (Modify > Object > Attribute > Global):
one at a time (Value: Change/Replace, Next) or global string replacement,
over the attributes chosen by block name, tag and value specifications.
Session::start now prefers an id that keeps its leading dash, so -ATTEDIT
is not taken for ATTEDIT; dashed aliases (-INSERT...) resolve as before.

INSERT `attribs` and ATTEDIT `values` match attribute tags in any case and
report the keys no attribute has in `unknownTags`.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:33 -04:00
Valentin KleinandClaude Opus 5.5 5b286119d2 ATTDISP and ATTSYNC; the display follows ATTMODE (fixes #275, #276) (#282)
ATTDISP asks "Enter attribute visibility setting [Normal/ON/OFF]" (default:
the current setting) and sets ATTMODE to 1 / 2 / 0. The renderer now reads
ATTMODE: OFF hides every attribute, ON also draws invisible ones, Normal
draws the visible ones as before.

ATTSYNC ("Enter an option [?/Name/Select] <Select>") brings every reference
of a block, in model space, layouts and other block definitions, in line
with the block's attribute definitions: missing attributes are added with
their defaults, ones whose definition is gone are removed, values are kept
for tags still defined (any case), and position, height, style and
visibility come from the definition.

Both live in the new cmd/blocks/attribs.rs.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:27 -04:00
Valentin KleinandClaude Opus 5.5 cf9fbb8bf5 LAYMRG and LAYDEL: merge layers, delete a layer with its objects (fixes #286) (#296)
LAYMRG moves every object on the source layers (model space, layouts
and block definitions) to the target layer, drops the sources from
viewport freeze lists and colour overrides, and deletes the source
layers. LAYDEL deletes the layers and every object on them the same
way. Typed, both ask "Select object on layer to merge/delete or
[Name]:" until Enter (Undo drops the last one), LAYMRG then asks for
the target layer, and both warn and ask [Yes/No] <No> before changing
anything. Scripts answer Y. The JSON forms laymrg {from, to} and
laydel {names} never prompt.

Layer 0, Defpoints and xref-dependent layers are refused, and so is
merging a layer into itself. LAYDEL refuses the current layer; LAYMRG
merges it and makes the target current. Each run is one undo step.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:21 -04:00
Valentin KleinandClaude Opus 5.5 e9ec19b138 Midpoint, Perpendicular and Tangent snaps on elliptical arcs and splines; Quadrant stays on the arc (fixes #332) (#333)
Midpoint snaps halfway along the length of an elliptical arc or open spline.
Perpendicular and Tangent from a base point find every foot point and
tangent point on ellipses, elliptical arcs and splines (the one nearest the
cursor wins, as for circles): tangents to an ellipse exactly through the unit
circle, the rest by sampling the parameter range and bisecting each sign
change, with fixed iteration bounds. Quadrant on an elliptical arc offers only
the quadrant points the arc passes through.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:16 -04:00
Valentin KleinandClaude Opus 5.5 6a15ba2a7d Dimensions snap; justified text snaps and grips at its alignment point; grid snap follows SNAPBASE/SNAPANG; OSNAPHATCH (fixes #334) (#337)
- A dimension snaps to what it draws: the objects of its block (read from a
  file), expanded like block references, or else the lines generated from it
  (Endpoint on their ends, Midpoint, Intersection, Nearest); Node on its
  definition points.
- TEXT justified other than left-baseline (as read from DXF, group 11)
  snaps (Insertion) and grips at its alignment point; attributes too.
- The snap grid starts at SNAPBASE and turns by SNAPANG (drawing variables,
  defaults 0,0 and 0 degrees). SETVAR takes `x,y` for point variables.
- Hatches are left out of object snaps unless OSNAPHATCH is 1 (new session
  variable, default 0 as in AutoCAD).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:12 -04:00
Valentin KleinandClaude Opus 5.5 b1a78fbb8f Typed angles follow ANGBASE, ANGDIR and AUNITS (fixes #326) (#328)
Polar points and angle prompts read bare numbers in AUNITS (explicit
d/r/g, deg-min-sec and bearings still work) and measure directions from
ANGBASE in the ANGDIR sense; `d<<a` (degrees) and `d<<<a` (AUNITS)
measure from +X counterclockwise. XLINE Reference turns per ANGDIR;
included angles and sweeps take the units only. ANGBASE is held in
radians like $ANGBASE in DXF; SETVAR reads and reports it in degrees.
JSON params keep their meaning.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:07 -04:00
Valentin KleinandClaude Opus 5.5 fe0eb45537 Dimensions take viewport layer colours, leader arrows their style's DIMSCALE, attributes their own properties (fixes #313) (#319)
- A dimension on a layer with a viewport colour override now takes the
  override inside that viewport (dimension and extension lines, arrows,
  text), like every other object.
- A LEADER's arrowhead is DIMASZ x DIMSCALE of the leader's dimension
  style; the drawing's DIMSCALE only applies when the style's is 0 (as
  for dimensions), and both 0 no longer gives a zero-size arrow.
- Attributes are drawn with the properties of their attribute
  definition, which AutoCAD copies to the attribute on insertion:
  ByLayer = the definition's layer (layer 0 = the block reference's
  layer), ByBlock = the block reference's colour and lineweight,
  explicit colours and lineweights as set. An attribute on an off or
  frozen layer is hidden. Attributes without a definition keep the block
  reference's colour and now its lineweight too, instead of 0.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:55:02 -04:00
Valentin KleinandClaude Opus 5.5 282487fbc5 MCP: reject tool arguments that don't fit the schema; query_entities counts all matches and windows by geometry (fixes #335) (#336)
tools/call checks the arguments against the tool's input schema: an
unknown tool, non-object arguments, a missing required argument or one
of the wrong type is a JSON-RPC -32602 error, as the MCP spec says, so
`command_line {}` no longer presses Enter and `script {}` no longer
succeeds. An empty batch `[]` gets one Invalid Request (-32600).

The `entities` command behind query_entities rejects a malformed window,
non-string type/layer and bad limit/offset instead of ignoring them.
`count` is every match, `returned` the page (`offset` is now in the tool
schema), and a window matches objects whose geometry is inside or
crosses it (the window/crossing selection test), with `crossing: false`
for entirely inside.

list_commands' filter matches command ids, labels and aliases, not any
text in the command's JSON.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:54:58 -04:00
Valentin KleinandClaude Opus 5.5 2e3da66380 Refuse edits whose result has infinite or undefined coordinates (fixes #338) (#339)
Inputs to MOVE, SCALE, ROTATE, ARRAY, OFFSET and the rest are checked for
being finite, but finite inputs can still overflow: moving by 1e308 twice
put a circle at infinity, after which inspect showed null coordinates,
the extents were null and a DXF save wrote it at 0,0.

After a command changes the drawing (JSON `execute`, grip edits, and an
interactive command when it ends), the objects it changed are checked:
every number in their fields and their extents must be finite. If not,
the drawing and selection are put back, no undo step is recorded and the
command fails with "the result is too large: object … would get infinite
or undefined coordinates. Nothing was changed".

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:54:53 -04:00
Valentin KleinandClaude Opus 5.5 0fef879509 Grip edits act on the whole selection (fixes #320) (#322)
Stretching a grip now drags the grips of the other selected objects that sit
on the same point, so connected lines stay connected, and the Move, Rotate,
Scale and Mirror grip modes transform every selected object about the hot
grip, as in AutoCAD. grip.move/grip.stretch take the other objects in
`handles`; with only `handle` they edit that object alone, as before.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 16:54:48 -04:00