Repository navigation
Carry what the files state for OCS, polylines, text, viewports and layouts (0.3 port, 1/3) - #1
Closed
iujunhyung wants to merge 25 commits into
Closed
iujunhyung wants to merge 25 commits into
iujunhyung wants to merge 25 commits into
Conversation
tests/data/model-0.1.0.json is what the published 0.1.0 serializes for a drawing with one entity of every kind it knew, and the tables. A field added since is absent from it, and each such field documents what such a document reads as; the test holds them to it, so a consumer with an archive of 0.1.0 output keeps working as the model grows. SPLINE and LEADER are left out of the document: their shape changed after 0.1.0 (degree and knots; the annotation as a three-state reference), and a 0.1.0 document carrying either no longer loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A polygon mesh is an M by N grid of vertices. It used to fall through to Entity::Unknown, and a polygon-mesh DXF did not read at all -- REVSURF, RULESURF, EDGESURF and most terrain tools write exactly this entity. The new variant carries it the way POLYLINE_PFACE already is: a Solid3DEntity whose wireframe is the grid lines. The edge order is stated on the variant, so two readers of one mesh agree edge for edge. A new Entity variant is a breaking change by design (the enum is exhaustive so that consumers meet it as a compile error). Ported-from: efd43cc Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CIRCLE, ARC, LWPOLYLINE and POLYLINE_2D, TEXT, ATTRIB, ATTDEF, INSERT, SOLID and TRACE state their coordinates in an object coordinate system (OCS) whose normal is DXF 210. Without it the coordinates do not say where the entity is: a mirrored entity (normal (0, 0, -1)) stated at x = 10 lies at x = -10, and a reader that drops the normal puts every mirrored circle, polyline and block reference on the wrong side of the y axis. Each of them now carries `extrusion`, following the ELLIPSE precedent: the coordinates stay as the file states them, and taking them to the world is the consumer's step. Where the model's point type is 2D -- the polyline vertices, the text anchor, the solid's corners -- the OCS z the file states is carried as `elevation` (DXF 38 on an LWPOLYLINE, the z of group 10 otherwise), so a consumer has all it needs for a tilted normal too. Both default for a document written before them: (0, 0, 1) and 0, the reference's own defaults. The module doc now says which coordinates are OCS ones; the fields that were already OCS without saying so (centres, anchors, corners, vertices) say so too. Their meaning does not change. Ported-from: fae3b14, 89d77c5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Affine2::from_insert ignored the INSERT's OCS: a block mirrored with its reference (normal (0, 0, -1)) came out unmirrored and on the wrong side of the y axis. On AutoCADSamples7.dwg that is 132 block references whose contents land in the wrong place. The placement now takes the INSERT's OCS to the world's (x, y) by the DXF reference's arbitrary axis algorithm, which is where the normal and the insertion point's OCS z come in. For the usual normal (0, 0, 1) the result is bit for bit what it was. Proposal for upstream (the change of an existing method's result for data it could not see before): the model already owns this one piece of arithmetic so that every consumer of a block reference agrees where its contents are drawn. With `InsertEntity::extrusion` in the model, leaving the normal out of `from_insert` would make each consumer apply it on its own -- the disagreement Affine2 exists to prevent. Nothing else in the model takes OCS coordinates to the world; for CIRCLE, ARC, polylines, text and solids that stays the consumer's step. If upstream prefers to expose the OCS map itself (so consumers stop re-deriving the arbitrary axis algorithm for those), `ocs_to_world` here is the candidate. Ported-from: fae3b14 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A polyline's segment is an arc when its vertex states a bulge (DXF 42), and it has a width when the vertex states one (40/41) or the polyline does (43). Without them a fillet, a slot, a rounded corner or a revision cloud scallop is its chord, a DONUT (two vertices, bulges of 1) is a line back and forth, and a wide polyline is a hairline. `bulges` and `widths` are one entry per vertex, or empty when they would say nothing -- every segment straight, every segment as wide as `const_width` -- so that two spellings of one polyline (all zeros stated, nothing stated) compare equal. The bulge is carried as the file states it, in the OCS like the vertices. POLYLINE_2D shares the struct, takes its bulges and widths from its VERTEX records and has no constant width of its own. Length and area, which follow from them, are a consumer's to compute. This replaces the sentence 531c5e7 added to `vertices` ("a vertex's bulge is not carried"), which is no longer true. Ported-from: fae3b14 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… styled A justified text is placed by its alignment point (DXF 11), not by its start point (10): the same 10 is the left end of a left-justified text and a point the application computed from the font for a centred one. On one sample drawing 541 of 861 texts were centre or middle justified and landed in the wrong place without it. TEXT, ATTRIB and ATTDEF gain `horizontal_justification` (72) and `vertical_justification` (73, and 74 on the attribute types), both enums with the format's own defaults; `alignment_point` (11, None for left/baseline text, which states no such point); `width_factor` (41, default 1) and `oblique_angle` (51, radians, default 0); and `style_name` (7), a three-state reference like the others. The model has no STYLE table; the reference resolves by name the way every other reference does, and an absent group means the style STANDARD, which a reader applies when the drawing has one. Ported-from: 96abb6d Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DXF 70 on ATTRIB and ATTDEF is a set of four flags. Bit 1 hides the attribute's value while the drawing keeps it -- a title block's invisible attributes are data nobody sees on the sheet -- and it is a different flag from the DXF 60 every entity can carry. Bit 2 makes an ATTDEF constant: its value is the one every reference shows, and no ATTRIB carries it. `flags: AttributeFlags` carries all four bits by the names the reference gives them; a document written before it reads as no flag set. Ported-from: 96abb6d Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model had MTEXT's insertion point, attachment and rotation but not how wide its block is. DXF 41 is the width the text wraps at (0: not wrapped), and 42/43 the block's size as the writing application laid it out -- the one statement of how far the text reaches that does not need the font, which is what bounds, crops and hit tests of a drawing's text need. `extents_width`/`extents_height` are None when the file does not state them. `style_name` (DXF 7) is the text style, as on TEXT. The baseline direction (DXF 11) stays folded into `rotation`, as upstream already carries it. Ported-from: 96abb6d Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A paper-space VIEWPORT was its frame alone, so nothing said what part of the model a sheet shows or at what scale. It now carries the view the format states for it (`view: Option<ViewportView>`: centre in display coordinates, 12; the model height shown, 45; target, 17; direction, 16; twist, 51, radians; lens length, 42), `None` when the record does not carry one (files older than R2000 keep it in extended data); whether it is on (68, or the off bit of 90); its number in the layout (69, 1 being the sheet's own overall viewport, `None` from the binary format, which stores none); and the layers frozen in it alone (341/331), each a three-state reference. Scale, the model window and the model-to-paper mapping follow from these and stay with consumers. MULTILEADER's doc no longer names VIEWPORT as another frame-only entity. Ported-from: 1f08658 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whether a layer's entities are shown is layer state the model did not carry, except as the sign of a DXF layer's colour -- and the binary format keeps an off layer's colour positive and says "off" with a flag of its own, so every off layer of a DWG read as on. LayerRecord now says it for every file (`off`), with the other two flags that decide what is shown or touched (`frozen`, `locked`, DXF 70 bits 1 and 4), the plot flag (290, None when the file does not state it or the reader cannot tell a stated 0 from an absent group), the lineweight (370, hundredths of a millimetre or -3 for the default; None likewise) and the linetype (6, a three-state reference). Proposal for upstream: CadDatabase's doc listed linetypes, lineweights and styles among what the model deliberately leaves out. A layer now carries its lineweight and names its linetype, and texts name their style (the previous commits), so the sentence now says what is still out: the linetype and text style definitions, dictionaries and header variables. This revises a documented scope statement, which the principles ask to propose first; the fields themselves are additions. Which entity is hidden, and why, stays a consumer's rule. Ported-from: c045d6a Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A drawing's paper space is a set of sheets, each a LAYOUT object: a tab with a name and a place in the tab order, the block it shows, its limits, and the plot settings that say what paper it is set up for. Without them a reader of the model sees `*Paper_Space` blocks but not which tab each is, how big the sheet is or which way up. Tables gains `layouts` (name -> LayoutRecord), empty for a file that has none. LayoutRecord carries the name (DXF 1), the tab order (71), the shown block as a three-state reference (330), the limits (10/11) and PlotSettings: the paper's name and size (4, 44/45), its margins (40-43), the plot origin (46/47), the paper units (72) and rotation (73) as enums, and the custom scale's two sides (142/143) as stated. Where a sheet lies in paper space, and the viewports' model-to-paper mapping, follow from these and stay with consumers. The module and CadDatabase docs now list DIMSTYLE among the tables, which they had not since it was added. Ported-from: 1f08658, 7ff5c70 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An ordinate dimension measures one coordinate of its feature from a datum, and bit 64 of DXF 70 says which: set for X, clear for Y. Without it the model has the feature (13), the datum (10) and a measurement, and no way to say which distance between them the measurement is. `ordinate_axis: Option<OrdinateAxis>` is that bit on an ordinate dimension and None on every other kind. The doc of `definition_point` said group 10 is an ordinate's feature. It is the datum the ordinate measures from; the feature is group 13, which `points.extension1` carries. A reader filling the field from group 10 already puts the datum there -- only the sentence was wrong. Ported-from: 74bb835, a7f9ea5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DimStyleRecord carried ten of the variables that turn a measurement into the text a dimension shows. The text also depends on how the style writes a length (DIMLUNIT: decimal, architectural, fractional, ...), which zeros it leaves out (DIMZIN), what it rounds to (DIMRND), how it writes an angle and with how many places (DIMAUNIT, DIMADEC) and how it stacks a fraction (DIMFRAC). Without them "1'-3 1/2\"" and "15.50" are the same style. Each is added the way the others are: Option, None when the style does not state it; the three formats as enums, None too for a value outside the format's own. The arrow size (DIMASZ) joins the text height as the second size the style is drawn at; together they tell a style that states its sizes from one a file names but leaves empty. The struct's doc now says so. Formatting the text, and which value stands in for one the style does not state, stay the consumer's. Ported-from: 74bb835 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An MLINE's style gives its lines' offsets in the style's own units, and the MLINE's DXF 40 is the factor they are drawn at: a 200-unit wall in the STANDARD style (offsets 0.5 and -0.5) states 200. Without it every multi-line is drawn one unit wide, whatever the drawing says. `scale` is Option: the format requires the group, so a reader that has it always gives it, and `None` is a model that was not given one -- a document written before this field existed -- rather than a guessed 1. Ported-from: 00414f4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec, the writer and the oracle learn the shapes the model gained, so cases can pin them: - a spec can mirror an OCS entity (`EntitySpec::Mirrored`: normal (0, 0, -1), at an elevation), give an LWPOLYLINE bulges, per-vertex widths and a constant width, justify a TEXT (72/73/11 with width factor, obliquing and a style), and write SOLIDs, polygon meshes, ordinate dimensions and paper-space VIEWPORTs; - it declares text styles (a STYLE table), layers in any state (off as a negative colour, frozen/locked bits, 290, 370), dimension styles with every variable the model carries, invisible attributes, entities of paper space (DXF 67), and LAYOUT objects with their plot settings (an OBJECTS section, written only when there are layouts); - the oracle states what a reader owes back: coordinates as the file states them with their normal, polyline bulges and widths folded to the empty list where they say nothing, the text style STANDARD for a text that names none, a mesh's grid lines in the model's order, paper space after the model and in its own block. Every existing case writes the same bytes as before and expects the same model; the property tests now generate the new shapes too. The named cases that use them follow, one per commit. Ported-from: fae3b14, 96abb6d, c045d6a, 1f08658, 7ff5c70, efd43cc, 74bb835, a7f9ea5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A circle, an arc, a bulged polyline at an elevation, a text, a solid and a block reference, each written in the OCS whose normal is (0, 0, -1) -- what AutoCAD's MIRROR leaves behind -- beside one circle in the world's own axes. The oracle keeps every coordinate and bulge as the file states it, with the normal; the test checks that the block reference's placement takes it across the y axis, the one OCS step the model owns. The feature branch's mirrored_ocs_r2000.dxf and mirrored_bulge_r2000.dxf fixtures pinned the same drawing in world coordinates, which is what the model no longer does. Ported-from: fae3b14, 89d77c5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slot whose ends are half circles, a tapered arrow drawn with per-vertex widths, a constant-width polyline with a quarter-circle corner, a DONUT (two vertices, both bulges 1), and a polyline whose file states zero bulges and widths equal to its constant width. The last one pins the model's rule that such a spelling reads as the empty lists, the same model as a polyline that states nothing. Ported-from: fae3b14 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Texts in each justification the reference defines, placed by their alignment point (11), with a width factor, an oblique angle and a named style; a text naming no style in a file that declares STANDARD, which is what the reference reads the absence as; a text naming a style the file never declares, which must come back unresolved and keeping that name; and a title block with one visible attribute and one invisible one (DXF 70, bit 1). A reader going through LibreDWG's DXF importer will see STANDARD where the file names the undeclared style: the importer drops the name and applies its own default, the same loss G5 and G10 already pin for an undeclared dimension style and an undefined block. Ported-from: 96abb6d Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven layers, one per state the model carries (off, frozen, locked, not plotted, plotted with a lineweight, the default lineweight), each with a line on it; a paper space with a border, a title, the sheet's own overall viewport, a detail viewport at half scale turned 30 degrees with one layer frozen in it alone, and a viewport that is off; and three layouts: the model tab, an A3 sheet stated portrait and turned a quarter, and an empty letter sheet in inches on a second paper space. The plot flag is stated as 0 on one layer. A reader that cannot tell a stated 0 from an absent group reports None there, which the model's field allows and a parser's golden test pins as a known deviation. Ported-from: c045d6a, 1f08658, 7ff5c70 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A three-by-four polygon mesh, closed in N and open in M, with varying heights. The oracle is its grid lines in the order the model states for POLYLINE_MESH, 8 between the rows and 12 along them. Its vertices carry the subclass marker AcDbPolygonMeshVertex, which an unpatched LibreDWG DXF importer does not know and rejects the whole file over (a critical error); the vendored copy with the feature branch's in_dxf.c/dynapi.c patch reads it, and its grid matches this oracle edge for edge. Ported-from: efd43cc, 598fc09 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plate dimensioned from its lower-left corner by three X-type and two Y-type ordinate dimensions (DXF 70 bit 64 set and clear), all measuring from the datum at group 10, their features at 13 and their leaders ending at 14; one states its measurement, one a literal text. The ORD style states every variable the model carries, the ARCH style only its two unit formats, so the oracle pins both the stated values and the unstated ones as None. Ported-from: 74bb835, a7f9ea5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The JSON module named four kinds of reference field; the model now has more (text, tolerance and leader style names, a layer's linetype, a viewport's frozen layers, a layout's block, a leader's annotation), all serialized the same three-state way. The list now names them all. Ref::Unresolved linked to EntityCommon::handle, which became source_handle when entities gained reference IDs; the link is repaired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tables::layouts said a file older than R2000 has none. Four R13 and R14 DWGs of the LibreDWG corpus that a later AutoCAD saved in those formats (example_r13.dwg, example_r14.dwg, r14/Constraints.dwg, r14/Leader.dwg) keep their LAYOUT objects, and a reader reads them like any other: three tabs each (their DXF twins carry none). R13 and R14 have no layouts of their own; the sentence now says that, and that such a save keeps them. Found while porting the layout reader onto uncad; the field, its shape and the rest of its documentation are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…E edges Upstream (bcd39d6) added several things this branch had added too. Where both describe the same thing, upstream's shape (names, types, JSON keys, serde defaults, docs) is kept and ours is dropped; what upstream does not carry is kept on top of it, in upstream's conventions. Overlaps and how each was resolved: - Polyline bulge -- adopted upstream. A vertex is upstream's `PolylineVertex { point, bulge }` on LWPOLYLINE, POLYLINE_2D and HATCH polyline paths; our `LwPolylineEntity::bulges: Vec<f64>` (empty when all zero) is removed. - Polyline widths -- combined. Upstream carries none; ours were a parallel `widths: Vec<SegmentWidth>` on the entity. Since the bulge now sits on the vertex, the widths do too: `PolylineVertex::start_width` / `end_width` (DXF 40/41, serde default 0; 0 on a HATCH boundary). `SegmentWidth` is removed. `const_width` (DXF 43) stays on the entity and keeps its rule: a polyline that states it again on every vertex is the same model as one that states it once (vertices carry 0). - Polyline elevation and extrusion -- adopted upstream. Same names, types and defaults as ours; upstream's docs replace ours. - CIRCLE/ARC extrusion -- adopted upstream (same field; upstream's docs). - SOLID/TRACE elevation and extrusion -- adopted upstream (same fields; upstream's docs, and the corners' doc back to upstream's wording). - 3DFACE invisible edges -- adopted upstream (ours had none). - HATCH polyline path as vertices -- adopted upstream. - Principles: strings carry the text's own codes -- adopted upstream. - TEXT/ATTRIB/ATTDEF/INSERT extrusion and elevation -- kept ours (upstream has none); already upstream's type (`Point3D`, serde default (0, 0, 1); `f64`, default 0), docs reworded in upstream's terms. `Affine2::from_insert` reads that same `Point3D` and is unchanged. - Golden spec vertex -- combined. Upstream's `spec::Vertex { at, bulge }` is kept and gains `start_width` / `end_width` (`Vertex::wide`); our spec `LwPolyline { bulges, widths }` lists are removed, `const_width` stays and `elevation` (DXF 38) is added. - Golden mirroring -- adopted upstream, extended. Upstream's `mirrored: bool` on Circle, Arc and LwPolyline replaces our `EntitySpec::Mirrored { elevation, entity }` wrapper; Text, Solid and Insert gain the same field for G11. Only the polyline keeps an elevation, the one G11 states. - Golden writer -- adopted upstream's `extrusion(mirrored)` and its rule of writing 42 only for an arc segment, in place of our writer state and our all-or-nothing 42; 40/41 are written on every vertex of a polyline that has widths, 43 and 38 when not 0. - Golden expected model -- adopted upstream's `extrusion(mirrored)` in place of our `Ocs` threading; polylines map vertices with their widths. - Golden cases -- upstream's G7 additions kept as written; our G11 and G12 re-expressed in the merged shapes (G12's last polyline no longer states zero bulges: an absent 42 is the same straight segment). - Golden properties -- upstream's mirrored circle and arc strategies kept; our polyline strategy (bulges, widths, constant width) kept on upstream's vertices; our mirrored-wrapper strategy replaced by a solid with its own flag and a mirrored polyline at an elevation. - JSON tests -- upstream's values kept, widths moved onto the vertices. - JSON compatibility -- a 0.1.0 polyline (bare points) no longer loads under upstream's vertex shape, as a SPLINE and a LEADER already did not: the 0.1.0 document drops its LWPOLYLINE, its POLYLINE_2D and its HATCH's polyline path (re-serialized by uncad-model 0.1.0 itself, so it is still that version's output), and a test pins that such a polyline does not load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Merged into main as 218f969 (absorbed with follow-up adjustments). Closing this PR; the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge order: 1. #1 2. iyulab/iron-render-cad#1 3. iyulab/uncad#1
What this adds. Fields the parser now reads and the renderer and the LLM/VLM
export consume. Every addition follows the model's principles:
Optionfor what afile may leave unstated,
Reffor every reference by name or handle, a doc sentenceper field, and
serde(default)so a 0.1.0 document still loads, except a 0.1.0polyline. That last case is pinned by a tripwire test.
added for CIRCLE, ARC, polylines, SOLID and TRACE.
Affine2::from_insertappliesan INSERT's extrusion.
start_widthandend_widthon upstream'sPolylineVertex, andconst_widthon the entity.width factor, oblique angle and the style name. Attribute flags.
rect_width, the extents and the style name.on,viewport_idand the frozen layers.Entity::PolylineMesh.Option.Tables.layouts: a LayoutRecord with its plot settings.Needs your decision. These are "propose first" under the principles.
lineweights and styles. Layers now carry state, lineweight and a linetype name,
and texts name their style. The linetype and style definitions, dictionaries and
header variables are still left out.
PolylineVertexchange combined with ours. This is a breaking 0.2.0.
Tests. 59 pass. The fields were checked against a DXF twin of every drawing
where one exists.
🤖 Generated with Claude Code