Commit Graph
539 Commits
Author SHA1 Message Date
Tanish Manku 32db96f5e3 Stage 1 stack 3: review fixes on #1105, #1002, #1908, #1907, #2134, #2371 2026-11-06 13:35:40 -07:00
Tanish Manku b1263ed3a4 Merge branch 'stk-1908' into stack/s3 2026-11-06 13:31:50 -07:00
Tanish Manku 42c1dca89d Merge branch 'stk-1105' into stack/s3 2026-11-06 13:29:27 -07:00
Tanish Manku 1f11d4f701 Stage 1 stack 2: review fixes on #2587, #2585, #2608
- #2587: a frame turned by anything but a quarter turn keeps its size about its turned
  centre; the box of its two mapped corners collapsed it (a 10 x 10 square turned 45° became
  14.14 x 0, and the OLE header with it) for EXPLODE, REFEDIT, paste and array placement
  (austin62jake, CodeRabbit). Moves, scales, mirrors and quarter turns keep the exact
  corner mapping. `recase` reads only the rewritten digits and a few after them, not the
  whole 310 line (austin62jake). The PR's rotated-frame test expects the kept size.
- #2585: an `output` that contradicts the path's extension is refused, as a device that
  does is; it lost silently to the extension.
- #2608: the window's state is dropped when another dialog took its place (it missed a UI
  pass), so reopening reads the drawing instead of an old unsent edit (CodeRabbit); TEXT and
  ATTDEF get a one-line field (Enter commits), so a line break can't turn into a space on
  save; the module comment says the shortcut menu opens it.
2026-11-05 20:16:44 -07:00
Tanish Manku 173aa38f35 Merge branch 'stk-2585' into stack/s2
# Conflicts:
#	crates/engine/src/cmd/layout.rs
#	crates/engine/src/cmd/layout/tests.rs
2026-11-05 19:22:47 -07:00
Tanish Manku 7218285443 Merge branch 'stk-2587' into stack/s2
# Conflicts:
#	crates/doc/src/extents.rs
2026-11-05 19:22:25 -07:00
Tanish Manku 2b88b22949 Stage 1 stack: review fixes on #2584, #2471, #1626, #2028, #1026, #2658, #2612
- #2584: a tilted LWPOLYLINE/HATCH run is flattened to at most a million vertices, then
  its arcs stay chords (a hostile file of arcs asked for gigabytes; austin62jake). The
  file-format-parity.md hunk is dropped: maintainers update the parity docs.
- #2471: another program's proxy objects are in the DWG loss report as its proxy entities
  are (left out before 2013, or when their class can't be named); they went silently.
- #1626: a drawing saved at a view keeps it in the desktop app and when it has xrefs: the
  app's first frame and the xref pass both fitted the extents over it. OPEN's result says
  `saved_view`; one `view::saved_view` replaces the copy of `usable_view` in files.rs.
  A misplaced doc comment goes back to its test.
- #2028: a plot rotation (73) outside 0-3 is written back as 0, not as read.
- #1026: the binary code-page retry drops the first pass's tags before it runs (the peak
  doubled near the tag limit); a float spelling of an integer group clamps to what the
  width holds unsigned too, so 65535.0 reads as 65535 does, not 32767.
- #2658: the reference names Undo by the last command ("Undo Group of commands"); Undo
  matched nothing and went to the end of Edit. Ordering and its test share one rule.
