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.