* OOPS brings back what -WBLOCK erased
-WBLOCK erases the objects it writes, and OOPS brings back what the
last ERASE or BLOCK removed; the two landed separately, so OOPS never
saw what -WBLOCK erased. -WBLOCK now erases through erase_kept, as ERASE
and BLOCK do.
As AutoCAD 2024 does (accoreconsole, the drawing's entnext order read
back): OOPS after -WBLOCK brings its objects back, each where it was and
with its handle; only the last erase counts, -WBLOCK's or ERASE's; and
-WBLOCK * erases nothing, so OOPS still brings back the erase before it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* OOPS: keep the last erase through a refused REFEDIT command; Block Editor
During REFEDIT, a command that removes an object outside the working set
is undone (refedit::after_command), but erase_kept had already replaced
what OOPS kept: the objects came back, and the erase before them was
lost. erase_kept now leaves OOPS as it was when an object it removes is
outside the working set. That covers BLOCK, -WBLOCK, ERASE and the
Delete key.
In the Block Editor, its objects keep the handles they have in the block
definition, so in_blocks found them there and OOPS brought back nothing
erased in the editor. in_blocks now skips the block open there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
There was no OOPS. It now brings back what the last ERASE removed (the
Delete key too), or BLOCK deleting the objects it makes a block of, as
AutoCAD 2024 does (accoreconsole, the drawing's entnext order read
back):
- Each object keeps its handle and goes back directly above the object
that was under it, or under the one that was above it when that is
gone. An erase records both neighbours (EntityStore::neighbours).
- Only the last erase counts; entdel doesn't. OOPS says nothing and is
one undo step.
- An object stays out when it is already in the drawing, when an undo
took the drawing back to before it was made, when its layer, block or
layout is gone, and, during REFEDIT, when it isn't in the working set.
REFCLOSE forgets the working copies; the Block Editor has an OOPS of
its own.
- The command's label is in the 17 interface catalogs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
AutoCAD 2024's SCALE is a uniform 3D scale about the base point: every Z
and elevation scales about the base point's Z (the current ELEVATION for a
point given without one) and every thickness by the factor. cadcraft
scaled X and Y only. The JSON form also takes a Z with the base point.
OFFSET's copy now keeps the source's height: a line's end Zs, a circle's
or arc's centre Z, a polyline's elevation. A circle's or arc's copy has no
thickness and a polyline's keeps it, as AutoCAD makes them.
Grip scaling, ALIGN's scale option and MOCORO stay 2D: their base point is
often a grip or snapped point whose Z cadcraft doesn't keep.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Pure extraction, no behaviour change: same order of side effects, same
early returns and error values. Clears cognitive-complexity findings on
AreaM::input, explode_kind, hatch sweep, the COMPARE overlay paint and
the Files options tab (S3776).
* refactor: replace wildcard imports with explicit ones (S2208, batch_06)
* Restore imports that the cadcraft batch dropped so the engine compiles
The batch replaced glob imports with explicit lists that missed names
the files still use, and pointed some sub-module imports at the wrong
level. Restore the required imports and the test module globs.
* Format pedit_vertex imports so the CI fmt gate passes
DIMTFILL and DIMTFILLCLR were not modeled, so a dimension's text never had a
background and lines under it stayed visible. Dimension styles now carry both
variables. DIMTFILL 1 hides what is drawn under the text in the drawing's
background color, and DIMTFILL 2 fills the box in DIMTFILLCLR. The box is the
text box widened by DIMGAP on every side. The arc length symbol over the text
and the DIMINSPECT frame are drawn over the background.
The variables work with SETVAR, DIMOVERRIDE and the style fields. DXF stores
them as DIMSTYLE groups 69 and 70 from R2007 and in the style's CADCRAFT
xdata in older files; the reader skips the record's flags, which also use 70.
For DIMTFILL 2, the dimension's *D block and EXPLODE add a SOLID in
DIMTFILLCLR under the text. A dimension drawn from the *D block it was read
with gets a background around each of the block's texts, before the block's
first text or first segment of the text's marks, except for a text that a
SOLID of the block already covers. The background stays after the drawing is
saved and reopened.
* STRETCH moves only the multiline vertices inside the area (#1712)
* STRETCH selects a multiline when a crossing area holds one of its vertices (#1712)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The *D block and EXPLODE's MTEXT took the dimension's decoded text, so
%%c arrived as U+2300, which no SHX font has: other programs plotted a
diameter dimension in an SHX style without its sign. They now write
DimGeometry::mtext_contents(): the sign as %%c, which every font draws
(and CADCraft decodes), a % that would run into a code as %%%.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* OFFSET uses a single preselected object (#1610)
With exactly one offsettable object selected before OFFSET, take it and go
to the side prompt instead of asking to select an object again; a multiple
selection keeps the select-object loop. AutoCAD ignores preselection here,
but this is the requested behaviour for users coming from other tools.
* docs: document the functions touched by #1610
Found while testing crates/engine/src/cmd/modify.rs:1457 (break_entity; helpers `line()`/`arc()`/`lwpoly()` in crates/engine/src/cmd/helpers.rs:10-23)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/engine/src/cmd/modify.rs:584-597 (offset_kind, EntityKind::Ellipse branch)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
With ORTHOMODE on, the opposite corner of STRETCH's crossing window was bent
onto an axis, so the window collapsed and caught nothing. The prompt set only
the rubber-band base; it now marks it as a window corner (Prompt::corner), as
ZOOM Window and RECTANG already do.
Same fix for the other two-corner rectangles: the Window/Crossing/Box selection
modes, PLOTWINDOW, MVIEW (and its Restore), -VIEW Window and REVCLOUD
Rectangular. Regression tests in crates/engine/tests/stretch_window_ortho.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BREAK and BREAKATPOINT (break_entity) and line/arc/polyline JOIN
(modify::join) removed their source objects and added replacements
without checking the source layer's lock, so a locked line could be cut
or merged into a polyline. ERASE, MOVE, TRIM, FILLET, EXPLODE and the
ellipse/spline JOIN already guard against this.
Both now refuse with the same errors the sibling commands use:
"The object is on a locked layer." (BREAK, BREAKATPOINT, like FILLET)
and "An object is on a locked layer." (JOIN, like the ellipse/spline
join). JOIN refuses the whole request when any selected object is
locked, so a mixed-layer pair keeps its locked original in either
order. The guards sit in the shared functions, so JSON calls with
explicit handles or the selection, the interactive prompts and the
command line all reach them. The interactive commands report the
refusal on the command line.
Tests (crates/engine/tests/break_join_locked_layers.rs, red before the
fix): BREAK and BREAKATPOINT on a locked line by handle and by
selection; BREAK on a locked arc and circle; JOIN of locked lines and
of a locked/unlocked pair in both orders; interactive BREAK and JOIN.
Each checks the drawing is unchanged while locked, and that the same
edit works unlocked (BREAK also undoes and redoes).
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
At "Select objects", CP and WP take a polygon (Enter closes it) whose
inside marks the vertices that move, as a crossing window does; F takes a
fence, and what it crosses moves whole, like objects picked one at a
time. BOX works too, Undo takes back the last point, and the polygon or
fence is drawn while its points are placed.
The JSON form takes `polygon` or `fence` in place of `window`; both are
capped at the selection modes' point limit and refuse non-finite points.
Co-authored-by: Chanthawat Kiriyadee <enwuft@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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>
* TRIM: a pick after a closed polyline's last cut keeps only the complementary run (#726)
With no cutting intersection past the pick, trim_polyline used the first cut
plus the perimeter as the end of the piece and then kept the run from the
first cut to the last cut plus a whole perimeter, so one extra circuit of the
outline stayed (a 4 x 4 square became an open polyline of length 24 instead of
8). The kept run now ends at the last cut before the pick.
* Docs: update the progress docs for this change
The options at MTEXT's "Specify opposite corner" prompt printed "not
available yet". Each now asks its question and returns to the corner
prompt: Height (also sets TEXTSIZE), Justify (TL..BR, the insertion point
is the box's attachment point), Line spacing (At least/Exactly, then a
factor such as 1.5x or a distance), Rotation (picked, or typed through the
drawing's angle settings; the box turns with it), Style (? lists them),
Width (replaces the opposite corner) and Columns (No; Dynamic and Static
say columns are not available yet). MText stores the line spacing style
(DXF group 73) so Exactly round-trips.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
* 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 <noreply@anthropic.com>
* 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 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
OFFSET's Erase and Layer keywords open their Yes/No and Current/Source
prompts (remembered in OFFSETERASE/OFFSETLAYER and shown in the current
settings line); Multiple keeps offsetting the newest copy until Enter and
Undo takes back the last offset. The JSON form takes `erase` and `layer`.
MOVE and COPY ask for a displacement after the Displacement keyword or
Enter at the base point prompt.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The offset copy of a spline was always rebuilt with the open fit-point
interpolation, so a closed spline came out open with a corner at its start
point. Use Spline::from_fit with the source's closed flag, which gives a
periodic spline for closed input.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Array commands: ARRAY, and the edit-array option loops for ARRAYRECT/ARRAYPOLAR (#230)
ARRAY (alias AR) selects objects, asks for the array type and continues as
ARRAYRECT, ARRAYPATH or ARRAYPOLAR. ARRAYRECT no longer creates a fixed 4x3
grid right after selection: it previews the array and loops on
ASsociative/Base point/COUnt/Spacing/COLumns/Rows/Levels/eXit. ARRAYPOLAR
asks for the center (or Base point), then loops on Items/Angle between/
Fill angle/ROTate items/eXit. The copies are created on eXit or Enter.
Associative arrays, levels and polar rows report "not available yet" and
return to the loop without consuming the next input.
The array commands move to cmd/array.rs. Arrays stay non-associative
copies, and the JSON forms are unchanged. A full clockwise polar fill
(-360) no longer puts the last item on top of the source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs/gaps.md: narrow F5 (array option loops, #230)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arrays: refuse arrays over MAXARRAY instead of hanging or shrinking them (#230)
`arrayrect {"rows":1000000,"cols":1000000}` was clamped to 1000x1000 and
then created a million copies of every selected object, which hangs the
app. ARRAYRECT, ARRAYPOLAR and ARRAYPATH (JSON and interactive, also via
ARRAY) now count items x selected objects and refuse an array over the
new MAXARRAY session variable (default 100000, settable 100..10000000)
with a message naming the limit, instead of clamping the counts and
silently creating fewer copies. The interactive count prompts accept at
most what still fits under MAXARRAY.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>