code wiki / (root) / nx_slice_pipeline.nx

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}