code wiki / (root) / nx_slice_auto_supports.nx

nx_slice_auto_supports.nx source

↩ module page · 158 lines · 6925 B

1// nx_slice_auto_supports.nx -- auto-build a physics-aware support 2// plan from a real mesh. 3// 4// ===================================================================== 5// SUPERIOR CAPABILITY (per the 2026-05-20 cardinal): 6// Industry slicers auto-detect supports but ALL emit constant-diameter 7// pillars regardless of cantilever load. This primitive composes: 8// 9// mesh -> bvh -> per-layer slice -> overhang vertex detect -> 10// load-proportional pillar add (physics-aware sizing) -> NxSupportPlan 11// 12// The capability that industry doesn't have: auto-detected supports 13// with load-AWARE per-pillar sizing. The integrated workflow IS the 14// superior capability -- not just a math primitive. 15// ===================================================================== 16// 17// license_tier: ORIGINAL 18 19import "nx_syscalls.nx" 20import "nx_polygon.nx" 21import "nx_mesh.nx" 22import "nx_mesh_print_check.nx" 23import "nx_bvh.nx" 24import "nx_slice_plane.nx" 25import "nx_slice_contour.nx" 26import "nx_supports.nx" 27import "nx_material_profile.nx" 28import "nx_pillar_physics.nx" 29 30// Default local-load proxy for each detected overhang vertex. 31// v1 uses constant 1 g·mm moment per vertex; substrate caller can 32// override by walking the plan and updating per-pillar load with 33// geometry-derived values. The proximity-dedup of the plan ensures 34// repeated overhangs at the same XY across layers extend top_z to 35// the highest needing layer. 36const NX_AUTO_SUPPORT_DEFAULT_MASS_Q14: i64 = 16384 // 1 g 37const NX_AUTO_SUPPORT_DEFAULT_ARM_Q14: i64 = 32768 // 2 mm 38 39const NX_AUTO_SUPPORT_OK: i64 = 0 40const NX_AUTO_SUPPORT_ERR_INPUT: i64 = 1 41 42// Builds a physics-aware support plan by walking every layer Z step 43// of the mesh, detecting overhang vertices via nx_supports_layer_overhang, 44// and adding them to a NxSupportPlan via load-proportional sizing. 45// 46// Returns 0-ptr on bad input. Otherwise returns a fully-populated 47// NxSupportPlan that can be passed to nx_support_plan_emit_layer in 48// the main slice pipeline. 49 50func nx_slice_pipe_build_supports(mesh: *NxMesh, 51 bvh: *NxBvh, 52 material: *NxMaterialProfile, 53 layer_height_q14: i64, 54 overhang_tol_q14: i64, 55 footprint_q14: i64, 56 spacing_q14: i64) -> *NxSupportPlan { 57 if (mesh as i64) == 0 { return 0 as *NxSupportPlan } 58 if mesh.n_tris <= 0 { return 0 as *NxSupportPlan } 59 if (bvh as i64) == 0 { return 0 as *NxSupportPlan } 60 if (material as i64) == 0 { return 0 as *NxSupportPlan } 61 if layer_height_q14 <= 0 { return 0 as *NxSupportPlan } 62 63 let bbox: *NxMeshBBox = nx_mesh_bbox_compute(mesh) 64 if (bbox as i64) == 0 { return 0 as *NxSupportPlan } 65 if bbox.valid != 1 { return 0 as *NxSupportPlan } 66 67 // Plan capacity estimate: at most ~16 overhang verts per layer 68 // (typical model has a handful). Clamp to sane bounds. 69 let bbox_z_span: i64 = bbox.max_z - bbox.min_z 70 let n_layers_est: i64 = bbox_z_span / layer_height_q14 71 var plan_cap: i64 = n_layers_est * 4 72 if plan_cap < 32 { plan_cap = 32 } 73 if plan_cap > 4096 { plan_cap = 4096 } 74 75 let plan: *NxSupportPlan = nx_support_plan_new(plan_cap, footprint_q14, spacing_q14) 76 if (plan as i64) == 0 { return 0 as *NxSupportPlan } 77 78 let sp: *NxSupportPoints = nx_supports_new(1024) 79 80 // Walk layers from bottom to top. 81 // 82 // v1.1 (2026-05-20): cache last_nonempty_prev across coplanar- 83 // slice gaps. When a slice plane is coplanar with a mesh face 84 // (e.g., column-top and cap-bottom at the same Z in a mushroom), 85 // nx_slice_plane produces zero segments and contour-build 86 // returns zero contours. v1 treated this as "prev_contours is 87 // empty -> cur is bed-supported" which incorrectly missed real 88 // overhangs at the layer just past the coplanar slice. 89 // 90 // Fix: track last_nonempty_prev separately; use it for overhang 91 // comparison when prev_contours is empty but we have established 92 // history above the bed. (Documented in mushroom-overhang test 93 // commit prior to this one.) 94 var prev_contours: *NxSliceContours = 0 as *NxSliceContours 95 var last_nonempty_prev: *NxSliceContours = 0 as *NxSliceContours 96 var layer_idx: i64 = 0 97 var z_q14: i64 = bbox.min_z + layer_height_q14 98 while z_q14 <= bbox.max_z { 99 let soup: *NxSliceSoup = nx_slice_plane(mesh, bvh, z_q14) 100 let cur_contours: *NxSliceContours = nx_slice_contour_build(soup) 101 102 // Choose effective_prev: prefer current prev_contours; fall 103 // back to last_nonempty_prev when prev was empty (coplanar 104 // slice gap). 105 var effective_prev: *NxSliceContours = prev_contours 106 if (effective_prev as i64) != 0 { 107 if effective_prev.n_polys == 0 { 108 effective_prev = last_nonempty_prev 109 } 110 } 111 else { 112 effective_prev = last_nonempty_prev 113 } 114 115 if (effective_prev as i64) != 0 { 116 if effective_prev.n_polys > 0 { 117 // v1 pairing: match contours by index (single-island 118 // correct; multi-island best-effort). v2 centroid- 119 // tracking queued. 120 var ci: i64 = 0 121 while ci < cur_contours.n_polys { 122 let cur_poly: *NxPolygon = nx_slice_contours_get(cur_contours, ci) 123 var prev_poly: *NxPolygon = 0 as *NxPolygon 124 if ci < effective_prev.n_polys { 125 prev_poly = nx_slice_contours_get(effective_prev, ci) 126 } 127 if (cur_poly as i64) != 0 { 128 let n_before: i64 = sp.n 129 nx_supports_layer_overhang(prev_poly, cur_poly, 130 overhang_tol_q14, sp) 131 var vi: i64 = n_before 132 while vi < sp.n { 133 nx_support_plan_add_pillar_physics(plan, 134 sp.xs[vi], sp.ys[vi], z_q14, 135 NX_AUTO_SUPPORT_DEFAULT_MASS_Q14, 136 NX_AUTO_SUPPORT_DEFAULT_ARM_Q14, 137 material) 138 vi = vi + 1 139 } 140 } 141 ci = ci + 1 142 } 143 } 144 } 145 146 // Update last_nonempty_prev ONLY if cur has content. 147 if (cur_contours as i64) != 0 { 148 if cur_contours.n_polys > 0 { 149 last_nonempty_prev = cur_contours 150 } 151 } 152 prev_contours = cur_contours 153 layer_idx = layer_idx + 1 154 z_q14 = bbox.min_z + (layer_idx + 1) * layer_height_q14 155 } 156 157 return plan 158}