- #2612: its test's Insert gets `clip: None` (XCLIP landed after it was written).
2026-11-05 18:34:43 -07:00
Tanish Manku 12db7237cc Merge branch 'stk-2545' into stack/s1 2026-11-05 17:23:13 -07:00
Tanish Manku 831a437f1e Merge branch 'stk-2612' into stack/s1 2026-11-05 17:23:13 -07:00
Tanish Manku 577b2f74d6 Merge branch 'stk-2471' into stack/s1 2026-11-05 17:23:12 -07:00
SBaksa 63f53f6ed6 ARRAY, ARRAYPATH: one OLE frame check, after the matrices (review on #2587)
A matrix every frame accepts (a rectangular array, plain translations) now needs no
look at the objects, and an empty matrix list never touches the drawing. ARRAYPATH
scans the selection once instead of once per item.
Covered by the existing refusal tests; copy_all is not reachable without a drawing
through any command, so there is no no-drawing test.
2026-11-05 15:34:23 +11:00
SBaksa 485c74da6b ARRAY: check OLE frames once (review on #2587)
copy_all scanned the selection for an OLE frame once per matrix before copying.
It now looks at the objects once and then only tests each matrix.
Covered by the existing refusal tests (classic_polar_array_refuses_a_turned_frame_but_not_an_upright_one).
2026-11-05 14:00:28 +11:00
Tanish Manku f554de28b5 XCLIP: ON/OFF checks without copying boundaries and sets each reference once (CodeRabbit on #2756) 2026-11-04 13:02:38 -07:00
ffishman 190a44d3eb Merge main into fix/dwg-render-settings-class-version
main's #2673 runs the DWG writer in a child process. The kept version now goes with the request
(encode_kept_noted, worker protocol 2), so the worker reads the bridge DXF as that version too.
The proxies proxy_classes leaves out are now counted in Unwritten, as ACAD_PROXY_OBJECT and
ACAD_PROXY_ENTITY.
2026-11-04 15:46:24 -03:00
Tanish Manku 371e8b9e02 XCLIP: ON/OFF checks every selected reference before changing any (CodeRabbit on #2756) 2026-11-03 21:56:58 -07:00
Tanish Manku 0d72152142 Merge main into land/2354 2026-11-03 18:21:00 -07:00
Tanish Manku 639c2e9825 XCLIP: clip on DVIEW's preview reference 2026-11-03 18:20:48 -07:00
Tanish Manku 100d812155 Merge main into land/2354 2026-11-03 18:07:17 -07:00
Tanish Manku de753897c4 Merge main into land/1944 2026-11-03 18:00:03 -07:00
Tanish Manku 39ee2e5960 SHX fonts: review fixes (search the current folder for a bare drawing name, one lock for the font cache generation, no split_paths on wasm, MTEXT \U+ upper case as the layout, a point budget for repeated linetype shapes, web picks report skipped files and cap bytes read at once) 2026-11-03 17:59:52 -07:00
Tanish Manku 76d09aaddf Merge remote-tracking branch 'origin/main' into land/2354 2026-11-03 17:22:36 -07:00
Tanish Manku d1e6f4df96 XCLIP: invert flag as group 290, extents are the boundary's box, Delete with no boundary asks again; clip on main's new Insert literals 2026-11-03 17:22:36 -07:00
Tanish Manku c2d62a2842 Merge main into land/1944 2026-11-03 17:11:42 -07:00
Tanish Manku 68e01dd487 Merge main into land/1230 2026-11-03 17:11:25 -07:00
Tanish Manku 26e96cfb56 View twist: a locked viewport keeps its twist; the untwisted file view fits the extents as the turned view sees them
Review fixes from CodeRabbit on #2748:
- DVIEW TWist and view.set refuse to turn a locked active viewport ("The viewport is locked.")
- the untiled *Active VPORT record fits the extents rotated by the twist it writes, with the twist normalised
2026-11-03 17:10:20 -07:00
Tanish Manku f10e41409d Images: read no more than the limit, keep plot bounds to what a viewport shows, free textures with no images left
Review fixes from CodeRabbit on #2749:
- the image file is read through read_capped, so a file that grows after the size check can't exhaust memory
- an image cut by a layout viewport widens the sheet's bounds only by the part inside it, as other primitives do
- uploaded tiles of closed drawings are dropped also when the next list has no images
2026-11-03 16:52:11 -07:00
Tanish Manku 8232290830 View twist: a window too large to turn saves no view, and the saved one stays 2026-11-03 15:40:39 -07:00
Tanish Manku 457fc2319e View twist: review fixes
- Every engine twist (DVIEW, view.set, opening a drawing, the untiled view) goes through norm_angle, so a tiny negative twist is 0, not a full turn.
- VIEW Window in a twisted view stores the window's size along the view's axes (View::twisted_box), so a restore shows all of it.
- A deferred tangent or perpendicular snap on an arc in a twisted layout viewport turns the arc's angles with its centre.
- The active viewport's cached drawing is rebuilt when its twist changes; moving and scaling it can't turn it.
2026-11-03 15:14:10 -07:00
Tanish Manku 8cdcd6f85c Merge #2354 (XCLIP and XCLIPFRAME) onto main 2026-11-03 15:00:20 -07:00
Tanish Manku 28421dd1d7 Merge #1944 (SHX shape fonts, big fonts and unifonts; linetype shapes) onto main 2026-11-03 14:59:22 -07:00
Tanish Manku 4c00b6732f Merge #2505 (raster images drawn on screen and in PNG output) onto main 2026-11-03 14:52:15 -07:00
mattlafy 852688e000 Merge remote-tracking branch 'upstream/main' into wt-dview 2026-11-03 14:17:57 -05:00
Dylan M. Taylor f2afcd4f02 fix(engine): look names up in an index in AUDIT, layer-state restore and CAL (#2712)
AUDIT checked every layer's linetype and every style's text style by scanning the table; LAYERSTATE restore and restoring a named view's layer snapshot scanned the layer table for every saved layer; CAL scanned the variable map for every case-insensitive reference. Each now builds its name index once, keeping the same first-match and exact-match rules.
2026-11-03 09:07:10 -05:00
Khuong DoanandClaude Opus 5.5 7bb6c2f06f MINSERT: a block reference repeated in rows and columns, as AutoCAD does (#2698)
* MINSERT: a block reference repeated in rows and columns

MINSERT asks INSERT's questions, then the grid's as AutoCAD 2024 does
(measured with accoreconsole): the numbers of rows and columns (1 to
32767), the distance between rows or a unit cell when there is more than
one row, the distance between columns when there is more than one column,
then the attribute values once. A distance along an axis with one copy is
0, and one row by one column is a plain reference.

EXPLODE leaves a grid whole ("The object is minserted.") instead of
exploding its first copy and dropping the rest. MIRROR mirrors a block
reference as AutoCAD does, negating the X scale factor and a grid's
column distance, so a mirrored grid's rows stay on the right side. An
attribute with no default is asked without "<>".

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

* MINSERT JSON: refuse a distance that is not a number

`rowSpacing` and `columnSpacing` went through `f64_or`, which takes any
value that is not a number as the default, so `"rowSpacing": "15"` stored 0
and put every copy on top of the others without an error. They are now
checked as `rows` and `columns` are: absent or null is 0, a finite number is
kept, anything else is refused.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:06:58 -05:00
Dylan M. Taylor 9a112830c5 fix(engine): merge another drawing's tables and blocks in linear time (#2689)
INSERT of a file, IMPORT, paste and COMPARE export each checked every incoming layer, linetype, style and block name by scanning the growing destination vector, and restarted the anonymous block name search from 1 at every collision, so a compact drawing with many distinct names made the command quadratic. The four copies of that code become one helper (cmd/merge.rs) that looks names up in a set built once per table and continues each generated prefix from the last number it allocated. Xref loading does the same for its dependent symbols, and no longer rescans the block table after a fetch that imported nothing.
2026-11-03 09:06:49 -05:00
SBaksa 457627df33 OLE2FRAME: validate hex strictly, rewrite only the header groups, cheaper corner lookup (review on #2587)
A sign in a hex pair is no longer accepted; only the 310 groups holding the 98-byte header are decoded and rewritten, in place and without cloning the payload; the corner lookup is one allocation-free pass; the io ole_frame test module is not built for wasm like its DWG siblings; Unknown::is_ole_frame replaces five copies of the type check. Tests: a_plus_sign_in_ole_data_is_not_hex, a_large_ole_payload_only_rewrites_its_leading_groups.
2026-11-03 16:44:30 +11:00
SBaksa be413af026 OLE2FRAME: arrays refuse before adding copies; clearer refusal (review on #2587)
ARRAYPATH with align and polar arrays that rotate items refused part way, leaving the earlier copies in the drawing. Every copy's matrix is now checked before the first copy is made. The refusal text names the other unsupported transforms. Test: arraypath_refuses_before_adding_any_copy.
2026-11-03 16:37:51 +11:00
SBaksa 42e3d964dc Merge remote-tracking branch 'origin/main' into ole-frame 2026-11-03 16:22:22 +11:00
SBaksa 9bd5c479e3 OLE2FRAME: SCALE carries a frame with a uniform positive factor (review on #2587)
ole_frame_accepts took only a translation, so SCALE on a selection with a frame failed as a whole. A uniform positive scale keeps the frame axis-aligned and upper-left, so the corner rewrite handles it; rotation and mirroring are still refused. Tests: scale_carries_an_ole_frame_with_the_rest_of_the_selection, mocoro_scales_an_ole_frame, ole_frame_accepts_translations_and_uniform_positive_scales_only.
2026-11-03 15:45:05 +11:00
5ed192555c EXPLODE: a block reference with different X and Y scales gives ellipses, not circles (#2649)
* EXPLODE: a block reference with different X and Y scales gives ellipses, not circles

EXPLODE of a block reference whose X and Y scales differ kept its circles
as circles with the mean scale, its arcs as circular arcs, its ellipses
with their old ratio and its polyline arcs as circular bulges: not the
shape the reference draws. BURST and INSERT with explode go the same way.

When the reference's transform isn't a similarity, a circle now becomes a
full ELLIPSE, an arc an elliptical arc, an ellipse gets its true axes, and
a polyline with arc segments (and no widths) its lines and elliptical
arcs. Everything else, and every evenly scaled or mirrored reference,
transforms as before.

The exact ellipse transform and the similarity test move unchanged from
snap.rs to cadcraft-geom (Ellipse::transformed, Mat3::similarity_scale)
and are shared by object snaps and EXPLODE.

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

* contributors: add Tyler-Chiriboga

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

---------

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-02 22:54:03 -05:00
4c00f8b9e3 FIND: a Find and Replace window, not only the command line (#2607)
* FIND: report the text around each match

The result list needs to show what matched, not just its handle, so each entry now carries a snippet of the text around the first hit, the hit's offset in it and its length. Offsets count characters, not bytes, so a Chinese or Vietnamese drawing never splits a glyph.

Two optional parameters let a window search part of the space: `types` narrows the search by object type name and `handles` limits it to the objects a caller has already listed, which is what makes replacing a single row possible. Both default to the old behaviour, so scripts, LISP and the MCP server get the answer they always got.

* FIND: a Find and Replace window

FIND only had a command line: it asked for the text, ran, and printed a count, and the palette's Find and Replace tile opened the same prompt. The answer lived in the drawing and the place to read it was the command line.

This adds a window behind the same command. The result list shows each object with the hit lit up, a click selects that object and zooms to it, Replace edits the row under the cursor and Replace All the rows on the list. The window stays open while the drawing is edited, because finding and replacing is a loop: look, locate, edit, look again.

A JSON call carries its parameters, so scripts, LISP and the MCP server still get the engine's answer; only the bare command opens the window. Cmd+F reaches it from anywhere, and the twenty labels it adds are in every interface language.

* FIND: clear the window's state when it closes or the drawing changes

The result rows hold handles, and a handle only means something in the
drawing it was found in. The state now lives exactly as long as the window
does, and an epoch change while the window sits open clears the rows instead
of letting Replace All edit an object the search never matched. A successful
replacement keeps the engine's replacement report rather than letting the
re-search overwrite it with a match count.

* FIND: an empty replacement is a deletion, and stale rows die on any change

Replacing with nothing deletes the matched text, which is a legitimate
replacement, so the buttons no longer require text in the Replace field.

The result rows answer one question - this text, with these switches, in this
drawing and space. Editing the search term or either switch now clears the
rows and owes a fresh search, and so does switching between model space and a
layout: a handle means nothing outside the space it was found in.

* rustfmt: sort the module list after the merge

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-11-02 22:53:24 -05:00
455b2a87cb EXTEND: a polyline's arc end segment runs on round its circle (#2656)
EXTEND on an open polyline ran a ray along the chord of an arc end segment
and kept the old bulge, so the arc changed radius and centre, or the
boundary was reported missed when only the circle reached it. The arc end
segment now runs on round its own circle to the nearest crossing ahead,
turning the same way, and its bulge becomes that of the total turn, as
EXTEND does with an ARC. The polyline also keeps its constant width,
elevation and linetype generation, which the rebuilt polyline dropped.

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:52:39 -05:00
Dylan M. Taylor 1b7c705fac Dynamic blocks: index kept objects once per listing; copy each value record once (#2678)
* Dynamic blocks: index kept objects once per listing and copy each value record once

Listing blocks rebuilt a handle index of every kept foreign object for each
block with an extension dictionary, multiplying the block count by the object
count. The listing now builds one ObjectIndex and asks is_dynamic_block_in per
block; dynamic_block answers None before indexing when the block has no
extension dictionary.

templates() copied a value record's tags once per dictionary key, so a record
listed under many keys was copied many times, and it looked up each
dictionary entry by scanning every object. It now resolves entries through
its handle index and copies each record at most once (CWE-400, CWE-407).

* Dynamic blocks: list a block as dynamic from its graph object without walking the graph's members
2026-11-02 22:52:35 -05:00
mattlafy cbc9db75b1 Merge remote-tracking branch 'upstream/main' into pr/dview
# Conflicts:
#	crates/engine/src/prompt.rs
#	crates/engine/src/session/execute.rs
#	crates/io/src/dxf_read/entities.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/cmdline.rs
2026-11-02 21:53:26 -05:00
SBaksa a9dbc50ce2 Merge origin/main into ole-frame (drop parity-checklist change) 2026-11-03 13:46:27 +11:00
Dylan M. Taylor 0d3b3d21c0 Regions: test a loop's probe only against loops whose box holds it, within a budget (#2680)
Region::renest tested every loop's probe point against every other
tessellated loop, scanning all of its edges each time, so the work was the
loop count times the total vertex count. It now skips loops whose bounding
box does not hold the probe, and stops once MAX_RENEST_EDGE_VISITS edge tests
have been spent, leaving the flags unchanged and returning false; UNION,
SUBTRACT and INTERSECT then report the regions as too complex to combine, as
they already do for too many boundary pairs (CWE-407).
2026-11-02 21:16:51 -05:00
ffishman 6c80e04faa Merge main into fix/dwg-render-settings-class-version
main's #2474 and #2582 change not_saved_as next to the proxy class check, and #1725 adds a test next to ours; all are kept.
2026-11-02 21:38:02 -03:00
Tanish Manku eacdbe43ee Save: a folder that can't be made says so, not that the backup failed (#2669)
The backup ran before the folders were made. On Linux, a path under a file
gave 'the backup copy could not be written' instead of naming the folder.
2026-11-02 17:10:53 -07:00
Tanish Manku bf4d51bf61 Merge #2260: a save to a missing folder writes the file and says so
The folders are made by a new writer only user-named targets use, so autosave
and a dangling symlink's target keep the strict write_atomic. Save, Save As,
Export, Plot, WBLOCK, layout templates, render and the CLI report the topmost
new folder at the command line and in createdFolder.
2026-11-02 16:48:16 -07:00
STE1200andClaude Opus 5.5 5ec0d215d8 CLI: info and convert report what a damaged DWG left out (read notes, #859) (#2337)
A damaged DWG opens partially since #859 and the drawing's read notes
say how many object records were left out. OPEN echoes them and MCP
`open` returns them as `warnings`, but `cadcraft-cli info` and
`convert` stayed silent: a corpus round trip wrote thousands of skipped
records away without a word.

- convert: each read note goes to stderr as `IN: <note>`, next to the
  skipped-records warning (#872). The exit code stays 0.
- drawing.inspect (and so info, MCP inspect_drawing and the control
  channel): new field `readNotes`, next to `skipped`. Existing fields
  are unchanged.
- run already prints OPEN's notes with the session log.
- README and docs/control-protocol.md say where the notes appear.

Part of #1039.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:01:51 -07:00