Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` (<!-- corpus-run:begin -->[run 40716086829](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40716086829) on 2026-10-28<!-- corpus-run:end -->) with
job on `main` (<!-- corpus-run:begin -->[run 40719710653](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40719710653) on 2026-10-28<!-- corpus-run:end -->) 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):
Expand Down Expand Up @@ -99,7 +99,7 @@ provisional until a controlled AutoCAD drawing confirms it (#348).
add up, not that the values are right. The DXF value oracle
([`examples/dxf_oracle/`](./examples/dxf_oracle/main.rs)) pairs every decoded
record with the same handle in AutoCAD's DXF export and scores each field. On
`main` it scores <!-- oracle-summary:begin -->173,688 field comparisons on 73 types: 153,787 equal, 11,916 equal to the DXF default, 288 different and 7,697 present only in the DWG<!-- oracle-summary:end -->
`main` it scores <!-- oracle-summary:begin -->173,765 field comparisons on 74 types: 153,864 equal, 11,916 equal to the DXF default, 288 different and 7,697 present only in the DWG<!-- oracle-summary:end -->
([`tools/oracle/baseline.tsv`](./tools/oracle/baseline.tsv) pins the result,
and CI fails when it moves). Decoded types with no scorer yet are listed as
unscored in the oracle summary the Corpus job uploads.
Expand Down
2 changes: 1 addition & 1 deletion STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ 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, <!-- corpus-run:begin -->[run 40716086829](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40716086829) on 2026-10-28<!-- corpus-run:end -->, on the public drawings
`Corpus (public DWG)` job, <!-- corpus-run:begin -->[run 40719710653](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40719710653) on 2026-10-28<!-- corpus-run:end -->, on the public drawings
pinned in `tools/corpus/pins.tsv`): <!-- corpus-summary:begin -->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-summary:end -->
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
Expand Down
2 changes: 1 addition & 1 deletion docs/data/spec-coverage.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
20.4.75 HATCH 188 partial src/entities/hatch.rs #147 Gradient block unconditional (ARCHITECTURE §7g); a spline edge with no fit points stores no tangents (§7y, #228). The 8 R14 records are never routed (#229). DXF oracle (examples/dxf_oracle): 48 records R2000-R2018, every decoded value equals AutoCAD's (23360 equal, 0 by the DXF default, 20 with no DXF group); 454 DXF groups not decoded. The spec's own example (§20.4.75.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107).
20.4.76 FIELD 192 missing #149
20.4.77 FIELDLIST 193 missing #149
20.4.78 GEODATA 194 closes src/entities/geodata.rs #150 Routed by class name; geoloc.dwg's record (object version 3) closes and all 77 values equal its DXF twin. The north direction is a 2RD, not 3RD (ARCHITECTURE §7aa). Object version 1 and the R2007-and-earlier civil block are unmeasured.
20.4.78 GEODATA 194 closes src/entities/geodata.rs #150 Routed by class name; geoloc.dwg's record (object version 3) closes and all 77 values equal its DXF twin. The north direction is a 2RD, not 3RD (ARCHITECTURE §7aa). Object version 1 and the R2007-and-earlier civil block are unmeasured. DXF oracle (examples/dxf_oracle): 1 records R2018, every decoded value equals AutoCAD's (77 equal, 0 by the DXF default, 0 with no DXF group); 3 DXF groups not decoded.
20.4.79 IDBUFFER 196 missing #149 The spec's own example (§20.4.79.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107).
20.4.80 IMAGE 197 decoder src/entities/image.rs #150 DXF oracle (examples/dxf_oracle): 6 records R2000-R2018, every decoded value equals AutoCAD's (135 equal, 0 by the DXF default, 0 with no DXF group); 48 DXF groups not decoded. The spec's own example (§20.4.80.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107).
20.4.81 IMAGEDEF 199 decoder src/entities/imagedef.rs #150 DXF oracle (examples/dxf_oracle): 6 records R2000-R2018, every decoded value equals AutoCAD's (48 equal, 0 by the DXF default, 0 with no DXF group); 12 DXF groups not decoded. The spec's own example (§20.4.81.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107).
Expand Down
2 changes: 1 addition & 1 deletion docs/spec-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ One row for every section of the *Open Design Specification for .dwg files* v5.4
| §20.4.75 | HATCH | 188 | `partial` | src/entities/hatch.rs | #147 | Gradient block unconditional (ARCHITECTURE §7g); a spline edge with no fit points stores no tangents (§7y, #228). The 8 R14 records are never routed (#229). DXF oracle (examples/dxf_oracle): 48 records R2000-R2018, every decoded value equals AutoCAD's (23360 equal, 0 by the DXF default, 20 with no DXF group); 454 DXF groups not decoded. The spec's own example (§20.4.75.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107). |
| §20.4.76 | FIELD | 192 | `missing` | | #149 | |
| §20.4.77 | FIELDLIST | 193 | `missing` | | #149 | |
| §20.4.78 | GEODATA | 194 | `closes` | src/entities/geodata.rs | #150 | Routed by class name; geoloc.dwg's record (object version 3) closes and all 77 values equal its DXF twin. The north direction is a 2RD, not 3RD (ARCHITECTURE §7aa). Object version 1 and the R2007-and-earlier civil block are unmeasured. |
| §20.4.78 | GEODATA | 194 | `closes` | src/entities/geodata.rs | #150 | Routed by class name; geoloc.dwg's record (object version 3) closes and all 77 values equal its DXF twin. The north direction is a 2RD, not 3RD (ARCHITECTURE §7aa). Object version 1 and the R2007-and-earlier civil block are unmeasured. DXF oracle (examples/dxf_oracle): 1 records R2018, every decoded value equals AutoCAD's (77 equal, 0 by the DXF default, 0 with no DXF group); 3 DXF groups not decoded. |
| §20.4.79 | IDBUFFER | 196 | `missing` | | #149 | The spec's own example (§20.4.79.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107). |
| §20.4.80 | IMAGE | 197 | `decoder` | src/entities/image.rs | #150 | DXF oracle (examples/dxf_oracle): 6 records R2000-R2018, every decoded value equals AutoCAD's (135 equal, 0 by the DXF default, 0 with no DXF group); 48 DXF groups not decoded. The spec's own example (§20.4.80.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107). |
| §20.4.81 | IMAGEDEF | 199 | `decoder` | src/entities/imagedef.rs | #150 | DXF oracle (examples/dxf_oracle): 6 records R2000-R2018, every decoded value equals AutoCAD's (48 equal, 0 by the DXF default, 0 with no DXF group); 12 DXF groups not decoded. The spec's own example (§20.4.81.1) is a class-numbered record with no class table printed, so only its walk and CRC are checked (#107). |
Expand Down
113 changes: 113 additions & 0 deletions examples/dxf_oracle/scorers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ pub fn section_of(label: &str) -> &'static str {
"MLINESTYLE" => "§20.4.73",
"MLINE" => "§20.4.50",
"MLEADERSTYLE" => "§20.4.87",
"GEODATA" => "§20.4.78",
"DICTIONARYVAR" => "§20.4.74",
"LAYOUT" => "§20.4.84",
"LWPOLYLINE" => "§20.4.85",
Expand Down Expand Up @@ -1043,6 +1044,7 @@ pub fn score(
D::Leader(l) => score_leader(&mut c, l),
D::VPort(v) => score_vport(&mut c, v),
D::View(v) => score_view(&mut c, v),
D::GeoData(g) => score_geodata(&mut c, g),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Commit the GEODATA rows to the oracle baseline

The new scorer is enabled, but tools/oracle/baseline.tsv still contains no GEODATA rows. The oracle step in .github/workflows/ci.yml uses that file as its regression gate, and report::ratchet checks only keys already present in the baseline; disagreements printed by main.rs do not independently fail the command. Consequently, any of these newly scored values could regress—or the scorer could stop running entirely—without failing CI. Regenerate and commit the baseline with the GEODATA rows so the reported 77 agreeing values are protected.

Useful? React with 👍 / 👎.

D::BlockRecord(b) => score_block_record(&mut c, b, rec.handle, dwg),
// §20.4.51 and the other controls: BL num_entries; the TABLE's 70 is
// the DXF Reference's "maximum number of entries".
Expand Down Expand Up @@ -2088,6 +2090,117 @@ fn score_tablestyle(c: &mut Ctx<'_>, t: &dwg::objects::acad_table_style::AcadTab
}
}

// §20.4.78 against the DXF Reference's AcDbGeoData groups, as the decoder's
// probe maps them: the object version 90, the design coordinate type 70,
// the design point 10, the reference point 11, the unit scales and units
// 40, 91, 41 and 92, the up direction 210, the north direction 12 and 22,
// the scale estimation 95 and 141, the sea level 294 and 142, the
// projection radius 143, the coordinate system definition (303 chunks,
// then 301), the GeoRSS tag 302, the observation tags 305 to 307, the mesh
// points (93, then 13/14 source and destination) and the mesh faces (96,
// then 97, 98 and 99). The definition is written in chunks of 303 and a
// last 301, which are joined before comparing. Version 1 records (the
// R2009 layout) do not occur in the corpus and are not scored.
fn score_geodata(c: &mut Ctx<'_>, g: &dwg::entities::geodata::GeoData) {
if g.object_version < 2 {
c.dwg_only("(object version 1 is not scored)", g.object_version);
return;
}
if !c.subclass("AcDbGeoData") {
c.dwg_only("(the export has no AcDbGeoData subclass)", ());
return;
}
c.int("object_version", 90, i64::from(g.object_version), None);
c.int(
"design_coordinate_type",
70,
i64::from(g.design_coordinate_type),
None,
);
c.point3("design_point", 10, g.design_point, None);
c.point3("reference_point", 11, g.reference_point, None);
c.real("horizontal_unit_scale", 40, g.horizontal_unit_scale, None);
c.int("horizontal_units", 91, i64::from(g.horizontal_units), None);
c.real("vertical_unit_scale", 41, g.vertical_unit_scale, None);
c.int("vertical_units", 92, i64::from(g.vertical_units), None);
c.point3("up_direction", 210, g.up_direction, None);
c.point2("north_direction", 12, g.north_direction);
c.int(
"scale_estimation_method",
95,
i64::from(g.scale_estimation_method),
None,
);
c.real("user_scale_factor", 141, g.user_scale_factor, None);
c.int(
"sea_level_correction",
294,
i64::from(g.sea_level_correction),
None,
);
c.real("sea_level_elevation", 142, g.sea_level_elevation, None);
c.real(
"coordinate_projection_radius",
143,
g.coordinate_projection_radius,
None,
);
let mut chunks = String::new();
let mut code_page = false;
loop {
match c.take_text(303) {
Ok(Some(t)) => chunks.push_str(t),
Ok(None) => break,
Err(CodePage) => {
code_page = true;
break;
}
}
}
let last = if code_page {
Err(CodePage)
} else {
c.take_text(301)
};
match last {
Ok(Some(last)) => {
chunks.push_str(last);
let chunks = caret_decode(&chunks);
c.derived(
"coordinate_system_definition (303 chunks + 301)",
"303+301",
g.coordinate_system_definition == chunks,
format!("{} chars", g.coordinate_system_definition.chars().count()),
format!("{} chars", chunks.chars().count()),
);
}
Ok(None) | Err(CodePage) => c.dwg_only(
"coordinate_system_definition",
g.coordinate_system_definition.chars().count(),
),
}
c.text("geo_rss_tag", 302, &g.geo_rss_tag, None);
c.text("observation_from_tag", 305, &g.observation_from_tag, None);
c.text("observation_to_tag", 306, &g.observation_to_tag, None);
c.text(
"observation_coverage_tag",
307,
&g.observation_coverage_tag,
None,
);
c.int("mesh point count", 93, g.mesh_points.len() as i64, None);
for (s, d) in &g.mesh_points {
c.point2("mesh_points.source", 13, *s);
c.point2("mesh_points.destination", 14, *d);
}
c.int("mesh face count", 96, g.mesh_faces.len() as i64, None);
for f in &g.mesh_faces {
c.int("mesh_faces.0", 97, i64::from(f[0]), None);
c.int("mesh_faces.1", 98, i64::from(f[1]), None);
c.int("mesh_faces.2", 99, i64::from(f[2]), None);
}
}

// §20.4.73 with the DXF Reference's MLINESTYLE groups: 2, 70, 3, 62, 51, 52,
// 71, then 49 / 62 / 6 per line. The flag bits are remapped per the spec's
// table (DWG 1 -> DXF 2, 2 -> 1, 32 -> 64, 64 -> 32, 512 -> 1024, 1024 -> 512).
Expand Down
33 changes: 33 additions & 0 deletions tools/oracle/baseline.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -7226,6 +7226,39 @@ R2018 §20.4.75 HATCH seed point count 98 8 0 0 0
R2018 §20.4.75 HATCH seed_points.x 10 8 0 0 0
R2018 §20.4.75 HATCH seed_points.y 20 8 0 0 0
R2018 §20.4.75 HATCH solid_fill 70 8 0 0 0
R2018 §20.4.78 GEODATA coordinate_projection_radius 143 1 0 0 0
R2018 §20.4.78 GEODATA coordinate_system_definition (303 chunks + 301) 303+301 1 0 0 0
R2018 §20.4.78 GEODATA design_coordinate_type 70 1 0 0 0
R2018 §20.4.78 GEODATA design_point.x 10 1 0 0 0
R2018 §20.4.78 GEODATA design_point.y 20 1 0 0 0
R2018 §20.4.78 GEODATA design_point.z 30 1 0 0 0
R2018 §20.4.78 GEODATA geo_rss_tag 302 1 0 0 0
R2018 §20.4.78 GEODATA horizontal_unit_scale 40 1 0 0 0
R2018 §20.4.78 GEODATA horizontal_units 91 1 0 0 0
R2018 §20.4.78 GEODATA mesh face count 96 1 0 0 0
R2018 §20.4.78 GEODATA mesh point count 93 1 0 0 0
R2018 §20.4.78 GEODATA mesh_points.destination.x 14 12 0 0 0
R2018 §20.4.78 GEODATA mesh_points.destination.y 24 12 0 0 0
R2018 §20.4.78 GEODATA mesh_points.source.x 13 12 0 0 0
R2018 §20.4.78 GEODATA mesh_points.source.y 23 12 0 0 0
R2018 §20.4.78 GEODATA north_direction.x 12 1 0 0 0
R2018 §20.4.78 GEODATA north_direction.y 22 1 0 0 0
R2018 §20.4.78 GEODATA object_version 90 1 0 0 0
R2018 §20.4.78 GEODATA observation_coverage_tag 307 1 0 0 0
R2018 §20.4.78 GEODATA observation_from_tag 305 1 0 0 0
R2018 §20.4.78 GEODATA observation_to_tag 306 1 0 0 0
R2018 §20.4.78 GEODATA reference_point.x 11 1 0 0 0
R2018 §20.4.78 GEODATA reference_point.y 21 1 0 0 0
R2018 §20.4.78 GEODATA reference_point.z 31 1 0 0 0
R2018 §20.4.78 GEODATA scale_estimation_method 95 1 0 0 0
R2018 §20.4.78 GEODATA sea_level_correction 294 1 0 0 0
R2018 §20.4.78 GEODATA sea_level_elevation 142 1 0 0 0
R2018 §20.4.78 GEODATA up_direction.x 210 1 0 0 0
R2018 §20.4.78 GEODATA up_direction.y 220 1 0 0 0
R2018 §20.4.78 GEODATA up_direction.z 230 1 0 0 0
R2018 §20.4.78 GEODATA user_scale_factor 141 1 0 0 0
R2018 §20.4.78 GEODATA vertical_unit_scale 41 1 0 0 0
R2018 §20.4.78 GEODATA vertical_units 92 1 0 0 0
R2018 §20.4.80 IMAGE brightness 281 1 0 0 0
R2018 §20.4.80 IMAGE clip vertex count (a polygon is closed by repeating its first) 91 1 0 0 0
R2018 §20.4.80 IMAGE clip_boundary[0].x 14 1 0 0 0
Expand Down
Loading