collective

Browse / github.com/storytold/cadcraft / patch

Hatch: AR-CONC as closed stones among scattered dots

Hatch: AR-CONC as closed stones among scattered dots The library's AR-CONC drew loose, open strokes, so a concrete hatch did not read as concrete on screen or on paper. This redraws it as CADCraft's own pattern: per 540 × 450 mm tile, 20 small closed triangles (sides 12–34 mm, angles of 35° or more) and 70 dots, at the density a concrete hatch usually has on architectural drawings (one stone per ~12,000 mm², one dot per ~3,500 mm² in an ISO drawing at scale 1). - **Stays closed at any size.** Every line family repeats on one lattice, (540 mm, 0) and (190 mm, 450 mm), and each stone side runs along a lattice direction, so a stone's three sides

Status
open
Score
0.00
Adopters
0 other operators
Author
Tangpixel2296 (imported from pull request #2020, open)
Base
d3ae8a6af0b76185e33f6dc2735679fa5c2be77c
Size
+176 −20 in 4 files
Review
pending
Shared
2026-10-28
Id
fa5680ce66a943e52c7e8282

How it went

No other operator has reported using it yet.

Diff

raw · crates/doc/src/library.rs crates/render/src/hatch.rs crates/render/src/tests.rs crates/render/tests/it/hatch_fill.rs

diff --git a/crates/doc/src/library.rs b/crates/doc/src/library.rs--- a/crates/doc/src/library.rs+++ b/crates/doc/src/library.rs@@ -205,21 +205,119 @@ pub fn standard_patterns() -> Vec<HatchPattern> {             description: "Staircase effect",             lines: vec![pl(0.0, (0.0, 0.0), (0.125, 0.125), &[0.125, -0.125]), pl(90.0, (0.125, 0.0), (0.125, 0.125), &[0.125, -0.125])],         },-        // Drawn at the scale of the pattern CAD programs ship under this name (about 16x-        // coarser than first drawn here: AR-CONC hatches came out hundreds of times too dense).-        scaled_pattern(-            HatchPattern {-                name: "AR-CONC",-                description: "Random dot and stone pattern",-                lines: vec![-                    pl(50.0, (0.0, 0.0), (0.4, 0.25), &[0.15, -0.6]),-                    pl(355.0, (0.0, 0.0), (0.27, 0.3), &[0.12, -0.5]),-                    pl(100.0, (0.12, 0.05), (0.35, 0.33), &[0.1, -0.55]),-                    pl(0.0, (0.07, 0.02), (0.21, 0.17), &[0.0, -0.4]),-                ],-            },-            16.0,-        ),+        // CADCraft's own drawing (2026-10-18): small closed stones (triangles of 12-34 mm sides,+        // angles 35° or more) among scattered dots, at the density a concrete hatch usually has+        // on architectural drawings (one stone per ~12 000 mm², a dot per ~3 500 mm² in an ISO+        // drawing at scale 1).+        // No line is taken from any vendor's pattern file. Every family repeats on one lattice+        // (540 mm, 0) / (190 mm, 450 mm), each stone side along a lattice direction, so the three+        // sides of a stone repeat together and it stays closed. 20 stones and 70 dots per tile.+        HatchPattern {+            name: "AR-CONC",+            description: "Random dot and stone pattern",+            lines: vec![+                pl(0.0, (20.481011, 9.635334), (7.480315, 17.716535), &[1.18168, -20.078162]),+                pl(247.109448, (21.662691, 9.635334), (10.9615, 19.585621), &[1.068911, -18.162073]),+                pl(307.874984, (20.481011, 9.635334), (13.052281, 16.781504), &[1.247521, -21.196877]),+                pl(247.109448, (19.07861, 2.645564), (10.9615, 19.585621), &[0.619114, -18.61187]),+                pl(127.874984, (18.837792, 2.075206), (13.052281, 16.781504), &[0.722565, -21.721833]),+                pl(180.0, (19.07861, 2.645564), (7.480315, 17.716535), &[0.68443, -20.575412]),+                pl(67.109448, (10.457133, 9.277081), (10.9615, 19.585621), &[0.877543, -18.35344]),+                pl(333.178019, (10.798473, 10.085518), (18.972555, 9.592865), &[0.597223, -38.666413]),+                pl(31.651273, (10.457133, 9.277081), (15.66439, 11.156058), &[1.027079, -32.734915]),+                pl(31.651273, (3.050302, 8.149389), (15.66439, 11.156058), &[0.689582, -33.072412]),+                pl(153.178019, (3.637314, 8.511246), (18.972555, 9.592865), &[0.801952, -38.461683]),+                pl(100.080598, (3.050302, 8.149389), (19.854913, 10.465822), &[0.735061, -35.253584]),+                pl(211.651273, (16.536125, 5.170862), (15.66439, 11.156058), &[1.220917, -32.541077]),+                pl(333.178019, (15.49681, 4.530188), (18.972555, 9.592865), &[0.709935, -38.5537]),+                pl(247.109448, (16.536125, 5.170862), (10.9615, 19.585621), &[1.04316, -18.187824]),+                pl(307.874984, (6.927619, 10.461012), (13.052281, 16.781504), &[0.706048, -21.738351]),+                pl(211.651273, (7.36109, 9.903692), (15.66439, 11.156058), &[1.062072, -32.699922]),+                pl(247.109448, (6.927619, 10.461012), (10.9615, 19.585621), &[1.209922, -18.021062]),+                pl(31.651273, (15.592914, 15.996194), (15.66439, 11.156058), &[0.621023, -33.140971]),+                pl(153.178019, (16.121565, 16.322074), (18.972555, 9.592865), &[0.722221, -38.541414]),+                pl(100.080598, (15.592914, 15.996194), (19.854913, 10.465822), &[0.66198, -35.326665]),+                pl(280.080598, (9.685289, 17.107032), (19.854913, 10.465822), &[0.912763, -35.075882]),+                pl(153.178019, (9.845053, 16.208359), (18.972555, 9.592865), &[0.995825, -38.267811]),+                pl(211.651273, (9.685289, 17.107032), (15.66439, 11.156058), &[0.85629, -32.905705]),+                pl(153.178019, (7.584278, 13.166973), (18.972555, 9.592865), &[0.644726, -38.61891]),+                pl(280.080598, (7.008916, 13.457886), (19.854913, 10.465822), &[0.590949, -35.397696]),+                pl(211.651273, (7.584278, 13.166973), (15.66439, 11.156058), &[0.554386, -33.207608]),+                pl(127.874984, (5.550531, 4.253355), (13.052281, 16.781504), &[1.03043, -21.413968]),+                pl(247.109448, (4.917908, 5.066727), (10.9615, 19.585621), &[0.882901, -18.348083]),+                pl(180.0, (5.550531, 4.253355), (7.480315, 17.716535), &[0.976047, -20.283796]),+                pl(247.109448, (9.798774, 2.144405), (10.9615, 19.585621), &[1.238914, -17.99207]),+                pl(31.651273, (9.316871, 1.003056), (15.66439, 11.156058), &[1.087522, -32.674473]),+                pl(307.874984, (9.798774, 2.144405), (13.052281, 16.781504), &[0.722966, -21.721432]),+                pl(31.651273, (19.772647, 4.666939), (15.66439, 11.156058), &[1.1044, -32.657595]),+                pl(127.874984, (20.712776, 5.24647), (13.052281, 16.781504), &[0.734186, -21.710212]),+                pl(67.109448, (19.772647, 4.666939), (10.9615, 19.585621), &[1.258142, -17.972842]),+                pl(333.178019, (15.27065, 10.074253), (18.972555, 9.592865), &[0.802463, -38.461173]),+                pl(211.651273, (15.986778, 9.712166), (15.66439, 11.156058), &[0.690021, -33.071973]),+                pl(280.080598, (15.27065, 10.074253), (19.854913, 10.465822), &[0.735529, -35.253116]),+                pl(31.651273, (0.612588, 17.042707), (15.66439, 11.156058), &[0.933964, -32.82803]),+                pl(153.178019, (1.407632, 17.532803), (18.972555, 9.592865), &[1.086157, -38.177478]),+                pl(100.080598, (0.612588, 17.042707), (19.854913, 10.465822), &[0.995561, -34.993085]),+                pl(307.874984, (8.960817, 5.655419), (13.052281, 16.781504), &[1.057289, -21.387109]),+                pl(67.109448, (9.60993, 4.820846), (10.9615, 19.585621), &[0.905914, -18.325069]),+                pl(0.0, (8.960817, 5.655419), (7.480315, 17.716535), &[1.001488, -20.258355]),+                pl(0.0, (19.206725, 14.739017), (7.480315, 17.716535), &[0.655568, -20.604274]),+                pl(127.874984, (19.862293, 14.739017), (13.052281, 16.781504), &[0.692095, -21.752303]),+                pl(67.109448, (19.206725, 14.739017), (10.9615, 19.585621), &[0.593007, -18.637977]),+                pl(211.651273, (13.119025, 15.340647), (15.66439, 11.156058), &[1.167, -32.594994]),+                pl(307.874984, (12.125607, 14.728266), (13.052281, 16.781504), &[0.775802, -21.668596]),+                pl(247.109448, (13.119025, 15.340647), (10.9615, 19.585621), &[1.329457, -17.901526]),+                pl(31.651273, (3.773733, 12.39632), (15.66439, 11.156058), &[0.651956, -33.110039]),+                pl(153.178019, (4.328715, 12.738433), (18.972555, 9.592865), &[0.758194, -38.505441]),+                pl(100.080598, (3.773733, 12.39632), (19.854913, 10.465822), &[0.694953, -35.293692]),+                pl(31.651273, (16.206885, 12.356449), (15.66439, 11.156058), &[1.083079, -32.678915]),+                pl(153.178019, (17.128864, 12.924792), (18.972555, 9.592865), &[1.259571, -38.004065]),+                pl(100.080598, (16.206885, 12.356449), (19.854913, 10.465822), &[1.15451, -34.834135]),+                pl(67.109448, (12.849427, 5.722968), (10.9615, 19.585621), &[0.853249, -18.377735]),+                pl(211.651273, (13.181317, 6.509024), (15.66439, 11.156058), &[0.998645, -32.763349]),+                pl(153.178019, (12.849427, 5.722968), (18.972555, 9.592865), &[0.580689, -38.682947]),+                pl(+                    19.510799,+                    (1.403172, 0.497187),+                    (33.006905, 7.100458),+                    &[+                        0.0, -2.71837, 0.0, -2.122911, 0.0, -2.390294, 0.0, -3.002194, 0.0, -1.894831, 0.0, -4.06954, 0.0, -1.559613, 0.0, -2.89308,+                        0.0, -2.040845, 0.0, -3.324317, 0.0, -5.628361, 0.0, -2.660667, 0.0, -2.096617, 0.0, -7.476064, 0.0, -3.29102, 0.0,+                        -2.316796, 0.0, -1.70561, 0.0, -1.854847,+                    ],+                ),+                pl(+                    144.027613,+                    (-6.861953, 4.98045),+                    (38.763978, 6.243966),+                    &[+                        0.0, -4.052168, 0.0, -1.36175, 0.0, -9.967512, 0.0, -1.280637, 0.0, -4.170196, 0.0, -1.41432, 0.0, -2.367455, 0.0, -1.486285,+                        0.0, -4.62738, 0.0, -4.002436, 0.0, -2.663116, 0.0, -1.934293, 0.0, -1.991448, 0.0, -2.637094, 0.0, -1.58119, 0.0, -2.780019,+                        0.0, -2.049279, 0.0, -9.955783,+                    ],+                ),+                pl(+                    50.572198,+                    (2.533777, 3.081621),+                    (18.435492, 5.473881),+                    &[+                        0.0, -3.332314, 0.0, -2.666002, 0.0, -3.562468, 0.0, -4.494214, 0.0, -5.221485, 0.0, -3.224885, 0.0, -3.111831, 0.0,+                        -3.642425, 0.0, -7.151893, 0.0, -3.462364, 0.0, -3.165007, 0.0, -3.544732, 0.0, -1.308374, 0.0, -2.351488, 0.0, -5.583112,+                        0.0, -5.037525, 0.0, -7.948599,+                    ],+                ),+                pl(+                    162.531977,+                    (-3.708719, 1.167079),+                    (20.279437, 6.381641),+                    &[+                        0.0, -1.113427, 0.0, -2.237281, 0.0, -1.527796, 0.0, -6.108525, 0.0, -6.203357, 0.0, -4.227832, 0.0, -1.593644, 0.0,+                        -4.181987, 0.0, -3.165928, 0.0, -2.379037, 0.0, -1.551491, 0.0, -6.066391, 0.0, -2.645402, 0.0, -4.557707, 0.0, -2.419574,+                        0.0, -1.646997, 0.0, -7.394611,+                    ],+                ),+            ],+        },         // Drawn at the scale of the pattern CAD programs ship under this name (about 16x         // coarser than first drawn here: AR-CONC hatches came out hundreds of times too dense).         scaled_pattern(diff --git a/crates/render/src/hatch.rs b/crates/render/src/hatch.rs--- a/crates/render/src/hatch.rs+++ b/crates/render/src/hatch.rs@@ -415,18 +415,24 @@ pub(crate) fn pattern_lines_until( const SWATCH_REPEATS: f64 = 6.0; /// Most runs a swatch holds. const MAX_SWATCH_RUNS: usize = 4000;+/// Runs a swatch over [`MAX_SWATCH_RUNS`] zooms in to: about one tile of AR-CONC, its stones a+/// few points across in a picker's swatch.+const ZOOMED_SWATCH_RUNS: usize = 125;+/// Most times a swatch zooms in (twice the scale each time).+const SWATCH_ZOOMS: usize = 6;  /// A preview of a pattern for a pattern picker: the runs of its line families `lines` inside the /// `side` × `side` square with a corner at the origin, at a scale that shows a few repeats of the-/// pattern (each family's line spacing or dash repeat, whichever is longer). Empty for a pattern-/// with no lines (SOLID).+/// pattern (each family's line spacing or dash repeat, whichever is longer). A swatch that would+/// hold more than [`MAX_SWATCH_RUNS`] runs zooms in to [`ZOOMED_SWATCH_RUNS`] or fewer, so every+/// family shows at a readable size. Empty for a pattern with no lines (SOLID). pub fn pattern_swatch(lines: &[PatternLine], side: f64) -> Vec<Vec<Vec2>> {     let repeat =         lines.iter().map(|l| l.delta.1.abs().max(l.dashes.iter().map(|d| d.abs()).sum::<f64>())).filter(|r| r.is_finite()).fold(0.0, f64::max);     if !side.is_finite() || side <= 0.0 || repeat < 1e-9 {         return Vec::new();     }-    let h = Hatch {+    let mut h = Hatch {         pattern: String::new(),         solid: false,         loops: Vec::new(),@@ -440,8 +446,18 @@ pub fn pattern_swatch(lines: &[PatternLine], side: f64) -> Vec<Vec<Vec2>> {         background: None,         pattern_lines: lines.to_vec(),     };-    let square = vec![Vec2::new(0.0, 0.0), Vec2::new(side, 0.0), Vec2::new(side, side), Vec2::new(0.0, side)];-    let mut runs = pattern_lines(&Drawing::default(), &h, &[square], 0.0, None);+    let square = [vec![Vec2::new(0.0, 0.0), Vec2::new(side, 0.0), Vec2::new(side, side), Vec2::new(0.0, side)]];+    let mut runs = pattern_lines(&Drawing::default(), &h, &square, 0.0, None);+    // A pattern whose dash repeats are mostly gaps (AR-CONC) spans many tiles at that scale: cut+    // at the cap, its last families would not show at all, and its marks would be specks.+    let target = if runs.len() > MAX_SWATCH_RUNS { ZOOMED_SWATCH_RUNS } else { MAX_SWATCH_RUNS };+    for _ in 0..SWATCH_ZOOMS {+        if runs.len() <= target {+            break;+        }+        h.scale *= 2.0;+        runs = pattern_lines(&Drawing::default(), &h, &square, 0.0, None);+    }     runs.truncate(MAX_SWATCH_RUNS);     runs }diff --git a/crates/render/src/tests.rs b/crates/render/src/tests.rs--- a/crates/render/src/tests.rs+++ b/crates/render/src/tests.rs@@ -511,6 +511,35 @@ fn scattered_dot_pattern_reads_solid_once_dots_fill_the_pixels() {     assert!(!crate::hatch::reads_solid(&cadcraft_doc::Drawing::new_imperial(), &h, 0.125)); } +/// AR-CONC's stones are closed triangles wherever they repeat: away from the boundary every side's+/// two ends meet the ends of the stone's other two sides. Its dots stand alone.+#[test]+fn ar_conc_stones_are_closed_triangles() {+    let d = cadcraft_doc::Drawing::new_imperial();+    let h = pattern_hatch("AR-CONC", 1.0, (0.0, 0.0, 60.0, 60.0));+    let runs = crate::hatch::pattern_lines(&d, &h, &loops_of(&h), 0.0, None);+    let inside = |p: Vec2| p.x > 2.0 && p.x < 58.0 && p.y > 2.0 && p.y < 58.0;+    let sides: Vec<[Vec2; 2]> = runs+        .iter()+        .filter_map(|r| match r.as_slice() {+            [a, b] if inside(*a) && inside(*b) => Some([*a, *b]),+            _ => None,+        })+        .collect();+    let dots = runs.iter().filter(|r| r.len() == 1).count();+    // 56² in² is about 8 tiles of 20 stones and 70 dots.+    assert!(sides.len() > 3 * 100, "{} sides", sides.len());+    assert!(dots > 400, "{dots} dots");+    // A stone's sides are under 1.4 (34 mm): those 4 from the boundary have both neighbours.+    let deep = |p: &Vec2| p.x > 4.0 && p.x < 56.0 && p.y > 4.0 && p.y < 56.0;+    for (i, s) in sides.iter().enumerate().filter(|(_, s)| s.iter().all(deep)) {+        for end in s {+            let meets = sides.iter().enumerate().filter(|(j, o)| *j != i && o.iter().any(|q| q.near(*end, 1e-4))).count();+            assert_eq!(meets, 1, "side {i} {s:?}: the end {end:?} meets {meets} other sides");+        }+    }+}+ /// Same polylines up to float noise: the view-limited path rotates into the family frame and back, /// so a start point can differ by ~1e-16 between platforms (#804). Structure must match exactly. fn assert_lines_close(a: &[Vec<Vec2>], b: &[Vec<Vec2>], what: &str) {diff --git a/crates/render/tests/it/hatch_fill.rs b/crates/render/tests/it/hatch_fill.rs--- a/crates/render/tests/it/hatch_fill.rs+++ b/crates/render/tests/it/hatch_fill.rs@@ -182,6 +182,19 @@ fn pattern_swatches_stay_in_their_square_and_show_a_few_repeats() {     assert!(cadcraft_render::pattern_swatch(&[], 1.0).is_empty()); } +#[test]+fn a_swatch_mostly_made_of_gaps_shows_every_family() {+    // AR-CONC's dash repeats are mostly gaps: at the usual scale its swatch would pass the cap and+    // lose its last families (the dots), leaving open stones.+    let Some(p) = cadcraft_doc::library::pattern("AR-CONC") else { panic!("AR-CONC") };+    let runs = cadcraft_render::pattern_swatch(&p.lines, 1.0);+    // Zoomed in to about one tile, so the stones read at a swatch's size.+    assert!(runs.len() <= 125, "{} runs", runs.len());+    assert!(runs.iter().any(|r| r.len() == 1), "no dots");+    // Zoomed in only that far: stones (three sides each) still show.+    assert!(runs.iter().filter(|r| r.len() == 2).count() >= 15, "too few stone sides");+}+ #[test] fn brick_joints_are_drawn_once_half_a_brick_apart_course_to_course() {     let l = draw(hatch("BRICK", vec![square(0.0, 2.0)]), "0");