diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e6761755..03d2a113 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5193,6 +5193,43 @@ BL version (2), B enabled, TV image (an HDR path), BD rotation (0.0), B display_ python3 -I tools/corpus/fetch.py --dest ../dwg-corpus cargo run --release --example probe_render_environment -- ../dwg-corpus ``` + +## 7bt. SHAPE (2026-10-26, #140) + +The dispatcher's note on SHAPE said the one record in the corpus "has a +160-bit budget and nothing to spend it on". §20.4.37 spends it: `3BD` +insertion point, five `BD` (scale, rotation, width factor, oblique angle, +thickness), `BS` shape number and `3BD` extrusion, with no release-dependent +branch, then the SHAPEFILE handle in the handle stream. Read as printed the +list closes on all 7 SHAPE records of the corpus, one per ACadSharp +`sample_AC10xx` save, R14 to R2018, on each release's own data-stream +boundary. + +Against AutoCAD's DXF export (R2000 to R2018, matched by the handle in group +5) the insertion point and the scale equal groups 10, 20, 30 and 40 on all 6 +records, 24 values, none differing. AutoCAD omits a group at its default, so the +rotation, width factor, oblique angle, thickness and extrusion are absent +from all 6 and count as omitted, not as compared: 42 values that no twin +confirms, held to the boundary alone. The shape number has no DXF group; +the DXF writes the shape's name, which would need the shape file of the +text style (§20.4.56) to resolve. R14 has no twin. + +```sh +cargo run --release --example probe_shape -- ../dwg-corpus +``` + +| Release | Records | Close | Values compared | Omitted by the DXF | Differ | +|---|---|---|---|---|---| +| R14 | 1 | 1 | no twin | | | +| R2000 | 1 | 1 | 4 | 7 | 0 | +| R2004 | 1 | 1 | 4 | 7 | 0 | +| R2007 | 1 | 1 | 4 | 7 | 0 | +| R2010 | 1 | 1 | 4 | 7 | 0 | +| R2013 | 1 | 1 | 4 | 7 | 0 | +| R2018 | 1 | 1 | 4 | 7 | 0 | + +The spec's own example (§20.4.37.1) is an R14 record; it now runs through +the decoder, and `spec_examples` records its outcome. ## 8. Write pipeline (current scope) The inverse pipeline is partially shipped. Stage-1 (per-section diff --git a/README.md b/README.md index 35a705b7..fe5e9d95 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This is **0.1.0-alpha.1**. Do not use it in production. Do not benchmark it against the ODA SDK. Do not tell your CAD team dwg-rs solves their interop problem today. Entity and object decode coverage, measured in CI by the `Corpus (public DWG)` -job on `main` ([run 40716086829](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40716086829) on 2026-10-28) with +job on `main` ([run 40736084515](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40736084515) on 2026-10-28) with [`examples/coverage_report.rs`](./examples/coverage_report.rs), on the public drawings fetched by pinned sha256 ([`tools/corpus/pins.tsv`](./tools/corpus/pins.tsv); none is committed): @@ -25,14 +25,14 @@ drawings fetched by pinned sha256 | Version | Files tested | Decoded | Skipped | Errored | Success rate | Median drawing | |---------|--------------|---------|---------|---------|--------------|----------------| -| R14 (AC1014) | 12 | 4733 | 23 | 0 | **99.5 %** | 100.0 % | -| R2000 (AC1015) | 9 | 2781 | 4 | 0 | **99.9 %** | 100.0 % | -| R2004 (AC1018) | 12 | 106993 | 1 | 0 | **> 99.9 %** | 100.0 % | -| R2007 (AC1021) | 8 | 2236 | 1 | 0 | **> 99.9 %** | 100.0 % | -| R2010 (AC1024) | 8 | 2187 | 1 | 0 | **> 99.9 %** | 100.0 % | -| R2013 (AC1027) | 8 | 1786 | 1 | 0 | **99.9 %** | 100.0 % | -| R2018 (AC1032) | 4 | 1614 | 1 | 0 | **99.9 %** | 100.0 % | -| **Aggregate** | **61** | **122330** | **32** | **0** | **> 99.9 %** | **100.0 %** | +| R14 (AC1014) | 12 | 4734 | 22 | 0 | **99.5 %** | 100.0 % | +| R2000 (AC1015) | 9 | 2782 | 3 | 0 | **99.9 %** | 100.0 % | +| R2004 (AC1018) | 12 | 106994 | 0 | 0 | **100.0 %** | 100.0 % | +| R2007 (AC1021) | 8 | 2237 | 0 | 0 | **100.0 %** | 100.0 % | +| R2010 (AC1024) | 8 | 2188 | 0 | 0 | **100.0 %** | 100.0 % | +| R2013 (AC1027) | 8 | 1787 | 0 | 0 | **100.0 %** | 100.0 % | +| R2018 (AC1032) | 4 | 1615 | 0 | 0 | **100.0 %** | 100.0 % | +| **Aggregate** | **61** | **122337** | **25** | **0** | **> 99.9 %** | **100.0 %** | A success rate counts records, and the sources differ in size by orders of @@ -46,14 +46,14 @@ a change came from decoding or from the corpus: | Source | Files tested | Decoded | Skipped | Errored | Success rate | Median drawing | |--------|--------------|---------|---------|---------|--------------|----------------| | nextgis | 42 | 8443 | 0 | 0 | **100.0 %** | 100.0 % | -| acadsharp | 10 | 7618 | 13 | 0 | **99.8 %** | 99.9 % | +| acadsharp | 10 | 7625 | 6 | 0 | **99.9 %** | 100.0 % | | bookviewerapp3 | 4 | 1440 | 19 | 0 | **98.7 %** | 99.4 % | | oshw-jetson | 4 | 104577 | 0 | 0 | **100.0 %** | 100.0 % | | planar-algorithm | 1 | 252 | 0 | 0 | **100.0 %** | 100.0 % | -| **All sources** | **61** | **122330** | **32** | **0** | **> 99.9 %** | **100.0 %** | +| **All sources** | **61** | **122337** | **25** | **0** | **> 99.9 %** | **100.0 %** | -Totals: 122,330 decoded / 32 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 99.8 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.5 %, R2000 99.9 %, R2004 > 99.9 %, R2007 > 99.9 %, R2010 > 99.9 %, R2013 99.9 %, R2018 99.9 %. +Totals: 122,337 decoded / 25 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 99.9 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.5 %, R2000 99.9 %, R2004 100.0 %, R2007 100.0 %, R2010 100.0 %, R2013 100.0 %, R2018 100.0 %. [`tools/corpus/baseline.tsv`](./tools/corpus/baseline.tsv) pins each drawing's decoded, skipped and errored counts, its class-census shortfalls diff --git a/STATUS.md b/STATUS.md index f610c12f..69d2ef79 100644 --- a/STATUS.md +++ b/STATUS.md @@ -15,8 +15,8 @@ the current capability matrix. examples (`tools/ci/run_test_targets.sh`), and the Corpus job runs the corpus-reading targets on the 19 pinned drawings; clippy and fmt clean. - **Current real-file decode coverage, measured in CI** (the - `Corpus (public DWG)` job, [run 40716086829](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40716086829) on 2026-10-28, on the public drawings - pinned in `tools/corpus/pins.tsv`): 122,330 decoded / 32 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 99.8 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.5 %, R2000 99.9 %, R2004 > 99.9 %, R2007 > 99.9 %, R2010 > 99.9 %, R2013 99.9 %, R2018 99.9 %. + `Corpus (public DWG)` job, [run 40736084515](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40736084515) on 2026-10-28, on the public drawings + pinned in `tools/corpus/pins.tsv`): 122,337 decoded / 25 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 99.9 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.5 %, R2000 99.9 %, R2004 100.0 %, R2007 100.0 %, R2010 100.0 %, R2013 100.0 %, R2018 100.0 %. The 308 errors of the 2026-10-26 measurement were closed by #226, #227, #228 and #71; `sample_AC1032.dwg` comes from ACadSharp's MIT `samples/`, saved by AutoCAD 2025 (#96, CLEANROOM.md). diff --git a/docs/data/spec-coverage.tsv b/docs/data/spec-coverage.tsv index 3397d0e1..ec713557 100644 --- a/docs/data/spec-coverage.tsv +++ b/docs/data/spec-coverage.tsv @@ -61,7 +61,7 @@ 20.4.34 POLYLINE (MESH) (30) 130 closes src/entities/polygon_mesh.rs #142 The spec's own example (§20.4.34.1) closes on the record's boundary (#107). 20.4.35 SOLID (31) 131 closes src/entities/solid.rs #145 DXF oracle (examples/dxf_oracle): 84 records R2000-R2018, every decoded value equals AutoCAD's (1008 equal, 336 by the DXF default, 0 with no DXF group); 336 DXF groups not decoded. The spec's own example (§20.4.35.1) closes on the record's boundary (#107). 20.4.36 TRACE (32) 131 closes src/entities/trace.rs #145 The spec's own example (§20.4.36.1) closes on the record's boundary (#107). -20.4.37 SHAPE (33) 132 missing #140 The spec's own example (§20.4.37.1) has no decoder to run (#107). +20.4.37 SHAPE (33) 132 closes src/entities/shape.rs #140 Closes on the one SHAPE of each release R14 to R2018 (the ACadSharp sample set), and on the spec's own example (§20.4.37.1, an R14 record), which closed here for the first time (#107). Against AutoCAD's DXF export the insertion point and scale of the 6 R2000 to R2018 records equal groups 10, 20, 30 and 40: 24 values, 0 differ (examples/probe_shape); the rotation, width factor, oblique angle, thickness and extrusion are at their defaults and absent from the DXF, and the shape number has no DXF group, so those are boundary-only. R14 has no DXF twin. No R13 file is in the corpus. 20.4.38 VIEWPORT ENTITY (34) 133 closes src/entities/viewport.rs #146 Every field of the section, every version branch, closes on all 51 records of the public corpus (R14 to R2018; no R13 file). Values equal the DXF twin on the 36 R2000 to R2018 records that have one (#71, ARCHITECTURE.md §7u). DXF oracle (examples/dxf_oracle): 36 records R2000-R2018, every decoded value equals AutoCAD's (1686 equal, 0 by the DXF default, 36 with no DXF group); 264 DXF groups not decoded. The spec's own example (§20.4.38.1) closes on the record's boundary (#107). 20.4.39 ELLIPSE (35) 136 closes src/entities/ellipse.rs #143 DXF oracle (examples/dxf_oracle): 11 records R2000-R2018, every decoded value equals AutoCAD's (132 equal, 0 by the DXF default, 0 with no DXF group); 22 DXF groups not decoded. The spec's own example (§20.4.39.1) closes on the record's boundary (#107). 20.4.40 SPLINE (36) 136 closes src/entities/spline.rs #143 DXF oracle (examples/dxf_oracle): 12 records R2000-R2018, 2 values disagree (#393) (292 equal, 0 by the DXF default, 46 with no DXF group); 120 DXF groups not decoded. The spec's own examples (§20.4.40.1) close on the record's boundary (#107). diff --git a/docs/spec-coverage.md b/docs/spec-coverage.md index 6427e1f9..0d82fe61 100644 --- a/docs/spec-coverage.md +++ b/docs/spec-coverage.md @@ -18,8 +18,8 @@ One row for every section of the *Open Design Specification for .dwg files* v5.4 ## Summary -- All sections: 0 `complete`, 105 `closes`, 6 `decoder`, 6 `partial`, 21 `missing`. -- The 104 object prescriptions of §20.4: 0 `complete`, 85 `closes`, 6 `decoder`, 2 `partial`, 11 `missing`. +- All sections: 0 `complete`, 106 `closes`, 6 `decoder`, 6 `partial`, 20 `missing`. +- The 104 object prescriptions of §20.4: 0 `complete`, 86 `closes`, 6 `decoder`, 2 `partial`, 10 `missing`. ## Sections @@ -87,7 +87,7 @@ One row for every section of the *Open Design Specification for .dwg files* v5.4 | §20.4.34 | POLYLINE (MESH) (30) | 130 | `closes` | src/entities/polygon_mesh.rs | #142 | The spec's own example (§20.4.34.1) closes on the record's boundary (#107). | | §20.4.35 | SOLID (31) | 131 | `closes` | src/entities/solid.rs | #145 | DXF oracle (examples/dxf_oracle): 84 records R2000-R2018, every decoded value equals AutoCAD's (1008 equal, 336 by the DXF default, 0 with no DXF group); 336 DXF groups not decoded. The spec's own example (§20.4.35.1) closes on the record's boundary (#107). | | §20.4.36 | TRACE (32) | 131 | `closes` | src/entities/trace.rs | #145 | The spec's own example (§20.4.36.1) closes on the record's boundary (#107). | -| §20.4.37 | SHAPE (33) | 132 | `missing` | | #140 | The spec's own example (§20.4.37.1) has no decoder to run (#107). | +| §20.4.37 | SHAPE (33) | 132 | `closes` | src/entities/shape.rs | #140 | Closes on the one SHAPE of each release R14 to R2018 (the ACadSharp sample set), and on the spec's own example (§20.4.37.1, an R14 record), which closed here for the first time (#107). Against AutoCAD's DXF export the insertion point and scale of the 6 R2000 to R2018 records equal groups 10, 20, 30 and 40: 24 values, 0 differ (examples/probe_shape); the rotation, width factor, oblique angle, thickness and extrusion are at their defaults and absent from the DXF, and the shape number has no DXF group, so those are boundary-only. R14 has no DXF twin. No R13 file is in the corpus. | | §20.4.38 | VIEWPORT ENTITY (34) | 133 | `closes` | src/entities/viewport.rs | #146 | Every field of the section, every version branch, closes on all 51 records of the public corpus (R14 to R2018; no R13 file). Values equal the DXF twin on the 36 R2000 to R2018 records that have one (#71, ARCHITECTURE.md §7u). DXF oracle (examples/dxf_oracle): 36 records R2000-R2018, every decoded value equals AutoCAD's (1686 equal, 0 by the DXF default, 36 with no DXF group); 264 DXF groups not decoded. The spec's own example (§20.4.38.1) closes on the record's boundary (#107). | | §20.4.39 | ELLIPSE (35) | 136 | `closes` | src/entities/ellipse.rs | #143 | DXF oracle (examples/dxf_oracle): 11 records R2000-R2018, every decoded value equals AutoCAD's (132 equal, 0 by the DXF default, 0 with no DXF group); 22 DXF groups not decoded. The spec's own example (§20.4.39.1) closes on the record's boundary (#107). | | §20.4.40 | SPLINE (36) | 136 | `closes` | src/entities/spline.rs | #143 | DXF oracle (examples/dxf_oracle): 12 records R2000-R2018, 2 values disagree (#393) (292 equal, 0 by the DXF default, 46 with no DXF group); 120 DXF groups not decoded. The spec's own examples (§20.4.40.1) close on the record's boundary (#107). | diff --git a/examples/probe_shape.rs b/examples/probe_shape.rs new file mode 100644 index 00000000..1b59f384 --- /dev/null +++ b/examples/probe_shape.rs @@ -0,0 +1,72 @@ +//! Does the §20.4.37 SHAPE field list close on every corpus record, and do +//! its values equal AutoCAD's DXF export? +//! +//! # What this proves +//! +//! For every SHAPE (type 0x21) of every drawing under the corpus directory +//! this reports whether the crate's decoder closed exactly on the record's +//! data-stream boundary (ARCHITECTURE.md §7i) and, where the corpus holds +//! AutoCAD's DXF export of the drawing, compares each decoded value with the +//! `AcDbShape` entity whose handle (group 5) matches: the insertion point +//! (10, 20, 30), scale (40), rotation (50), width factor (41), oblique angle +//! (51), thickness (39) and extrusion (210, 220, 230). A group the DXF leaves +//! out (AutoCAD omits a default) is counted as omitted, not compared against +//! an assumed default. The shape number has no DXF group, the DXF writing the +//! shape's name (group 2) instead, so it is not compared. R14 has no twin in +//! the corpus, so its record is checked against its boundary alone. +//! +//! ```sh +//! python3 -I tools/corpus/fetch.py --dest ../dwg-corpus +//! cargo run --release --example probe_shape -- ../dwg-corpus +//! ``` +//! +//! Exit status is non-zero when a record errors, a value differs, a twin +//! lacks the record, or no value was compared at all. + +#[path = "support/twin.rs"] +mod twin; + +use dwg::ObjectType; +use dwg::entities::DecodedEntity; +use std::process::ExitCode; +use twin::{Group, Probe, Record, Tally}; + +fn compare(decoded: &DecodedEntity, rec: Option<&Record>, t: &mut Tally) -> bool { + let DecodedEntity::Shape(ours) = decoded else { + return false; + }; + let Some(rec) = rec else { + return true; + }; + let real = |code: u16| rec.first(code).and_then(Group::real); + for (what, v, code) in [ + ("insertion x", ours.insertion_point.x, 10), + ("insertion y", ours.insertion_point.y, 20), + ("insertion z", ours.insertion_point.z, 30), + ("scale", ours.scale, 40), + ("rotation", ours.rotation.to_degrees(), 50), + ("width factor", ours.width_factor, 41), + ("oblique", ours.oblique.to_degrees(), 51), + ("thickness", ours.thickness, 39), + ("extrusion x", ours.extrusion.x, 210), + ("extrusion y", ours.extrusion.y, 220), + ("extrusion z", ours.extrusion.z, 230), + ] { + t.real(what, v, real(code)); + } + true +} + +fn main() -> ExitCode { + twin::run( + &Probe { + tag: "SHAPE", + class: None, + kind: Some(ObjectType::Shape), + dxf_kind: "SHAPE", + decodes_on: |_| true, + note: "; the shape number has no DXF group (the DXF holds the name)", + }, + compare, + ) +} diff --git a/src/entities/dispatch.rs b/src/entities/dispatch.rs index b9a05378..90096c97 100644 --- a/src/entities/dispatch.rs +++ b/src/entities/dispatch.rs @@ -59,6 +59,7 @@ pub enum DecodedEntity { Ray(ray::Ray), XLine(xline::XLine), Solid(solid::Solid), + Shape(crate::entities::shape::Shape), /// 3DSOLID (§20.4.41) — an ACIS solid body. ThreeDSolid(three_d_solid::ThreeDSolid), /// REGION (§20.4.41) — an ACIS planar bounded face. @@ -229,6 +230,7 @@ impl DecodedEntity { Self::Ray(_) => OBJECT_TYPE_RAY, Self::XLine(_) => OBJECT_TYPE_XLINE, Self::Solid(_) => OBJECT_TYPE_SOLID, + Self::Shape(_) => OBJECT_TYPE_SHAPE, Self::ThreeDSolid(_) => OBJECT_TYPE_3DSOLID, Self::Region(_) => OBJECT_TYPE_REGION, Self::Body(_) => OBJECT_TYPE_BODY, @@ -1502,12 +1504,9 @@ fn fixed_entity_label(type_code: u16) -> Option<&'static str> { // 0x14 ORDINATE, 0x15 LINEAR, 0x16 ALIGNED, 0x17 ANG_3PT, // 0x18 ANG_2LN, 0x19 RADIUS, 0x1A DIAMETER. OBJECT_TYPE_DIMENSION_MIN..=OBJECT_TYPE_DIMENSION_MAX => "DIMENSION", - // SHAPE has no field list matched against real bytes yet — the - // one record in the corpus has a 160-bit budget and nothing to - // spend it on, so it stays `Unhandled` rather than being fed a - // guess. IMAGE and MLEADER are custom classes whose codes vary - // per file and resolve through `decode_from_raw_with_class_map`. - OBJECT_TYPE_SHAPE => return None, + OBJECT_TYPE_SHAPE => "SHAPE", + // IMAGE and MLEADER are custom classes whose codes vary per file + // and resolve through `decode_from_raw_with_class_map`. _ => return None, }) } @@ -1540,6 +1539,7 @@ fn decode_fixed_entity_body( OBJECT_TYPE_RAY => DecodedEntity::Ray(ray::decode(c)?), OBJECT_TYPE_XLINE => DecodedEntity::XLine(xline::decode(c)?), OBJECT_TYPE_SOLID => DecodedEntity::Solid(solid::decode(c)?), + OBJECT_TYPE_SHAPE => DecodedEntity::Shape(crate::entities::shape::decode(c)?), OBJECT_TYPE_TRACE => DecodedEntity::Trace(trace::decode(c)?), OBJECT_TYPE_3DFACE => DecodedEntity::ThreeDFace(three_d_face::decode(c, version)?), // REGION / 3DSOLID / BODY — one §20.4.41 field list for all @@ -1951,6 +1951,7 @@ mod tests { OBJECT_TYPE_POLYLINE_PFACE, OBJECT_TYPE_POLYLINE_MESH, OBJECT_TYPE_SOLID, + OBJECT_TYPE_SHAPE, OBJECT_TYPE_TRACE, OBJECT_TYPE_VIEWPORT, OBJECT_TYPE_ELLIPSE, @@ -1979,9 +1980,7 @@ mod tests { "0x{code:04X} decodes but has no label, so it would be reported Unhandled" ); } - // SHAPE has a code but no field list; the reserved range has - // neither. - assert!(fixed_entity_label(OBJECT_TYPE_SHAPE).is_none()); + // The reserved range has neither a code nor a field list. for code in [0x00u16, 0x08, 0x09, 0x1FF, 0xFFFF] { assert!(fixed_entity_label(code).is_none(), "0x{code:04X}"); } diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 68c2c7ad..dc09ab79 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -23,6 +23,7 @@ //! | VERTEX_* | [`vertex`] | §19.4.55-59 | //! | ACAD_TABLE | [`acad_table`] | §20.4.96.2 | //! | SEQEND | [`seqend`] | §19.4.6 | +//! | SHAPE | [`shape`] | §20.4.37 | //! | IMAGE | [`image`] | §19.4.35 | //! | IMAGEDEF | [`imagedef`] | §19.5.26 | //! | PROXY ENTITY | [`proxy_entity_passthrough`] | §19.4.91 | @@ -95,6 +96,7 @@ pub mod ray; pub mod region; pub mod revolved_surface; pub mod seqend; +pub mod shape; pub mod solid; pub mod spline; pub mod sun; diff --git a/src/entities/shape.rs b/src/entities/shape.rs new file mode 100644 index 00000000..d4829651 --- /dev/null +++ b/src/entities/shape.rs @@ -0,0 +1,62 @@ +//! SHAPE entity (§20.4.37, type 0x21) — one glyph of a shape file, placed +//! in the drawing. +//! +//! A SHAPE carries the number of its shape in the shape file of its text +//! style, not the shape's name; the DXF writes the name (group 2), which +//! this crate cannot recover without reading the shape file (§20.4.56), so +//! that group is not compared. The field list is the one §20.4.37 prints, +//! with no release-dependent branch: +//! +//! ```text +//! 3BD insertion point DXF 10, 20, 30 +//! BD scale DXF 40, default 1 +//! BD rotation DXF 50, radians, default 0 +//! BD width factor DXF 41, default 1 +//! BD oblique DXF 51, radians, default 0 +//! BD thickness DXF 39 +//! BS shape number DXF 2 (by name) +//! 3BD extrusion DXF 210, 220, 230 +//! H SHAPEFILE hard pointer, in the handle stream +//! ``` +//! +//! The one SHAPE of each release of the public corpus (ACadSharp's +//! `sample_AC10xx`) closes on its data-stream boundary from R14 to R2018. + +use crate::bitcursor::BitCursor; +use crate::entities::{Point3D, Vec3D, read_bd3}; +use crate::error::Result; + +/// A decoded SHAPE. +#[derive(Debug, Clone, PartialEq)] +pub struct Shape { + /// `3BD` insertion point, DXF 10, 20, 30. + pub insertion_point: Point3D, + /// `BD` scale, DXF 40. + pub scale: f64, + /// `BD` rotation in radians, DXF 50. + pub rotation: f64, + /// `BD` width factor, DXF 41. + pub width_factor: f64, + /// `BD` oblique angle in radians, DXF 51. + pub oblique: f64, + /// `BD` thickness, DXF 39. + pub thickness: f64, + /// `BS` index of the shape in the shape file. + pub shape_number: i16, + /// `3BD` extrusion, DXF 210, 220, 230. + pub extrusion: Vec3D, +} + +/// Decode a SHAPE's field list (§20.4.37) past the common entity data. +pub fn decode(c: &mut BitCursor<'_>) -> Result { + Ok(Shape { + insertion_point: read_bd3(c)?, + scale: c.read_bd()?, + rotation: c.read_bd()?, + width_factor: c.read_bd()?, + oblique: c.read_bd()?, + thickness: c.read_bd()?, + shape_number: c.read_bs()?, + extrusion: read_bd3(c)?, + }) +} diff --git a/tests/fixtures/spec-examples/r14-objects.tsv b/tests/fixtures/spec-examples/r14-objects.tsv index cc9fd76e..8a4c27a0 100644 --- a/tests/fixtures/spec-examples/r14-objects.tsv +++ b/tests/fixtures/spec-examples/r14-objects.tsv @@ -34,7 +34,7 @@ section pages label type len handle crc expect record 20.4.34 130-131 3d surf sol st 1E 26 66 3CE7 closes 1A00478059A3680000055B22320C83D182887C0509620B3A0C818C8C3CE7 20.4.35 131 solid 1F 82 CF 1803 closes 520047C073E2981000013350A8BE182452D8F20752C301401D30FA00FF310A9682A0F2068BFA70478B40FA027F99E65FB500EA01FFF9D1853CD8FA027F99E65FB500EA01FFF9D1853CD8FA053820A60A21EA226A1803 20.4.36 132 trace 20 81 E7 7EC2 closes 5100480079E2981000055B5370DAA0ADEEC14205BAE02ADAA96002057529DE3EFF8942034E20B38F50C002004B2A126570A952049E9BA592BF40A2061D16C2A5618152026F4E85D6E788A205260A21F8206C1A7EC2 -20.4.37 133 shape 21 38 F5 3874 unhandled 260048407D67480000015B14AF3D963959A1480420D5143541088A04CD32F4C0182887A03028F9ED3874 +20.4.37 133 shape 21 38 F5 3874 closes 260048407D67480000015B14AF3D963959A1480420D5143541088A04CD32F4C0182887A03028F9ED3874 20.4.38 134-135 vpent 22 279 0126 6C19 closes 1701488080499DF51110005001E4D5649455702004610000A0000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000F03F2800000000000000002800000000000022402878921A65ACA21A4028000000000000124028000000000000494028000000000000000028000000000000000046000046640046010046010046000046000046000046000028000000000000000028000000000000000028000000000000000028000000000000E03F28000000000000E03F28000000000000E03F28000000000000E03F4600000200020102018DA100000D6C3C490D32D6510D20000000000000024810F12434CB594454800000000000001120182887A0508129009286C19 20.4.39 136 ellipse 23 76 0122 ED08 closes 4C0048C08048A1481000055B0C0A03298AE7424801F09FBC531040DA045123D0F1D6AF7B9F9A8915EA36B2DD17F50020000000001E07E5D2A47DB04C5EF9FC0C16A22A7D908CA0182887E083A069ED08 20.4.40 138 spline 24 97 0101 99F5 closes 61004900804066A81000055B204819777BAFB3BEF9B67B554821D1F6EC493D161C80603C07634316F89FC0E44E5364CA30B201F0333C1CA7C20E810185809AFE6F6388020789BE3C1B4F51FC5F5114FA2FCF94200418CB8BBBC69D67F182887E08130599F5 diff --git a/tools/corpus/baseline.tsv b/tools/corpus/baseline.tsv index 33ebd788..270ef7e3 100644 --- a/tools/corpus/baseline.tsv +++ b/tools/corpus/baseline.tsv @@ -60,11 +60,11 @@ polyline2d_line_2007.dwg nextgis AC1021 186 0 0 0 0 CELLSTYLEMAP 3>1;DICTIONARYV polyline2d_line_2010.dwg nextgis AC1024 181 0 0 0 0 CELLSTYLEMAP 1>0;DICTIONARYVAR 10>5 0 491 0 0 0 polyline2d_line_2013.dwg nextgis AC1027 132 0 0 0 0 - 0 369 0 0 0 polyline2d_line_R14.dwg nextgis AC1014 292 0 0 0 0 - 0 0 0 0 292 -sample_AC1014.dwg acadsharp AC1014 1238 4 0 0 0 - 0 0 0 0 1242 -sample_AC1015.dwg acadsharp AC1015 1043 4 0 0 0 - 0 3160 6 0 0 -sample_AC1018.dwg acadsharp AC1018 1013 1 0 0 0 ACDB_TEXTOBJECTCONTEXTDATA_CLASS 2>0;CELLSTYLEMAP 2>1;DICTIONARYVAR 15>14;TABLECONTENT 9>2;TABLEGEOMETRY 9>2 0 3135 6 0 0 -sample_AC1021.dwg acadsharp AC1021 924 1 0 0 0 ACDB_TEXTOBJECTCONTEXTDATA_CLASS 2>0;TABLECONTENT 8>2;TABLEGEOMETRY 8>2 0 2960 6 0 0 -sample_AC1024.dwg acadsharp AC1024 922 1 0 0 0 TABLECONTENT 7>2;TABLEGEOMETRY 7>2 0 3050 6 0 0 -sample_AC1027.dwg acadsharp AC1027 864 1 0 0 0 TABLECONTENT 6>2;TABLEGEOMETRY 6>2 0 2905 6 0 0 -sample_AC1032.dwg acadsharp AC1032 841 1 0 0 0 TABLECONTENT 5>2;TABLEGEOMETRY 5>2 0 2852 6 0 0 +sample_AC1014.dwg acadsharp AC1014 1239 3 0 0 0 - 0 0 0 0 1242 +sample_AC1015.dwg acadsharp AC1015 1044 3 0 0 0 - 0 3160 6 0 0 +sample_AC1018.dwg acadsharp AC1018 1014 0 0 0 0 ACDB_TEXTOBJECTCONTEXTDATA_CLASS 2>0;CELLSTYLEMAP 2>1;DICTIONARYVAR 15>14;TABLECONTENT 9>2;TABLEGEOMETRY 9>2 0 3135 6 0 0 +sample_AC1021.dwg acadsharp AC1021 925 0 0 0 0 ACDB_TEXTOBJECTCONTEXTDATA_CLASS 2>0;TABLECONTENT 8>2;TABLEGEOMETRY 8>2 0 2960 6 0 0 +sample_AC1024.dwg acadsharp AC1024 923 0 0 0 0 TABLECONTENT 7>2;TABLEGEOMETRY 7>2 0 3050 6 0 0 +sample_AC1027.dwg acadsharp AC1027 865 0 0 0 0 TABLECONTENT 6>2;TABLEGEOMETRY 6>2 0 2905 6 0 0 +sample_AC1032.dwg acadsharp AC1032 842 0 0 0 0 TABLECONTENT 5>2;TABLEGEOMETRY 5>2 0 2852 6 0 0 原始数据.dwg planar-algorithm AC1015 252 0 0 0 0 - 0 636 0 0 0