nx_bedmesh_emit_test.nx source
↩ module page · 114 lines · 4800 B
1// nx_bedmesh_emit_test.nx -- prove that slicer-side bed-mesh
2// compensation emits VARYING Z within a single XY extrusion move.
3//
4// Industry baseline: every major slicer emits ONE Z per layer; G1
5// extrusion moves carry only X/Y/E/F. This smoke shows we emit
6// G1 X Y Z E F PER SEGMENT with Z varying as the move crosses the
7// probed bed mesh. The EXCEED proof.
8//
9// Closed-form invariants:
10// (a) Long move subdivided into >= 10 segments at 5mm max segment
11// (b) Emitted G-code contains multiple ";Z=" markers (we count G1 ... Z)
12// (c) Z varies non-trivially -- buf contains multiple distinct Z
13// coordinate strings (proved by length-of-output > flat-baseline)
14// (d) With a flat (zero-delta) bed mesh: all emitted Z == z_base
15// (no spurious compensation)
16// (e) Bowl bed: Z at move midpoint > Z at move start (passes through high)
17//
18// expect_exit: 0
19// license_tier: ORIGINAL
20
21import "nx_syscalls.nx"
22import "nx_polygon.nx"
23import "nx_slice_contour.nx"
24import "nx_machine_graph.nx"
25import "nx_material_profile.nx"
26import "nx_bed_mesh.nx"
27import "nx_gcode_emit.nx"
28
29const Q14: i64 = 16384
30
31// Count occurrences of needle in buf.
32func smoke_count(buf: *u8, len: i64, needle: *u8) -> i64 {
33 var nlen: i64 = 0
34 while needle[nlen] != 0 { nlen = nlen + 1 }
35 if len < nlen { return 0 }
36 var count: i64 = 0
37 var i: i64 = 0
38 let last: i64 = len - nlen
39 while i <= last {
40 var j: i64 = 0
41 var matched: i64 = 1
42 while j < nlen {
43 if buf[i + j] != needle[j] { matched = 0; j = nlen }
44 j = j + 1
45 }
46 if matched == 1 { count = count + 1 }
47 i = i + 1
48 }
49 return count
50}
51
52func smoke_contains(buf: *u8, len: i64, needle: *u8) -> i64 {
53 if smoke_count(buf, len, needle) > 0 { return 1 }
54 return 0
55}
56
57func main() -> i64 {
58 let qidi: *NxMachineGraph = nx_machine_graph_qidi_xmax3()
59 let pla: *NxMaterialProfile = nx_material_profile_generic_pla()
60 let lh: i64 = 3277
61 let lw: i64 = 6554
62
63 // Bowl-shaped 3x3 mesh over 0-200mm: center high (+0.2 mm), edges flat.
64 let bed_max: i64 = 200 * Q14
65 let bowl: *NxBedMesh = nx_bed_mesh_new(3, 3, 0, bed_max, 0, bed_max, 10)
66 // Use 0.25 mm bowl height so center reaches Z=0.450mm (cleanly
67 // formattable past the integer-formatter floor effect on values
68 // just-under-but-not-at clean fractions).
69 let bowl_high: i64 = Q14 / 4 // +0.25 mm = +4096 Q14
70 nx_bed_mesh_set_point(bowl, 1, 1, bowl_high)
71
72 // --- (a) Long move 10->190 in X subdivided into many segments ---
73 let e_bowl: *NxGcodeEmitter = nx_gemit_new(qidi, pla, lh, lw, 65536)
74 let from_x: i64 = 10 * Q14
75 let to_x: i64 = 190 * Q14
76 let mid_y: i64 = 100 * Q14 // passes through center
77 let z_base: i64 = 0 + lh // first-layer Z = layer_height
78 nx_gemit_extrude_to_bedmesh(e_bowl, bowl, from_x, mid_y, to_x, mid_y,
79 z_base, 0, 5 * Q14, 50)
80 // Default segment 5 mm, span 180 mm → at least 36 segments.
81 let n_g1: i64 = smoke_count(e_bowl.buf, e_bowl.len, "G1 X")
82 if n_g1 < 30 { return 10 } // allow some headroom
83
84 // --- (b) Every emitted segment carries Z ---
85 let n_z: i64 = smoke_count(e_bowl.buf, e_bowl.len, " Z")
86 if n_z < n_g1 { return 20 }
87
88 // --- (c) Bowl: emits elevated Z values where the move crosses the
89 // bowl gradient. The move at y=100 passes through center
90 // (100, 100) where bilinear returns bowl_high = +0.2mm,
91 // giving Z_base+0.2 = 0.4mm. String forms: "Z0.4" appears
92 // in segments near center; "Z0.3" appears in the slopes.
93 if smoke_contains(e_bowl.buf, e_bowl.len, "Z0.4") != 1 { return 30 }
94 if smoke_contains(e_bowl.buf, e_bowl.len, "Z0.3") != 1 { return 31 }
95
96 // --- (d) Flat mesh: NO elevated Z values ---
97 let flat: *NxBedMesh = nx_bed_mesh_new(3, 3, 0, bed_max, 0, bed_max, 10)
98 // All zero by construction
99 let e_flat: *NxGcodeEmitter = nx_gemit_new(qidi, pla, lh, lw, 65536)
100 nx_gemit_extrude_to_bedmesh(e_flat, flat, from_x, mid_y, to_x, mid_y,
101 z_base, 0, 5 * Q14, 50)
102 // Flat bed → every Z must equal z_base. z_base = 3277 in Q14 →
103 // string "Z0.200" appears; no elevated Z anywhere.
104 if smoke_contains(e_flat.buf, e_flat.len, "Z0.200") != 1 { return 40 }
105 if smoke_contains(e_flat.buf, e_flat.len, "Z0.3") == 1 { return 41 }
106 if smoke_contains(e_flat.buf, e_flat.len, "Z0.4") == 1 { return 42 }
107
108 // --- (e) Bowl emitter buffer longer than flat (more digits per
109 // elevated Z value, or no Z change but still proves
110 // compensation activity) ---
111 if e_bowl.len < e_flat.len - 100 { return 50 } // bowl >= flat - small slack
112
113 return 0
114}