code wiki / _hdl_build / nx_fab_slice.nx

nx_fab_slice.nx source

↩ module page · 155 lines · 6774 B

1// nx_fab_slice.nx -- L0 FAB SUBSTRATE, rung 1 of the electronics-fab arc (hardware layer up): 2// binary STL in -> fixed-point micrometer geometry -> z-plane slice -> G-code out. This is the team's 3// first machine-DRIVER capability (charter layer-0: deterministic + a machine driver -> real outcome): 4// G-code is the wire format every FDM printer motion controller (Marlin/Klipper class) consumes. 5// LAWS HONORED: struct-free (NxDirent law), integer-only fixed point (1 unit = 1 micrometer, so 1.000mm 6// = 1000), no variable shifts (pow2 by loop), exact-integer KATs in nx_fab_slice_test.nx. 7// HONEST SCOPE: slices ONE triangle per call + emits a single polyline; contour chaining across a full 8// mesh, perimeter offsetting, infill, and the C-oracle differential are the documented NEXT rungs -- 9// flagged, not faked. license_tier: ORIGINAL 10import "nx_syscalls.nx" 11const FAB_MAGIC_65536: i64 = 65536 12const FAB_MAGIC_16777216: i64 = 16777216 13const FAB_MAGIC_2147483648: i64 = 2147483648 14const FAB_MAGIC_8388608: i64 = 8388608 15 16// f32 decode verdicts/clamps (structured, not magic ints buried in logic) 17const FAB_UM_BAD: i64 = -888888888888888 // NaN/Inf input -> caller must reject (defensive at the boundary) 18const FAB_UM_MAX: i64 = 888888888888888 // clamp for absurd-magnitude floats (no silent overflow) 19 20// 2^k by loop -- the no-variable-shift law 21func fab_pow2(k: i64) -> i64 { var p: i64 = 1; var i: i64 = 0; while i < k { p = p * 2; i = i + 1 } return p } 22 23// little-endian u32 read (STL is LE by spec) 24func fab_u32le(b: *u8, off: i64) -> i64 { 25 var v: i64 = b[off] as i64 26 v = v + (b[off+1] as i64) * 256 27 v = v + (b[off+2] as i64) * FAB_MAGIC_65536 28 v = v + (b[off+3] as i64) * FAB_MAGIC_16777216 29 return v 30} 31 32// signed division, round-to-nearest, half away from zero -- the slice interpolator's exactness rule 33func fab_div_round(num: i64, den: i64) -> i64 { 34 var n: i64 = num; var d: i64 = den; var sg: i64 = 1 35 if n < 0 { n = 0 - n; sg = 0 - sg } 36 if d < 0 { d = 0 - d; sg = 0 - sg } 37 let q: i64 = (n + d/2) / d 38 return sg * q 39} 40 41// IEEE-754 binary32 bit pattern -> micrometers (value_mm * 1000), round-to-nearest. 42// Integer-only: value = (-1)^s * (m|2^23) * 2^(e-150); um = that * 1000. 43func ff32_to_um(bits: i64) -> i64 { 44 let s: i64 = (bits / FAB_MAGIC_2147483648) % 2 45 let e: i64 = (bits / FAB_MAGIC_8388608) % 256 46 let m: i64 = bits % FAB_MAGIC_8388608 47 if e == 255 { return FAB_UM_BAD } // NaN/Inf -> reject at the boundary 48 if e == 0 { return 0 } // zero + denormals (< 1.2e-38 mm) -> 0 um 49 let p1000: i64 = (m + FAB_MAGIC_8388608) * 1000 // (mantissa with implicit bit) * scale; < 2^34 50 let sh: i64 = e - 150 51 var v: i64 = 0 52 if sh >= 0 { 53 if sh > 28 { v = FAB_UM_MAX } // p1000*2^sh would pass 2^62 -> clamp, never wrap 54 if sh <= 28 { v = p1000 * fab_pow2(sh) } 55 } 56 if sh < 0 { 57 let k: i64 = 0 - sh 58 if k > 40 { v = 0 } // below half a micrometer for any mantissa 59 if k <= 40 { let d: i64 = fab_pow2(k); v = (p1000 + d/2) / d } 60 } 61 if s == 1 { v = 0 - v } 62 return v 63} 64 65// ---- binary STL (80-byte header, u32 tri count, 50 bytes/tri: 12B normal + 3 verts * 12B + u16 attr) ---- 66 67func fstl_tri_count(b: *u8) -> i64 { return fab_u32le(b, 80) } 68 69// vertex coordinate in micrometers: triangle t, vertex v (0..2), axis ax (0=x,1=y,2=z) 70func fstl_vert_um(b: *u8, t: i64, v: i64, ax: i64) -> i64 { 71 let off: i64 = 84 + t*50 + 12 + v*12 + ax*4 72 return ff32_to_um(fab_u32le(b, off)) 73} 74 75// load triangle t's 9 coordinates (x0 y0 z0 x1 y1 z1 x2 y2 z2, micrometers) into out 76func fstl_tri_um(b: *u8, t: i64, out: *i64) -> i64 { 77 var v: i64 = 0 78 while v < 3 { 79 var ax: i64 = 0 80 while ax < 3 { out[v*3+ax] = fstl_vert_um(b, t, v, ax); ax = ax + 1 } 81 v = v + 1 82 } 83 return 9 84} 85 86// ---- slice: triangle x z-plane -> 0 or 2 intersection points ---- 87// tri = 9 coords as above; zc = cut height (um); out receives x,y pairs (um). 88// Edge-crossing convention is HALF-OPEN (za <= zc < zb or zb <= zc < za) so a vertex exactly on the 89// plane is counted on exactly one of its two edges -- no double-hit, no miss. A face lying flat in the 90// plane yields 0 (degenerate by convention). Returns the number of points written (0 or 2). 91func fslice_tri(tri: *i64, zc: i64, out: *i64) -> i64 { 92 var np: i64 = 0 93 var i: i64 = 0 94 while i < 3 { 95 let j: i64 = (i + 1) % 3 96 let za: i64 = tri[i*3+2] 97 let zb: i64 = tri[j*3+2] 98 var cross: i64 = 0 99 if za <= zc { if zb > zc { cross = 1 } } 100 if zb <= zc { if za > zc { cross = 1 } } 101 if cross == 1 { 102 let xa: i64 = tri[i*3]; let ya: i64 = tri[i*3+1] 103 let xb: i64 = tri[j*3]; let yb: i64 = tri[j*3+1] 104 out[np*2] = xa + fab_div_round((xb - xa) * (zc - za), zb - za) 105 out[np*2+1] = ya + fab_div_round((yb - ya) * (zc - za), zb - za) 106 np = np + 1 107 } 108 i = i + 1 109 } 110 return np 111} 112 113// ---- G-code emit: polyline of n points (um pairs) -> "G0 X..mm Y..mm" then "G1 ..." moves ---- 114 115func fgc_puts(out: *u8, off: i64, s: *u8) -> i64 { 116 var i: i64 = 0 117 while s[i] != (0 as u8) { out[off+i] = s[i]; i = i + 1 } 118 return off + i 119} 120 121// micrometers -> "mmm.fff" decimal millimeters (3 fixed decimals = full um precision, the G-code idiom) 122func fgc_putd(out: *u8, o: i64, vin: i64) -> i64 { 123 var off: i64 = o 124 var v: i64 = vin 125 if v < 0 { out[off] = 45 as u8; off = off + 1; v = 0 - v } 126 let mm: i64 = v / 1000 127 let fr: i64 = v % 1000 128 let t: *u8 = sys_mmap(24); var k: i64 = 0; var m: i64 = mm 129 if m == 0 { t[0] = 48 as u8; k = 1 } 130 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 131 var i: i64 = 0 132 while i < k { out[off] = t[k-1-i]; off = off + 1; i = i + 1 } 133 out[off] = 46 as u8; off = off + 1 134 out[off] = (48 + (fr / 100) % 10) as u8; off = off + 1 135 out[off] = (48 + (fr / 10) % 10) as u8; off = off + 1 136 out[off] = (48 + fr % 10) as u8; off = off + 1 137 return off 138} 139 140// emit the polyline; returns byte length written. First point = travel (G0), rest = moves (G1). 141// Extrusion (E) and feed (F) words are the toolpath rung's job -- geometry first, honestly scoped. 142func fgcode_emit(pts: *i64, n: i64, out: *u8) -> i64 { 143 var off: i64 = 0 144 var i: i64 = 0 145 while i < n { 146 if i == 0 { off = fgc_puts(out, off, "G0 X" as *u8) } 147 if i > 0 { off = fgc_puts(out, off, "G1 X" as *u8) } 148 off = fgc_putd(out, off, pts[i*2]) 149 off = fgc_puts(out, off, " Y" as *u8) 150 off = fgc_putd(out, off, pts[i*2+1]) 151 out[off] = 10 as u8; off = off + 1 152 i = i + 1 153 } 154 return off 155}