code wiki / (root) / nx_stl_to_gcode_real_test.nx

nx_stl_to_gcode_real_test.nx source

↩ module page · 186 lines · 6330 B

1// nx_stl_to_gcode_real_test.nx -- HARD REAL TEST end-to-end: 2// hand-constructed binary STL bytes -> nx_stl_load_binary -> 3// nx_slice_pipe_run_v2 -> G-code text with supports + perimeters. 4// 5// Operator directive 2026-05-20: "lets get to some hard real testing" 6// 7// Earlier hard real test (nx_slice_pipe_v2_real) used the in-memory 8// nx_mesh_make_tetrahedron helper -- already pre-converted Q14 9// vertices, no STL bytes touched. THIS test exercises the COMPLETE 10// real-byte path: 11// 1. Hand-build binary STL byte stream for a 10mm tetrahedron 12// 2. Pass raw bytes through nx_stl_load_binary (real parser, real 13// IEEE 754 float32 -> Q14 conversion via nx_fp32_bytes_to_q14) 14// 3. Feed the parsed mesh into nx_slice_pipe_run_v2 15// 4. Assert structural properties of emitted G-code 16// 17// This proves: external-format bytes -> substrate slicer -> G-code, 18// the workflow an operator would actually use loading a Christus STL. 19// 20// expect_exit: 0 21// license_tier: ORIGINAL 22 23import "nx_syscalls.nx" 24import "nx_mesh.nx" 25import "nx_stl.nx" 26import "nx_machine_graph.nx" 27import "nx_material_profile.nx" 28import "nx_gcode_emit.nx" 29import "nx_slice_pipeline.nx" 30 31const Q14: i64 = 16384 32 33// IEEE 754 binary32 bit patterns: 34const F32_ZERO: i64 = 0x00000000 // +0.0 35const F32_TEN: i64 = 0x41200000 // +10.0 (sign 0, exp 130, mant 0x200000) 36 37// Write a binary32 bit pattern as 4 little-endian bytes at buf[off]. 38func put_f32(buf: *u8, off: i64, bits32: i64) -> i64 { 39 buf[off + 0] = (bits32 & 0xff) as u8 40 buf[off + 1] = ((bits32 >> 8) & 0xff) as u8 41 buf[off + 2] = ((bits32 >> 16) & 0xff) as u8 42 buf[off + 3] = ((bits32 >> 24) & 0xff) as u8 43 return 0 44} 45 46// Write one triangle's 50-byte record: 12-byte normal (zeros) + 9 47// float32 vertex coords + 2 zero attribute bytes. 48func put_tri(buf: *u8, off: i64, 49 x0: i64, y0: i64, z0: i64, 50 x1: i64, y1: i64, z1: i64, 51 x2: i64, y2: i64, z2: i64) -> i64 { 52 put_f32(buf, off + 0, 0) 53 put_f32(buf, off + 4, 0) 54 put_f32(buf, off + 8, 0) 55 put_f32(buf, off + 12, x0) 56 put_f32(buf, off + 16, y0) 57 put_f32(buf, off + 20, z0) 58 put_f32(buf, off + 24, x1) 59 put_f32(buf, off + 28, y1) 60 put_f32(buf, off + 32, z1) 61 put_f32(buf, off + 36, x2) 62 put_f32(buf, off + 40, y2) 63 put_f32(buf, off + 44, z2) 64 buf[off + 48] = 0 65 buf[off + 49] = 0 66 return 0 67} 68 69func smoke_count(buf: *u8, len: i64, needle: *u8) -> i64 { 70 var nlen: i64 = 0 71 while needle[nlen] != 0 { nlen = nlen + 1 } 72 if len < nlen { return 0 } 73 var count: i64 = 0 74 var i: i64 = 0 75 let last: i64 = len - nlen 76 while i <= last { 77 var j: i64 = 0 78 var matched: i64 = 1 79 while j < nlen { 80 if buf[i + j] != needle[j] { matched = 0; j = nlen } 81 j = j + 1 82 } 83 if matched == 1 { count = count + 1 } 84 i = i + 1 85 } 86 return count 87} 88 89func main() -> i64 { 90 // ===== Hand-build binary STL: 10mm tetrahedron ===== 91 // 92 // Vertices in real-millimetre coords: 93 // v0 = (0, 0, 0) base-corner 94 // v1 = (10, 0, 0) base-corner 95 // v2 = (0, 10, 0) base-corner 96 // v3 = (0, 0, 10) apex (top) 97 // 98 // 4 triangles (CCW outward winding): 99 // T0: v1-v2-v3 (apex face -- slanted, the steep overhang) 100 // T1: v0-v3-v2 (x=0 face) 101 // T2: v0-v1-v3 (y=0 face) 102 // T3: v0-v2-v1 (z=0 base face on the bed) 103 104 let stl: *u8 = sys_mmap(512) 105 // 80-byte header already zeroed by mmap. 106 // 4-byte little-endian n_tris = 4 107 stl[80] = 4 108 109 put_tri(stl, 84 + 0 * 50, 110 F32_TEN, F32_ZERO, F32_ZERO, 111 F32_ZERO, F32_TEN, F32_ZERO, 112 F32_ZERO, F32_ZERO, F32_TEN) 113 put_tri(stl, 84 + 1 * 50, 114 F32_ZERO, F32_ZERO, F32_ZERO, 115 F32_ZERO, F32_ZERO, F32_TEN, 116 F32_ZERO, F32_TEN, F32_ZERO) 117 put_tri(stl, 84 + 2 * 50, 118 F32_ZERO, F32_ZERO, F32_ZERO, 119 F32_TEN, F32_ZERO, F32_ZERO, 120 F32_ZERO, F32_ZERO, F32_TEN) 121 put_tri(stl, 84 + 3 * 50, 122 F32_ZERO, F32_ZERO, F32_ZERO, 123 F32_ZERO, F32_TEN, F32_ZERO, 124 F32_TEN, F32_ZERO, F32_ZERO) 125 126 let stl_len: i64 = 84 + 4 * 50 // 284 bytes 127 128 // ===== Parse the real STL bytes via the shipped binary loader ===== 129 130 let r: *NxStlResult = nx_stl_load_binary(stl, stl_len) 131 if r.verdict != NX_STL_OK { return 10 } 132 if r.n_tris_header != 4 { return 11 } 133 if r.n_verts_unique != 4 { return 12 } 134 if (r.mesh as i64) == 0 { return 13 } 135 136 let mesh: *NxMesh = r.mesh 137 if mesh.n_tris != 4 { return 14 } 138 if mesh.n_verts != 4 { return 15 } 139 140 // Verify the parsed vertex Q14 magnitudes (10mm = 10 * Q14 = 163840). 141 // Pick vertex 3 (apex at (0,0,10)). Vertex order is dedup-determined; 142 // assert at least ONE vertex reaches Q14*10 in Z. 143 var any_apex: i64 = 0 144 var vi: i64 = 0 145 while vi < mesh.n_verts { 146 let z: i64 = nx_mesh_get_vertex_z(mesh, vi) 147 if z >= 9 * Q14 { 148 if z <= 11 * Q14 { any_apex = 1 } 149 } 150 vi = vi + 1 151 } 152 if any_apex != 1 { return 20 } 153 154 // ===== Slice the loaded mesh ===== 155 156 let qidi: *NxMachineGraph = nx_machine_graph_qidi_xmax3() 157 let pla: *NxMaterialProfile = nx_material_profile_generic_pla() 158 let lh: i64 = 3277 // 0.2 mm 159 let lw: i64 = 6554 // 0.4 mm 160 161 let e: *NxGcodeEmitter = nx_gemit_new(qidi, pla, lh, lw, 1048576) 162 if (e as i64) == 0 { return 30 } 163 164 nx_gemit_preamble(e) 165 let n_layers: i64 = nx_slice_pipe_run_v2(e, mesh, 20, lh) 166 if n_layers <= 0 { return 40 } 167 168 // ===== Hard real assertions on emitted G-code ===== 169 if n_layers < 30 { return 50 } 170 if n_layers > 200 { return 51 } 171 172 if smoke_count(e.buf, e.len, "M104 S") < 1 { return 60 } 173 if smoke_count(e.buf, e.len, "M84") < 1 { return 61 } 174 if smoke_count(e.buf, e.len, ";SKIRT") < 1 { return 62 } 175 if smoke_count(e.buf, e.len, ";SUPPORT_START") < 1 { return 63 } 176 if smoke_count(e.buf, e.len, ";SUPPORT_END") < 1 { return 64 } 177 178 let n_g1: i64 = smoke_count(e.buf, e.len, "G1 X") 179 if n_g1 < 500 { return 70 } 180 181 // Sensible byte count 182 if e.len < 30000 { return 80 } 183 if e.len > 900000 { return 81 } 184 185 return 0 186}