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}