nx_slice_pipeline.nx source
↩ module page · 249 lines · 10767 B
1// nx_slice_pipeline.nx -- end-to-end mesh -> multi-layer G-code
2// orchestrator. THE integration primitive that composes every
3// P0-P3.2 building block:
4//
5// nx_mesh (input mesh)
6// nx_bvh (spatial index for slice pruning)
7// nx_slice_plane (per-Z plane intersection -> segments)
8// nx_slice_contour (segments -> closed polygons)
9// nx_polygon offset (inward shrink for perimeter loops)
10// nx_infill (rectilinear fill, alternating per layer)
11// nx_gcode_emit (G-code text emission)
12// nx_machine_graph (printer kinematics)
13// nx_material_profile (filament temperatures + speeds)
14// nx_gcode_manifest (provenance header)
15//
16// For each Z layer from bed to top of mesh:
17// 1. Plane-slice the mesh -> segment soup
18// 2. Assemble segments -> closed polygon contours
19// 3. For each contour:
20// a. Emit perimeter loop (the contour itself; v1 = single
21// perimeter, multi-perimeter via offset queued for v2)
22// b. Generate infill segments (alternating direction per layer
23// for grid pattern, mechanical isotropy)
24// c. Emit infill segments as G1 print moves
25//
26// Per cardinal feedback-engineering-sciences-bits-up-3d-print-first:
27// this primitive demonstrates the methodology by which ALL future
28// engineering arcs will be orchestrated -- compose Tier-1 sovereign
29// primitives bits-up into the applied workflow.
30//
31// First print target: simple Voron-cube-style calibration print on
32// the Qidi X-Max 3, then iterate up to mini-Christus, then full
33// Christus per NISHI_3D_PRINT_ROADMAP §6 calibration ladder.
34//
35// license_tier: ORIGINAL
36
37import "nx_syscalls.nx"
38import "nx_mesh.nx"
39import "nx_mesh_print_check.nx"
40import "nx_bvh.nx"
41import "nx_polygon.nx"
42import "nx_slice_plane.nx"
43import "nx_slice_contour.nx"
44import "nx_infill.nx"
45import "nx_machine_graph.nx"
46import "nx_material_profile.nx"
47import "nx_gcode_emit.nx"
48import "nx_supports.nx"
49import "nx_slice_auto_supports.nx"
50
51const NX_SLICE_PIPE_Q14: i64 = 16384
52
53// ===== verdicts ====================================================
54
55const NX_SLICE_PIPE_OK: i64 = 0
56const NX_SLICE_PIPE_ERR_NULL_INPUT: i64 = 1
57const NX_SLICE_PIPE_ERR_BAD_LAYER_H: i64 = 2
58const NX_SLICE_PIPE_ERR_NO_CONTOURS: i64 = 3
59const NX_SLICE_PIPE_ERR_EMIT_FAILED: i64 = 4
60
61// ===== one layer slice + emit =====================================
62//
63// Slices the mesh at the given Z height, assembles contours, and
64// emits perimeter + infill G-code for the layer. Returns
65// NX_SLICE_PIPE_OK if at least one contour found, ERR_NO_CONTOURS
66// if the plane missed the mesh entirely.
67
68func nx_slice_pipe_one_layer(emitter: *NxGcodeEmitter,
69 mesh: *NxMesh, bvh: *NxBvh,
70 z_q14: i64, layer_idx: i64,
71 infill_density_pct: i64) -> i64 {
72 let soup: *NxSliceSoup = nx_slice_plane(mesh, bvh, z_q14)
73 if soup.n_segments <= 0 { return NX_SLICE_PIPE_ERR_NO_CONTOURS }
74 let contours: *NxSliceContours = nx_slice_contour_build(soup)
75 if contours.n_polys <= 0 { return NX_SLICE_PIPE_ERR_NO_CONTOURS }
76
77 // First-layer brim: emit BEFORE perimeters so brim fuses with
78 // the model perimeter as adhesion ring. Only on layer 0 + only
79 // if operator enabled via nx_gemit_set_brim.
80 if layer_idx == 0 {
81 if emitter.n_brim_loops > 0 {
82 var bi: i64 = 0
83 while bi < contours.n_polys {
84 let bpoly: *NxPolygon = nx_slice_contours_get(contours, bi)
85 if (bpoly as i64) != 0 {
86 nx_gemit_brim(emitter, bpoly, z_q14)
87 }
88 bi = bi + 1
89 }
90 }
91 }
92
93 // Emit perimeters: the contours themselves (single-perimeter v1).
94 // Composes nx_gemit_layer which iterates the contours array.
95 nx_gemit_layer(emitter, contours, layer_idx)
96
97 // Infill: for each contour, generate fill segments + emit as
98 // G1 print moves at print speed. Alternates horizontal/vertical
99 // per layer for grid-pattern mechanical isotropy.
100 let vertical: i64 = layer_idx & 1
101 let z_for_infill: i64 = (layer_idx + 1) * emitter.layer_height_q14
102 let mat: *NxMaterialProfile = emitter.material
103 var speed: i64 = mat.print_speed_mms
104 if layer_idx == 0 { speed = mat.first_layer_speed_mms }
105 let travel: i64 = mat.travel_speed_mms
106
107 var pi: i64 = 0
108 while pi < contours.n_polys {
109 let poly: *NxPolygon = nx_slice_contours_get(contours, pi)
110 if (poly as i64) != 0 {
111 let infill_soup: *NxSliceSoup = nx_slice_soup_new(poly.n_verts * 16 + 64)
112 let v: i64 = nx_infill_lines(poly, infill_density_pct,
113 emitter.line_width_q14, vertical,
114 infill_soup)
115 if v == NX_INFILL_OK {
116 // Emit each infill segment as a travel-then-extrude pair.
117 var si: i64 = 0
118 while si < infill_soup.n_segments {
119 let seg: *i64 = nx_slice_soup_seg_ptr(infill_soup, si)
120 let x1: i64 = seg[0]
121 let y1: i64 = seg[1]
122 let x2: i64 = seg[2]
123 let y2: i64 = seg[3]
124 nx_gemit_travel_to(emitter, x1, y1, z_for_infill, travel)
125 nx_gemit_extrude_to(emitter, x1, y1, x2, y2, speed)
126 si = si + 1
127 }
128 }
129 }
130 pi = pi + 1
131 }
132
133 return NX_SLICE_PIPE_OK
134}
135
136// ===== full pipeline ===============================================
137//
138// Computes mesh bbox, builds BVH once (reused across all layers),
139// and iterates Z from layer_height up to bbox.max_z in layer_height
140// steps. Each layer composes through one_layer above. After all
141// layers, emits postamble. Caller is responsible for the preamble
142// + provenance manifest emit (those depend on operator-controlled
143// inputs like which manifest entries to include).
144//
145// Returns the total number of layers emitted (or negative on error).
146
147func nx_slice_pipe_run(emitter: *NxGcodeEmitter,
148 mesh: *NxMesh,
149 infill_density_pct: i64) -> i64 {
150 if (mesh as i64) == 0 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
151 if mesh.n_tris <= 0 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
152 if emitter.layer_height_q14 <= 0 { return 0 - NX_SLICE_PIPE_ERR_BAD_LAYER_H }
153
154 let bbox: *NxMeshBBox = nx_mesh_bbox_compute(mesh)
155 if bbox.valid != 1 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
156
157 let bvh: *NxBvh = nx_bvh_build(mesh)
158 if (bvh as i64) == 0 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
159
160 // First-layer skirt: closes #1 substrate-side failure mode (per
161 // NISHI_3D_PRINT_STABILITY_99_ROADMAP §1.1). Single rectangular
162 // loop around mesh XY bbox at first-layer Z + 5mm Q14 offset.
163 let skirt_offset_q14: i64 = 5 * NX_SLICE_PIPE_Q14 // 5 mm
164 nx_gemit_skirt(emitter,
165 bbox.min_x, bbox.min_y,
166 bbox.max_x, bbox.max_y,
167 emitter.layer_height_q14,
168 skirt_offset_q14)
169
170 var layer_idx: i64 = 0
171 var z_q14: i64 = bbox.min_z + emitter.layer_height_q14
172 while z_q14 <= bbox.max_z {
173 nx_slice_pipe_one_layer(emitter, mesh, bvh, z_q14,
174 layer_idx, infill_density_pct)
175 layer_idx = layer_idx + 1
176 z_q14 = bbox.min_z + (layer_idx + 1) * emitter.layer_height_q14
177 }
178
179 nx_gemit_postamble(emitter)
180 return layer_idx
181}
182
183// ===== v2: integrated slicer with auto-supports =====================
184//
185// Composes nx_slice_pipe_build_supports (pre-pass that walks the mesh
186// and builds a physics-aware support plan) + per-layer slicing + plan
187// emission per layer. Single call: given an emitter + mesh -> G-code
188// with supports auto-detected + load-proportional sizing applied.
189//
190// This is the integrated SUPERIOR CAPABILITY (per 2026-05-20 cardinal):
191// industry slicers auto-detect supports BUT emit constant-diameter
192// pillars. Substrate auto-detects + load-aware sizes -- the
193// integrated workflow is the new capability.
194//
195// Returns total layer count, or negative verdict on failure.
196
197func nx_slice_pipe_run_v2(emitter: *NxGcodeEmitter,
198 mesh: *NxMesh,
199 infill_density_pct: i64,
200 overhang_tol_q14: i64) -> i64 {
201 if (mesh as i64) == 0 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
202 if mesh.n_tris <= 0 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
203 if emitter.layer_height_q14 <= 0 { return 0 - NX_SLICE_PIPE_ERR_BAD_LAYER_H }
204
205 let bbox: *NxMeshBBox = nx_mesh_bbox_compute(mesh)
206 if bbox.valid != 1 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
207
208 let bvh: *NxBvh = nx_bvh_build(mesh)
209 if (bvh as i64) == 0 { return 0 - NX_SLICE_PIPE_ERR_NULL_INPUT }
210
211 // Pre-pass: auto-build physics-aware support plan from mesh.
212 // Emitter's material drives load-aware pillar sizing. Default
213 // footprint = line_width × 5 (5x nozzle), spacing = line_width × 2.
214 let foot_q14: i64 = emitter.line_width_q14 * 5
215 let space_q14: i64 = emitter.line_width_q14 * 2
216 let plan: *NxSupportPlan = nx_slice_pipe_build_supports(mesh, bvh,
217 emitter.material,
218 emitter.layer_height_q14,
219 overhang_tol_q14,
220 foot_q14,
221 space_q14)
222
223 // Skirt around bbox.
224 let skirt_offset_q14: i64 = 5 * NX_SLICE_PIPE_Q14
225 nx_gemit_skirt(emitter,
226 bbox.min_x, bbox.min_y,
227 bbox.max_x, bbox.max_y,
228 emitter.layer_height_q14,
229 skirt_offset_q14)
230
231 var layer_idx: i64 = 0
232 var z_q14: i64 = bbox.min_z + emitter.layer_height_q14
233 while z_q14 <= bbox.max_z {
234 // Emit supports for this layer FIRST (if plan exists) -- runs
235 // before perimeters so the support material bonds upward into
236 // the model perimeter on its next layer.
237 if (plan as i64) != 0 {
238 nx_support_plan_emit_layer(plan, emitter, z_q14)
239 }
240 // Standard layer: brim (layer 0) + perimeters + infill.
241 nx_slice_pipe_one_layer(emitter, mesh, bvh, z_q14,
242 layer_idx, infill_density_pct)
243 layer_idx = layer_idx + 1
244 z_q14 = bbox.min_z + (layer_idx + 1) * emitter.layer_height_q14
245 }
246
247 nx_gemit_postamble(emitter)
248 return layer_idx
249}