nx_csg_test.nx source
↩ module page · 123 lines · 5824 B
1// nx_csg_test.nx -- KAT gate for SDF + marching-tetrahedra CSG booleans.
2// Proves: (a) the difference actually REMOVES material (fewer inside samples than
3// the box alone), (b) the result is a valid mesh with the box's extent, (c) it
4// slices WATERTIGHT and a mid cut shows >=2 closed loops (outer wall + the hole =
5// the topological signature of a through-hole), (d) the CSG solid prints (real
6// G-code). This is the CSG-booleans rung moving from BEHIND toward parity.
7// expect_exit: 0.
8
9import "nx_syscalls.nx"
10import "nx_mesh.nx"
11import "nx_mesh_edit.nx"
12import "nx_sdf.nx"
13import "nx_csg.nx"
14import "nx_bvh.nx"
15import "nx_slice_plane.nx"
16import "nx_slice_contour.nx"
17import "nx_stl_write.nx"
18import "nx_stl.nx"
19import "nx_machine_graph.nx"
20import "nx_material_profile.nx"
21import "nx_gcode_emit.nx"
22import "nx_slice_pipeline.nx"
23
24func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
25func wn(v: i64) -> i64 {
26 let b: *u8 = sys_mmap(28); var m: i64 = v
27 if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) }
28 let t: *u8 = sys_mmap(28); var k: i64 = 0
29 if m == 0 { t[0] = 48 as u8; k = 1 }
30 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
31 var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 }
32 sys_write(1, b, k); return 0
33}
34func smoke_count(buf: *u8, len: i64, needle: *u8) -> i64 {
35 var nlen: i64 = 0; while needle[nlen] != 0 { nlen = nlen + 1 }
36 if len < nlen { return 0 }
37 var count: i64 = 0; var i: i64 = 0; let last: i64 = len - nlen
38 while i <= last {
39 var j: i64 = 0; var matched: i64 = 1
40 while j < nlen { if buf[i + j] != needle[j] { matched = 0; j = nlen } j = j + 1 }
41 if matched == 1 { count = count + 1 }
42 i = i + 1
43 }
44 return count
45}
46func chk(cond: i64, pass: *i64, fail: *i64, label: *u8) -> i64 {
47 if cond == 1 { pass[0] = pass[0] + 1; w(" ok " as *u8); w(label); w("\n" as *u8) }
48 else { fail[0] = fail[0] + 1; w(" XX " as *u8); w(label); w("\n" as *u8) }
49 return 0
50}
51func eqi(a: i64, b: i64) -> i64 { if a == b { return 1 } return 0 }
52
53func main() -> i64 {
54 let pass: *i64 = (sys_mmap(8)) as *i64
55 let fail: *i64 = (sys_mmap(8)) as *i64
56 pass[0] = 0
57 fail[0] = 0
58 let bb: *i64 = (sys_mmap(48)) as *i64
59 let H: i64 = 163840 // 10mm box half-extent (20mm cube)
60 let R: i64 = 81920 // 5mm hole radius
61 w("=== nx_csg KAT (SDF + marching-tetrahedra booleans) ===\n" as *u8)
62
63 // --- (a) the difference REMOVES material: sample-count box vs box-minus-cyl ---
64 let boxp: *NxSdfPrim = nx_sdf_make(NX_SDF_BOX, 0, 0, 0, H, H, H)
65 let cylp: *NxSdfPrim = nx_sdf_make(NX_SDF_CYL, 0, 0, 0, R, H + 16384, 0)
66 var nbox: i64 = 0
67 var ndiff: i64 = 0
68 var gz: i64 = 0 - H
69 while gz <= H {
70 var gy: i64 = 0 - H
71 while gy <= H {
72 var gx: i64 = 0 - H
73 while gx <= H {
74 if nx_sdf_eval(boxp, gx, gy, gz) < 0 { nbox = nbox + 1 }
75 if nx_csg_field(boxp, cylp, NX_CSG_DIFF, gx, gy, gz) < 0 { ndiff = ndiff + 1 }
76 gx = gx + 16384
77 }
78 gy = gy + 16384
79 }
80 gz = gz + 16384
81 }
82 w(" inside samples: box=" as *u8); wn(nbox); w(" box-cyl=" as *u8); wn(ndiff); w("\n" as *u8)
83 chk(ndiff < nbox, pass, fail, "difference REMOVED material (box-cyl < box)" as *u8)
84 chk(ndiff > 0, pass, fail, "solid is non-empty after subtraction" as *u8)
85
86 // --- (b) build the CSG mesh ---
87 let m: *NxMesh = nx_csg_box_minus_cyl(H, H, H, R, 24)
88 w(" CSG mesh tris=" as *u8); wn(m.n_tris); w(" verts=" as *u8); wn(m.n_verts); w("\n" as *u8)
89 chk(m.n_tris > 100, pass, fail, "CSG produced a substantial mesh" as *u8)
90 chk(eqi(nx_mesh_validate(m), NX_MESH_OK), pass, fail, "CSG mesh valid (indices in range)" as *u8)
91 nx_mesh_aabb(m, bb)
92 w(" AABB x[" as *u8); wn(bb[0]); w("," as *u8); wn(bb[3]); w("]\n" as *u8)
93 chk(bb[3] >= 160000, pass, fail, "CSG mesh keeps box extent (max_x ~ +10mm)" as *u8)
94 chk(bb[3] <= 167000, pass, fail, "CSG mesh not over-grown past box face" as *u8)
95
96 // --- (c) watertight + the hole is real (mid slice has >=2 closed loops) ---
97 nx_mesh_translate(m, 0, 0, H) // sit on bed (z 0..2H)
98 let bvh: *NxBvh = nx_bvh_build(m)
99 let soup: *NxSliceSoup = nx_slice_plane(m, bvh, H + 7000) // mid height, off the grid planes
100 let c: *NxSliceContours = nx_slice_contour_build(soup)
101 w(" mid slice: polys=" as *u8); wn(c.n_polys); w(" open=" as *u8); wn(c.n_open); w("\n" as *u8)
102 chk(eqi(c.n_open, 0), pass, fail, "CSG solid slices WATERTIGHT (n_open=0)" as *u8)
103 chk(c.n_polys >= 2, pass, fail, "through-hole present: mid slice has >=2 closed loops" as *u8)
104
105 // --- (d) the CSG solid PRINTS (real G-code) ---
106 let stl: *u8 = sys_mmap(1048576)
107 let n: i64 = nx_stl_write_mesh(stl, m)
108 let r: *NxStlResult = nx_stl_load_binary(stl, n)
109 chk(eqi(r.verdict, NX_STL_OK), pass, fail, "CSG STL round-trips through reader" as *u8)
110 let qidi: *NxMachineGraph = nx_machine_graph_qidi_xmax3()
111 let pla: *NxMaterialProfile = nx_material_profile_generic_pla()
112 let e: *NxGcodeEmitter = nx_gemit_new(qidi, pla, 3277, 6554, 4194304)
113 nx_gemit_preamble(e)
114 let nl: i64 = nx_slice_pipe_run_v2(e, r.mesh, 20, 3277)
115 let g1: i64 = smoke_count(e.buf, e.len, "G1 X" as *u8)
116 w(" CSG->print: layers=" as *u8); wn(nl); w(" G1=" as *u8); wn(g1); w(" bytes=" as *u8); wn(e.len); w("\n" as *u8)
117 chk(nl >= 50, pass, fail, "CSG solid sliced >= 50 layers" as *u8)
118 chk(g1 >= 1000, pass, fail, "CSG solid: many real extrusion moves" as *u8)
119
120 w("=== VERDICT pass=" as *u8); wn(pass[0]); w(" fail=" as *u8); wn(fail[0]); w(" ===\n" as *u8)
121 if fail[0] == 0 { return 0 }
122 return 1
123}