code wiki / (root) / nx_infill.nx

nx_infill.nx source

↩ module page · 224 lines · 8590 B

1// nx_infill.nx -- scan-line infill pattern generator for closed 2// NxPolygon contours. Produces line segments that the G-code 3// emitter prints as rectilinear infill inside the perimeter. 4// 5// Algorithm: classical scan-line fill (Foley/van Dam 1990). 6// 1. compute polygon bbox 7// 2. for each horizontal scan line at y = bbox.min_y + k*spacing: 8// a. find x-coord where each polygon edge crosses y 9// (skip horizontal edges; integer cross-multiply form) 10// b. sort x-coords ascending 11// c. pair consecutive crossings; emit segment per pair 12// 13// Spacing derived from line_width + density_pct: 14// spacing_mm = (line_width_mm * 100) / density_pct 15// Density 100% = touching lines, 50% = 50% air, 25% = 75% air. 16// 17// Grid pattern (perpendicular alternation per layer for better 18// mechanical isotropy): when called with vertical=1, the algorithm 19// runs on a transposed (x<->y) coordinate system, producing 20// vertical scan lines. Caller toggles `vertical` per layer index. 21// 22// Reuses NxSliceSoup as output (same flat-segment shape as 23// nx_slice_plane; downstream G-code emitter consumes either). 24// 25// Composes nx_polygon (closed contour input) + nx_qsort_i64 26// (crossing-coord sort) + nx_slice_plane (segment soup output type). 27// 28// Per cardinal NISHI_3D_PRINT_ROADMAP ยง2.x infill: lines pattern v1 29// is the substrate floor. Honeycomb / gyroid / cubic are future 30// patterns queued for after this primitive validates on real prints. 31// Per cardinal feedback-bits-up-exceed-never-match: substrate ships 32// the universal scan-line core; richer patterns layer on top, NOT 33// instead of, this primitive. 34// 35// Pure i64 arithmetic throughout (integer cross-multiply avoids 36// division during the straddle test; one division per crossing for 37// the x-coord computation). 38// 39// license_tier: ORIGINAL 40 41import "nx_syscalls.nx" 42import "nx_polygon.nx" 43import "nx_slice_plane.nx" 44import "nx_qsort.nx" 45 46// ===== sealed-enum patterns ======================================== 47 48const NX_INFILL_PATTERN_LINES: i64 = 0 49const NX_INFILL_PATTERN_GRID: i64 = 1 // alternating dir/layer 50const NX_INFILL_PATTERN_HONEYCOMB: i64 = 2 // queued 51const NX_INFILL_PATTERN_GYROID: i64 = 3 // queued 52 53// ===== verdicts ==================================================== 54 55const NX_INFILL_OK: i64 = 0 56const NX_INFILL_ERR_BAD_POLYGON: i64 = 1 57const NX_INFILL_ERR_BAD_DENSITY: i64 = 2 58const NX_INFILL_ERR_BAD_PATTERN: i64 = 3 59const NX_INFILL_ERR_CAPACITY: i64 = 4 60 61func nx_infill_verdict_name(v: i64) -> *u8 { 62 if v == NX_INFILL_OK { return "OK" } 63 if v == NX_INFILL_ERR_BAD_POLYGON { return "BAD_POLYGON" } 64 if v == NX_INFILL_ERR_BAD_DENSITY { return "BAD_DENSITY" } 65 if v == NX_INFILL_ERR_BAD_PATTERN { return "BAD_PATTERN" } 66 if v == NX_INFILL_ERR_CAPACITY { return "CAPACITY" } 67 return "UNKNOWN" 68} 69 70// ===== scan-line implementation =================================== 71// 72// Given a polygon and a horizontal scan line y, fills `out_xs` with 73// the x-coordinates of crossings (sorted ascending) and returns the 74// crossing count. Skips horizontal polygon edges to avoid div/0. 75// 76// The integer cross-multiply form is used to compare: 77// x_crossing = xi + (y - yi) * (xj - xi) / (yj - yi) 78// The division is only done ONCE per crossing (when we materialize 79// x_crossing for sorting + emit). No floating point. 80 81func nx_infill_scanline_xs(poly: *NxPolygon, y: i64, 82 out_xs: *i64, max_xs: i64) -> i64 { 83 var count: i64 = 0 84 var i: i64 = 0 85 while i < poly.n_verts { 86 var j: i64 = i + 1 87 if j >= poly.n_verts { j = 0 } 88 let xi: i64 = nx_polygon_get_x(poly, i) 89 let yi: i64 = nx_polygon_get_y(poly, i) 90 let xj: i64 = nx_polygon_get_x(poly, j) 91 let yj: i64 = nx_polygon_get_y(poly, j) 92 // Strict straddle: (yi > y) != (yj > y). Horizontal edges 93 // (yi == yj) fall out naturally -- both > y conditions 94 // identical -> NOT a crossing. 95 var yi_above: i64 = 0 96 if yi > y { yi_above = 1 } 97 var yj_above: i64 = 0 98 if yj > y { yj_above = 1 } 99 if yi_above != yj_above { 100 let dy: i64 = yj - yi 101 let dx: i64 = xj - xi 102 // dy != 0 here because yi != yj (otherwise both `_above` 103 // would be identical). Safe to divide. 104 let x_cross: i64 = xi + (y - yi) * dx / dy 105 if count < max_xs { 106 out_xs[count] = x_cross 107 count = count + 1 108 } 109 } 110 i = i + 1 111 } 112 return count 113} 114 115// Transposed variant: same algorithm but swap x<->y. Used when 116// `vertical = 1` to produce vertical scan lines. Returns y-coords 117// of crossings for a given scan x. 118 119func nx_infill_scanline_ys(poly: *NxPolygon, x: i64, 120 out_ys: *i64, max_ys: i64) -> i64 { 121 var count: i64 = 0 122 var i: i64 = 0 123 while i < poly.n_verts { 124 var j: i64 = i + 1 125 if j >= poly.n_verts { j = 0 } 126 let xi: i64 = nx_polygon_get_x(poly, i) 127 let yi: i64 = nx_polygon_get_y(poly, i) 128 let xj: i64 = nx_polygon_get_x(poly, j) 129 let yj: i64 = nx_polygon_get_y(poly, j) 130 var xi_right: i64 = 0 131 if xi > x { xi_right = 1 } 132 var xj_right: i64 = 0 133 if xj > x { xj_right = 1 } 134 if xi_right != xj_right { 135 let dx: i64 = xj - xi 136 let dy: i64 = yj - yi 137 let y_cross: i64 = yi + (x - xi) * dy / dx 138 if count < max_ys { 139 out_ys[count] = y_cross 140 count = count + 1 141 } 142 } 143 i = i + 1 144 } 145 return count 146} 147 148// ===== main entry ================================================= 149// 150// Generates lines / grid infill segments for `poly` at `density_pct` 151// fill density, writing into `out` (which the caller pre-allocates 152// with sufficient capacity). 153// 154// `vertical = 0` -> horizontal scan lines (typical even layer). 155// `vertical = 1` -> vertical scan lines (typical odd layer for grid). 156// 157// `line_width_q14` = the width of the extruded bead, used to derive 158// scan spacing. Caller passes the same value used by the G-code 159// emitter for consistency. 160 161func nx_infill_lines(poly: *NxPolygon, density_pct: i64, 162 line_width_q14: i64, vertical: i64, 163 out: *NxSliceSoup) -> i64 { 164 if (poly as i64) == 0 { return NX_INFILL_ERR_BAD_POLYGON } 165 if poly.n_verts < 3 { return NX_INFILL_ERR_BAD_POLYGON } 166 if density_pct <= 0 { return NX_INFILL_ERR_BAD_DENSITY } 167 if density_pct > 100 { return NX_INFILL_ERR_BAD_DENSITY } 168 if line_width_q14 <= 0 { return NX_INFILL_ERR_BAD_DENSITY } 169 170 let spacing_q14: i64 = line_width_q14 * 100 / density_pct 171 if spacing_q14 <= 0 { return NX_INFILL_ERR_BAD_DENSITY } 172 173 // BBox in Q14 174 let bb: *i64 = (sys_mmap(32)) as *i64 175 if nx_polygon_bbox(poly, bb) != NX_POLYGON_OK { 176 return NX_INFILL_ERR_BAD_POLYGON 177 } 178 let bb_min_x: i64 = bb[0] 179 let bb_min_y: i64 = bb[1] 180 let bb_max_x: i64 = bb[2] 181 let bb_max_y: i64 = bb[3] 182 183 // Per-scanline scratch: at most n_verts crossings on a convex 184 // boundary, more on concave. Allocate n_verts*2 to be safe. 185 let max_xs: i64 = poly.n_verts * 2 186 let xs: *i64 = (sys_mmap(max_xs * 8)) as *i64 187 188 var scan: i64 = 0 189 if vertical == 0 { scan = bb_min_y } 190 if vertical == 1 { scan = bb_min_x } 191 let scan_max: i64 = bb_max_y * (1 - vertical) + bb_max_x * vertical 192 let scan_min: i64 = bb_min_y * (1 - vertical) + bb_min_x * vertical 193 194 while scan <= scan_max { 195 var n_xs: i64 = 0 196 if vertical == 0 { 197 n_xs = nx_infill_scanline_xs(poly, scan, xs, max_xs) 198 } 199 if vertical == 1 { 200 n_xs = nx_infill_scanline_ys(poly, scan, xs, max_xs) 201 } 202 // Sort the crossing coords ascending (in-place i64 sort). 203 if n_xs > 1 { nx_qsort_i64(xs, n_xs) } 204 205 // Pair consecutive crossings into in/out segment endpoints. 206 var k: i64 = 0 207 while k + 1 < n_xs { 208 let a: i64 = xs[k] 209 let b: i64 = xs[k + 1] 210 var rc: i64 = 0 211 if vertical == 0 { 212 rc = nx_slice_soup_emit(out, a, scan, b, scan) 213 } 214 if vertical == 1 { 215 rc = nx_slice_soup_emit(out, scan, a, scan, b) 216 } 217 if rc != 0 { return NX_INFILL_ERR_CAPACITY } 218 k = k + 2 219 } 220 scan = scan + spacing_q14 221 } 222 223 return NX_INFILL_OK 224}