Their boundary and vertices are in the object's plane (OCS). Only a
mirrored plane (0,0,-1) was handled, by flipping x; a tilted plane whose
normal points down was taken for a mirror too, and one pointing up kept
its plane coordinates, so each face landed turned about the origin by
its own angle. A tilted one is now mapped to the WCS and drawn as seen
from above at its mean height: bulged edges are flattened (they are
elliptical there), and a hatch's pattern direction and origin follow.
Its extrusion becomes Z, since its points are plan coordinates now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add -ARRAY, the classic command-line array, as AutoCAD lays it out
Rectangular arrays move the copy in row r and column c by (c * column distance, r * row
distance), asking for a distance only when there is more than one row or column; polar arrays
space n items 360/n apart around a full circle and fill/(n - 1) otherwise, turning the copies or
moving them with the group 10 point of the object selected last. The copies are separate
objects; the type asked defaults to the one used last.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -ARRAY: refuse an unknown type and oversized arrays before making transforms
The JSON form errors on a type other than rectangular or polar, and checks MAXARRAY before the
copies' transforms are made; the command line bounds the columns by the rows (CodeRabbit).
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>
* Turkish catalog: fix wrong registers and resolve term collisions
Proofreading by a native speaker. 32 rows, wording only:
- From/To were e-mail words (Kimden/Kime) -> Kaynak/Hedef
- ViewCube Home was Giris (login) -> Baslangic
- untranslated "or" in the zoom scale prompt -> veya
- BREAK vs Cut both "Kes" -> BREAK family uses "Kir"
- TRIM vs CLIP both "Kirp" -> TRIM uses "Buda"
- spline vs curve both "egri" -> spline stays "spline"
- plot style/preview/window said "Cizim X" (= drawing) -> "Cizdirme X"
- Elevation vs Height both "Yukseklik" -> Elevation is "Kot"
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add GULERTOLGA to contributors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add Vietnamese (vi) to the interface languages
vi.tsv covers every key of the other catalogs and the three strings none of them has yet, with
one plural form. Its tone-marked letters (U+1EA0-U+1EF9) aren't in egui's bundled faces, so the
Vietnamese interface takes the first system interface font that has them (Segoe UI, Arial, SF,
DejaVu Sans, Noto Sans) and otherwise stays English and says why, as Korean does without Hangul.
AI-assisted; native-speaker review pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vietnamese: the terminology Vietnamese AutoCAD users speak
Polyline, Pan, Offset, Spline and Grip stay in English as they are said in the office; Modify
is Hiệu chỉnh, LINE draws an Đoạn thẳng, Explode is Phân rã, Linetype Loại nét, the plot style
table Bảng nét in, Polar and Object Snap Tracking Dò theo góc / Dò theo điểm bắt, Settings Cài
đặt; the snap modes Endpoint, Midpoint and Nearest are Đầu mút, Điểm giữa and Điểm gần nhất.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vietnamese: name the language by the font loaded; Arabic's own message
The picker writes Tiếng Việt only when the interface's loaded system font has its letters
(another interface loads the first installed candidate, which may lack them). The font choice is
one function with a unit test, gated off wasm with its callers. Choosing Arabic without an
Arabic font no longer gets the Chinese message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add CHANGE, changing objects by a change point or their properties as AutoCAD does
A change point moves each line's nearer end to it (level or plumb under ORTHOMODE), passes a
circle through it, and places a text, attribute definition or block reference by it when that is
the one object selected; otherwise "Change point ignored" and the object's questions follow, in
selection order. Properties is CHPROP's loop with Elev, applied as the command ends. Objects on
locked layers are left out, through selection helpers now shared with CHPROP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CHANGE: refuse a JSON call without a readable point or elevation
A `point` or `elevation` that can't be read is an error, and so is a call with neither, instead
of changing nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CHANGE: Elev on every kind AutoCAD sets it for; Aligned and Fit text; rotation default
Measured in AutoCAD 2024: Elev also sets the z of ellipses, multiline texts, solids and flat
3D polylines (a 3D polyline between heights keeps them), and of hatches, which stop being
associative ("Hatch boundary associativity removed."). Aligned text isn't asked its height or
rotation and Fit text its rotation, their two points setting them. The rotation default shows
as the drawing measures angles (ANGBASE 90 shows a text at 0 as 270.00). Shared z is compared
to rounding, and the radius and height prompts say what they need.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* -INSERT: ATTREQ, Preset and Verify attributes as AutoCAD asks them
ATTREQ 0 inserts blocks with their attributes' default values without asking (a session
setting, as AutoCAD keeps it in the registry). Preset attributes are never asked for, "Enter
attribute values" heads the questions, and Verify attributes are asked again after "Verify
attribute values", each with the value typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -INSERT: a click doesn't answer a Verify attribute
Only typed text or Enter answers "Verify attribute values"; anything else asks again (CodeRabbit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
RECTANG's polyline now starts at the first corner and runs along X to
the other corner, as AutoCAD 2024 draws it; each chamfer cuts the edge
into its corner by the first distance and the edge out by the second,
and fillet arcs start on the edge into their corner (clockwise outlines
get negative bulges). RECTANG keeps its chamfer, fillet, width,
elevation, thickness and rotation for the next rectangle, lists them as
"Current rectangle modes:" and offers the rotation as "<50.50>"; chamfer
and fillet clear each other. A circumscribed POLYGON starts at the end
of the picked edge counter-clockwise from its midpoint.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.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>
* Add -UNITS; show due surveyor bearings as one letter
-UNITS asks for the length format and precision, the angle format and
precision, the direction of angle 0 and whether angles turn clockwise,
with AutoCAD 2024's prompts, listings, defaults and messages. As in
AutoCAD, each answer is kept as it is given, and choosing another format
resets its precision to 4. The direction is typed in the chosen angle
format and turns as ANGDIR says.
Surveyor's units show a bearing that is due north, south, east or west as
that letter ("E"), except to whole degrees ("N 90d E"), as AutoCAD's
ANGTOS does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* units: read a due bearing typed as one letter (E, N, W, S) in surveyor's units
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>
* Refuse mirror lines whose points are closer than the geometry EPS
Found while testing crates/engine/src/cmd/modify/transform.rs:run_mirror (coincidence check `a.near(b, 1e-12)`), with crates/geom/src/mat.rs:43-47 (Mat3::mirror)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* mirror: the interactive prompt also refuses points closer than the geometry EPS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/engine/src/sysvars.rs:~404-425 (generic fallthrough in set: String branch, then the `Some(HVal::Real(_))` arm)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/utility/calc.rs:~355-360 (call, "min" | "max" branch using f64::min/f64::max)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
* wblock: write the source drawing's $INSUNITS and $MEASUREMENT
run_wblock built the written drawing from Drawing::new_imperial() and
never copied the source's units, so every WBLOCK file declared inches
($INSUNITS 1) and imperial measurement ($MEASUREMENT 0) whatever the
source: a block written from a metric drawing came back at 0.0254 of
its size in any program that honours $INSUNITS on insert. The written
drawing now takes the source's values for both variables, as AutoCAD's
-WBLOCK does. A unitless source ($INSUNITS 0, kept by the reader per
#1209) writes unitless.
Co-Authored-By: GLM-5.3 <noreply@z.ai>
* wblock: start from the source drawing's header and styles
Clone the source header (resetting $INSBASE as before) and copy the
multileader, table and multiline styles and the annotation scale list
along with the other tables, so a block written from a metric drawing
keeps its text size, current dimension style, limits and scales, not
the imperial template's (review on #2023).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wblock_units: each WBLOCK call writes into its own TempDir
The two tests shared one temp path and raced when cargo ran them in
parallel (review on #2023).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: GLM-5.3 <noreply@z.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): dash only the visible part of a highlight, so zooming far in doesn't freeze (#2299)
With the pointer on an object, its rollover highlight is dashed, and draw_highlight dashed the
object's whole outline in screen space, off-screen parts included: one shape per 10 points of
it. Zoomed far in, a short line is billions of points long, so the app froze and then ran out
of memory, which is the freeze and crash #2299 reports. Wheel-zooming a circle and two lines
with the pointer on them reached 7.9 million shapes in one frame at a view height of 0.001
before a 1 GB allocation failed.
The highlight now clips each outline to the view, plus a margin, in world coordinates first
(precise when zoomed far in), and dashes only what is left. Each visible piece starts its dash
pattern at its distance along the whole outline, so dashes don't crawl along a line that runs
off screen while the view pans. The two dashed rectangles drawn in screen space, a layout's
printable area and the crossing window, clip each edge to the painter's clip rect for the same
reason. The same zoom now stays under 1,300 shapes per frame down to the zoom limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): a clipped rectangle edge keeps its dashes where the whole edge puts them
dashed_rect clipped each edge to the screen and then started its dashes at the clip boundary,
so a printable area or crossing window running off screen had its dashes move with the view,
and its bottom and left edges were drawn in reverse. Each edge now keeps its direction and its
dashes start at their distance along the whole edge, as the highlight's outlines do. The
zoomed-far-in app test also checks that the line really is hovered, so it can't pass with no
highlight drawn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: austin62jake <340374338+austin62jake@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
rstar 0.13 can panic when inserting after removals in a bulk-loaded tree, and picking runs outside the command guard. The patch now runs on a copy under catch_unwind and falls back to a bulk load. The web build can't catch a panic, so it always bulk-loads. Diagnosis, reproduction and fix from #2468.
Fixes#2468
Co-authored-by: octopoulpos <32617732+octopoulpos@users.noreply.github.com>
plan.dwg and plan.dxf in one folder shared plan.bak. A DWG keeps <name>.bak, as AutoCAD names it, and any other drawing appends .bak. The naming comes from #1006.
Co-authored-by: Filippo Reimer <49290121+filforce95@users.noreply.github.com>
* Validate new linetype names in the rename command
Found while testing crates/engine/src/cmd/props.rs:1154-1200 (run_rename, "linetype" branch)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* rename: a linetype's new name is stored trimmed, as -RENAME types it (CodeRabbit)
---------
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Since #2518 the write syncs the folder after the rename, so the autosave
file exists while the write is still running. Until it finishes the drawing
is busy and the next change isn't planned, so the test's tick() returned
None. Autosaver::writing() says whether a write is still running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(dxf): add deterministic codec and drawing round-trip matrices
Exercise typed tags and model-space LINE/CIRCLE/TEXT through in-memory ASCII and binary DXF at every save version. Compare both reads with the original model and check independent handwritten byte/entity fixtures. Keep desired-behaviour tests for extrusion and ByBlock plot-style loss explicitly ignored until fixed. No production or docs changes.
* test(dxf): independently verify common entity properties
* test(io): add a broad DWG preservation matrix with focused pending regressions
Share deterministic Drawing cases and exact assertions between DXF and DWG without reducing DXF coverage. Run representable DWG cases at all six save versions, twice in RAM. Test R2000 color/transparency and the single TEXT elevation as explicit format limits. Keep six independently reproduced loss regressions ignored without weakening comparisons. No production or docs changes.
* test(io): close binary-width and referenced-linetype coverage gaps
Check every integer group range against independently constructed binary payloads, catching shared reader/writer width mistakes. Resolve linetypes with Drawing lookup and compare layer linetypes too. Add missing/changed-reference self-tests and inherited-pattern DXF/DWG round trips. Reproduced the review counterexamples before and after; full CI passes with two nextest threads. Test-only changes.
* test(io): expand DXF and DWG matrices across entities and drawing collections
Add basic coverage for all 29 modeled entity kinds, block graphs and attributes, all ACI colors, richer properties/XDATA, sparse handles, headers, symbol tables, views, groups, layouts and foreign payloads. Preserve typed fields and authored references through two RAM saves. Add independent DXF groups and native DWG bridge checks, plus explicit refusal-or-preservation tests for unknown entities. Keep confirmed discrepancies and unresolved preservation contracts as focused ignored tests; native linetype angles and silently dropped unknown DWG entities are independently reproduced. Version limits and foreign object handle remapping are explicit contracts, not blanket normalization. Tests only; no production or docs changes. Full CI passes with two nextest threads: 2777 passed, 27 skipped.
* test(io): cover bounded property domains and targeted field interactions
Independently check all ACI values and transparency percentages, native
layer/style flag combinations, and literal large handles. Add shared DXF/DWG
cases for alignment with mirrored styles, inherited versus explicit zero
properties, repeated nested attributes, and integer-boundary handles.
Keep exhausted handle allocation separate: DWG refuses the save; a focused
ignored DXF regression reproduces silent loss of all nine model entities.
No production or documentation changes.
Validation: full cargo xtask ci with NEXTEST_TEST_THREADS=2 passes all seven
gates (2792 passed, 28 skipped). The pending DXF regression fails explicitly
when run with --ignored.
* test(dxf): separate chunk conformance from binary reader tolerance
Address PR #2237 review: shared exact-tag round trips only use hex chunks
within the documented 127-byte group limit. Independently inspect ASCII
and binary writer bytes, reassemble payloads, and verify following-record
framing. Keep full 0..255 length-byte read tolerance in handwritten fixtures,
not as a claim of conforming writer output.
Add two focused ignored regressions for oversized ASCII/binary output,
reproduced with 128-byte payloads. They extend existing issue #1025.
No production changes.
Validation: cargo xtask ci passes all seven gates with nextest 0.9.150
and two test threads (2782 passed, 30 skipped). Both new pending checks
fail explicitly when run with --ignored.
* test(io): compare DWG dimension blocks through their references
Anonymous dimension-block suffixes may change between drawing sessions (Autodesk documentation; ODA DWG section 20.4.52.1). Resolve their names through preserved dimension identities and keep strict checks for cache content, entity handles, block properties and reference integrity.
Enable the pending dimensions test across all six DWG versions and two saves. Add a paired-cache fixture and comparator controls rejecting swapped/merged/dangling references, geometry and property changes, stale caches, and named-block renaming. Leave the shared comparator and DXF contract unchanged.
Validation: cargo xtask ci (all seven stages; 2786 passed, 29 skipped); roundtrip matrix (211 passed, 26 ignored).
Resolves the preservation-contract decision in #2257 (tracker #2238, B4); no production codec fix.
* test(io): specify WIPEOUT explicit closure on roundtrip
An implicit WIPEOUT boundary gains one explicit closing marker in DXF/DWG. Compare against independently authored explicit boundaries in both winding directions across all six versions and two saves; retain strict field and resave comparisons. Enable the two pending closure checks without changing the shared comparator.
Distinguish this valid file representation from the grip-edit defect: the latter is fixed separately in PR #2284, with engine, outline and rendering regressions. No production change in this test-only branch.
Validation: cargo xtask ci, all seven stages passed (2788 passed, 27 skipped); active roundtrip matrix 213 passed; remaining ignored checks reproduce 24 drawing failures and two codec failures. One unrelated HTTP control-server test failed transiently, then passed in isolation and in the full rerun.
Related #2256 and tracker #2238 B3.
* test(io): expose multiline ATTRIB alignment corruption
Keep the original origin witness and add distinct nonzero TEXT/MTEXT anchors. An active R2018 ASCII/binary two-save check verifies world-space multiline geometry and value edits; this does not excuse corrupted fallback TEXT fields.
Retain the strict pending #2255 check and add comparison of independently downgraded R2000/R2004 geometry. The observed Left -> Center change is a real reader error, not permitted alignment-point canonicalization. The reader hunk from existing PR #1942 makes the extended strict check pass in an isolated experiment; no competing production fix is included here.
Validation of this test-only tree: all seven cargo xtask ci stages pass (2789 passed, 27 skipped); active matrix 214 passed; remaining 24 drawing and two codec diagnostics still fail. An unrelated HTTP control-server test failed transiently, then passed alone and in the complete rerun.
Related #2255, #1942 and tracker #2238 B2.
* test(io): separate ATTRIB value and legacy-placement guarantees
Assert exact saved ATTRIB values, read_as and authored embedded MTEXT contents before and after value edits. A mutation test rejects corrupt fields, including a value/read_as pair whose rendered geometry remains unchanged.
Make legacy placement an independently runnable pending regression instead of a callback behind the failing field comparator. Compare direct R2000/R2004 exports with exports after one/two R2018 roundtrips, both encodings in each stage, and verify that the primary single-line fallback actually survives. Keep all three contracts local and distinct; remove the old cross-module callback.
The new legacy test fails directly on world-space geometry with the unchanged reader. Both pending ATTRIB tests pass with the reader hunk from existing PR #1942 in an isolated experiment (548 IO tests also pass). No production fix is included.
Validation: all seven cargo xtask ci stages pass with NEXTEST_TEST_THREADS=1 (2790 passed, 28 skipped). Earlier parallel runs and one isolated retry hit pre-existing HTTP/busy control-server failures; no exclusions or test changes. Active matrix 215 passed; all 25 drawing and two codec pending diagnostics still fail.
Addresses review of #2237; related #2255, #1942 and tracker #2238 B2.
---------
Co-authored-by: cloudj <jonbahnes@gmail.com>
* test(io): a centred ATTDEF keeps its insertion point in a DWG (#2464)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): an ATTDEF with no default value is placed by its tag's width in DWG (#2464)
The writer keeps a justified text's stored insertion point only when it is near where the
baseline starts for the text's width; an ATTDEF with an empty default value has width 0, so every
AutoCAD insertion point further than half a height from the alignment point was replaced by the
alignment point. The tag is what is drawn: judge against its width.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): keep a stored ATTDEF point that is close to the empty text's start
The tag-width baseline check replaced a point read from a drawing whenever the
font's width of the tag put the computed start further than the allowance away,
e.g. a centred LONGTAG with group 10 at x=99.5 and group 11 at x=100 moved to
about 92.17 in DXF and DWG. A stored point close to the empty text's own start
is now kept as before; only otherwise is it judged against the tag.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(io): store an ATTDEF point that differs from the computed baseline
`assert_kept(start)` stored exactly the baseline the tag-width fallback computes, so a write that replaced group 10 with that baseline passed too. The point is now 1 off the start, within the tolerance. Review of #2466.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(io): an empty-value ATTDEF keeps its point with an empty or absent prompt too
The tag-width check applies to every ATTDEF with no default value, since the
CAD displays the tag whatever the prompt: the guard now says `attdef` instead
of reading as a prompt test, and the test covers a prompt, an empty one and none.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(io): a LINE keeps its extrusion through DXF and DWG (#2241)
`line_extrusion_roundtrips_with_world_endpoints` (red: group 230 is not written for a LINE with
normal (0, 0, -1), so it comes back with (0, 0, 1)).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): write a LINE's extrusion (210/220/230) (#2241)
A LINE's points are world coordinates and the reader already keeps its extrusion (the direction
of its thickness), but the writer left the groups out, so a normal (0, 0, -1) came back as
(0, 0, 1) from DXF and from DWG, which is written through DXF.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(io): a LINE's extrusion is written as a unit vector or not at all (#2241)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): write a LINE's extrusion normalized, nothing for a zero or NaN normal (#2241)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): write a tiny extrusion's direction, drop a non-finite one
Vec3::normalized returns zero at or below the drawing tolerance, so a LINE with
extrusion (0, 0, -1e-10) saved with no 210/220/230 and reopened as +Z, flipping a
thickness; an infinite component normalized to NaN and was written as a zero
vector. The writer now normalizes by the vector's own length and writes nothing
only for a zero or non-finite vector.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): keep the direction of a huge finite extrusion
The length of (0, -1.5e308, 1.5e308) overflows to infinity, so the normal came out as (0, 0, 0) and was written as such. The vector is scaled by its largest component before it is normalized; the case is in `line_extrusion_is_written_as_a_unit_vector_or_not_at_all`. Review of #2417.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(io): the Constant mode of an attached ATTRIB survives DXF and DWG (#2246)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): an attached ATTRIB keeps its Constant mode through DXF and DWG (#2246)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(io): attached ATTRIB Constant mode over every save version and encoding
The regression covered the default DXF and DWG saves only. It now also saves
with each Version::ALL entry as ASCII DXF, binary DXF and DWG.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(io): MTEXT legacy column groups stay in the AcDbMText subclass (#2247)
`legacy_column_groups_stay_in_the_mtext_subclass` (red: the column width 48 is read as the
entity's ltscale, 12.5; the height count 50 as a column height).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): MTEXT legacy column groups no longer leak into the entity (#2247)
`common()` read the linetype scale (48) and the other AcDbEntity groups from the whole record,
so the column width 48 of an MTEXT's legacy column groups became its ltscale. They are now read
from the AcDbEntity subclass only. `read_columns` took the height count (the first 50) for a
column height and `column_tags` did not write it; both follow the DXF layout now.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(engine): the MTEXT entmake column heights carry their count (#2247)
Group 50 after the column type is the height count, then the heights. Thanks to austin62jake for the report and the fix.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(io): a marker-less MTEXT with an embedded object keeps its own ltscale (#2247)
Case from #2276 (Zaxaerith): without subclass markers, the 48 of an embedded object (101) was read as Common.ltscale.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): stop the entity's own groups at an embedded object (#2247)
Without subclass markers, an embedded object's 48 was read as Common.ltscale. Suggested by austin62jake; case and test from #2276 (Zaxaerith).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* style(io): rustfmt the new MTEXT test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): keep layer and colour that follow a subclass marker in a list with no AcDbEntity marker
entity_groups ended the common region at the first subclass marker other than
AcDbEntity, so an entmake list such as (0 . "LINE") (100 . "AcDbLine") (8 . "Walls")
was read on layer 0. The region now ends at a marker only when the record has an
AcDbEntity marker (files from R13 on always do); other records are read as a
whole up to an embedded object (101), as before the column change.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(io): markers inside an application-defined group do not end the entity's groups
A `102 {...102 }` group before AcDbEntity may hold any code, 100 and 101 included: `entity_groups` stopped there and the layer and colour that followed fell back to defaults. The scan now skips the contents of such groups when looking for the AcDbEntity marker and for the end of its groups. Review of #2416.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(io): keep every column height of files written before the height count
CADCraft up to f536bf4 wrote group 76 = n and n group-50 heights, no count.
Reading those with the counted format dropped the first height and rewrote
76 as n - 1. The first 50 is a count only when there are 76 + 1 of them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(io): MTEXT column heights skip application-defined groups; no count for an empty list
A `102 {...}` group inside the MTEXT record may hold a 50 of its own: it
counted as a height and made a legacy record (76 = n, n heights, no count)
look counted, dropping the first height. `read_columns` now filters the
record with `outside_app` (shared with `entity_groups`).
`column_tags` wrote a lone `50 = 0` for a dynamic manual column with no
heights, which read back as one height of 0; the count is omitted for an
empty list, so `heights: []` round trips. ZWCAD 2025 also read that lone
50 as the rotation and zeroed it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>