From 683b1e98453e4b7d355be6248cc9601d4d7ed699 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 11 Oct 2026 03:38:33 +0700 Subject: [PATCH] #1011: DXF US survey inches, yards and miles import at their size $INSUNITS 22, 23 and 24 (US survey inch, yard and mile, as Autodesk's INSUNITS reference lists them) had no size or name, so the drawing opened as unitless (millimetres, or inches with $MEASUREMENT 0) and document.dxfInfo said Unitless. They now derive from the US survey foot (1200/3937 m) like code 21, with their names. The test reads each survey code's unit name and default scale and opens a survey-inch line at 7200.0144 pt. Co-Authored-By: Claude Opus 5.5 # Conflicts: # ROADMAP.md --- ROADMAP.md | 2 +- crates/cad/src/import.rs | 5 ++++- crates/cad/src/import/build.rs | 7 ++++++- crates/cad/src/import/tests.rs | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 7e3f4710..f79f0acc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -123,7 +123,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap | Date | What landed | |---|---| -| 2026-10-11 | Effect › Brush Strokes: Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes, Sumi-e (G2). Command parameters from agents and files checked before anything changes (paint, place, cuts, dashes, crop, asset ids, anchors, paths, distort; #984–#999); command dialogs keep whole numbers whole, so Create Object Mosaic takes the tiles typed (#1000); stepper arrows take Shift (×10) and Ctrl/Cmd (÷10) like the arrow keys (#991). Exports and Rasterize supersample Art Optimized edges, so shapes meeting edge to edge leave no seam (#983). Character and paragraph styles take leading, kerning, mojikumi and the other attributes left out at their defaults (#1001). Drawing, typing and pasting with no layer shown and unlocked is refused instead of adding art to a hidden or locked layer (#1002). MCP `add_text` gives type on or in a path the colour asked for (#1003). MCP `screenshot` of a connected app renders the artboard asked for (#1004). `vectorcraft-cli run` ends quietly when its reader closes early (#1005). Select › All on Active Artboard selects on the artboard made active, not always the first (#1006). Reverse Gradient keeps off-centre midpoints, mirrored (#1007). A polar grid with no radial dividers draws none (#1008). Use Precise Cursors also covers the Pen's join pointer (#1009). SVGs with one absolute and one unitless root side import at their size, unstretched (#1010). | +| 2026-10-11 | Effect › Brush Strokes: Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes, Sumi-e (G2). Command parameters from agents and files checked before anything changes (paint, place, cuts, dashes, crop, asset ids, anchors, paths, distort; #984–#999); command dialogs keep whole numbers whole, so Create Object Mosaic takes the tiles typed (#1000); stepper arrows take Shift (×10) and Ctrl/Cmd (÷10) like the arrow keys (#991). Exports and Rasterize supersample Art Optimized edges, so shapes meeting edge to edge leave no seam (#983). Character and paragraph styles take leading, kerning, mojikumi and the other attributes left out at their defaults (#1001). Drawing, typing and pasting with no layer shown and unlocked is refused instead of adding art to a hidden or locked layer (#1002). MCP `add_text` gives type on or in a path the colour asked for (#1003). MCP `screenshot` of a connected app renders the artboard asked for (#1004). `vectorcraft-cli run` ends quietly when its reader closes early (#1005). Select › All on Active Artboard selects on the artboard made active, not always the first (#1006). Reverse Gradient keeps off-centre midpoints, mirrored (#1007). A polar grid with no radial dividers draws none (#1008). Use Precise Cursors also covers the Pen's join pointer (#1009). SVGs with one absolute and one unitless root side import at their size, unstretched (#1010). DXF US survey inches, yards and miles open at their size and name (#1011). | | 2026-10-10 | Initial live Revolve (Effect › 3D and Materials, #846, #605), in its own `three-d` crate. Progress docs re-measured and restructured to the craftrules standard (this page, `docs/target-app-parity.md`, `gaps.md`, `ui-parity.md`, the format, hardware, localization, effects and type checklists). Same day: v0.8.0; Effect › Distort, Pixelate and Texture filters (13); German interface; Variables (data merge); Graph Type value axes and tick marks; restart when the first frame never reaches the screen (#964) | | 2026-10-09 | Radial Blur, Smart Blur, Unsharp Mask (gap 2 begins); 340 commits, the busiest day | | 2026-10-08 | v0.5.0–v0.7.0; community contributions: Pen and shape-tool modifiers, Layers, Artboards, Japanese composition, Hebrew/Arabic type, interface languages, saved selections | diff --git a/crates/cad/src/import.rs b/crates/cad/src/import.rs index 721586e5..b7967093 100644 --- a/crates/cad/src/import.rs +++ b/crates/cad/src/import.rs @@ -129,7 +129,7 @@ fn default_ratio(h: &reader::Header) -> (Unit, f64) { /// The name of `$INSUNITS` code `code`. fn units_name(code: i64) -> &'static str { - const NAMES: [&str; 22] = [ + const NAMES: [&str; 25] = [ "Unitless", "Inches", "Feet", @@ -152,6 +152,9 @@ fn units_name(code: i64) -> &'static str { "Light years", "Parsecs", "US survey feet", + "US survey inches", + "US survey yards", + "US survey miles", ]; usize::try_from(code).ok().and_then(|i| NAMES.get(i)).copied().unwrap_or("Unitless") } diff --git a/crates/cad/src/import/build.rs b/crates/cad/src/import/build.rs index 449b95f7..404b6a16 100644 --- a/crates/cad/src/import/build.rs +++ b/crates/cad/src/import/build.rs @@ -66,6 +66,8 @@ pub(crate) struct Build<'d, 'a> { /// The unit of `$INSUNITS` code `code` in points (0, unitless: none). pub(crate) fn insunits_points(code: i64) -> Option { let m = 72.0 / 0.0254; + // The US survey foot: exactly 1200/3937 m. + let survey_ft = m * 1200.0 / 3937.0; Some(match code { 1 => 72.0, 2 => 864.0, @@ -87,7 +89,10 @@ pub(crate) fn insunits_points(code: i64) -> Option { 18 => m * 1.495_978_707e11, 19 => m * 9.460_730_472_580_8e15, 20 => m * 3.085_677_581e16, - 21 => m * 1200.0 / 3937.0, + 21 => survey_ft, + 22 => survey_ft / 12.0, + 23 => survey_ft * 3.0, + 24 => survey_ft * 5280.0, _ => return None, }) } diff --git a/crates/cad/src/import/tests.rs b/crates/cad/src/import/tests.rs index 0e69cb54..5cddf427 100644 --- a/crates/cad/src/import/tests.rs +++ b/crates/cad/src/import/tests.rs @@ -128,6 +128,20 @@ fn units_choose_the_default_scale() { let um = info(&drawing(&[(9, "$INSUNITS"), (70, "13")], &[], &[], LINE)).unwrap(); assert_eq!((um.unit, um.units.as_str()), (Unit::Millimeters, "Microns")); assert!((um.scale - 1000.0).abs() < 1e-6); + // #1011: US survey inches, yards and miles (from the survey foot, 1200/3937 m), not unitless. + let survey_ft = 72.0 / 0.0254 * 1200.0 / 3937.0; + for (code, name, pt) in [ + ("21", "US survey feet", survey_ft), + ("22", "US survey inches", survey_ft / 12.0), + ("23", "US survey yards", survey_ft * 3.0), + ("24", "US survey miles", survey_ft * 5280.0), + ] { + let i = info(&drawing(&[(9, "$INSUNITS"), (70, code)], &[], &[], LINE)).unwrap(); + assert_eq!((i.unit, i.units.as_str()), (Unit::Millimeters, name)); + assert!((i.scale * pt - 72.0 / 25.4).abs() < 1e-9, "{name}: 1 mm is {} units", i.scale); + } + let survey_in = open(&drawing(&[(9, "$INSUNITS"), (70, "22")], &[], &[], LINE), &ImportOptions::default()).document; + assert!((survey_in.artboards[0].rect.width() - 7200.0144000288).abs() < 1e-6, "{:?}", survey_in.artboards[0].rect); let imperial = info(&drawing(&[(9, "$MEASUREMENT"), (70, "0")], &[], &[], LINE)).unwrap(); assert_eq!((imperial.unit, imperial.scale), (Unit::Inches, 1.0)); // A ratio: 1 cm = 10 units is 1:1 in millimetres again.