code wiki / (root) / nx_adaptive_layer.nx

nx_adaptive_layer.nx source

↩ module page · 154 lines · 6578 B

1// nx_adaptive_layer.nx -- bits-up Q14 multi-criteria adaptive layer 2// height computation for FDM (and any direct-write process where the 3// substrate computes per-Z layer thickness from local geometry). 4// 5// ===================================================================== 6// EXCEED axis (research baseline) 7// ===================================================================== 8// 9// Industry adaptive-layer-height treatments (as of 2026-05): 10// 11// Cura 5.x `adaptive_layer_height`: 12// CURVATURE ONLY. Computes mesh surface curvature per candidate 13// Z and outputs layer height = max if flat / min if curved. 14// Single-input heuristic from Wong & Pfaffenberger 2017. 15// 16// OrcaSlicer 2.3.2 "By Slope": 17// CURVATURE ONLY. Same algorithm family as Cura. 18// 19// PrusaSlicer 2.7 "variable layer height": 20// CURVATURE ONLY. Manual or automatic from surface angle. 21// 22// Bambu Studio: 23// No adaptive layer height; constant layer height. 24// 25// Academic SOTA: 26// Steuben et al. 2016 "Multi-Criteria Adaptive Slicing": 27// Proposed curvature + overhang + feature-dimension, but uses 28// offline FEM stress analysis -- minutes per model, not slice-time. 29// 30// Wang et al. 2018 "Adaptive Slicing for FDM Based on 31// Print-Time-Aware Optimization": 32// Time-aware single-pass; still curvature-dominated. 33// 34// EXCEED axis (this file): 4-CRITERION weighted Q14 score at O(1) per 35// Z-step. Caller supplies 4 normalized [0, 16384] inputs: 36// 37// curvature_q14: surface curvature at this Z (Cura's input) 38// load_above_q14: cantilever moment magnitude above this Z 39// (composes nx_pillar_physics -- the load model 40// industry doesn't have) 41// bridge_present: 0 or 1 -- bridges need fine Z for sag control 42// overhang_prox_q14: normalized [0,1] proximity to nearest overhang 43// (finer Z just before an overhang starts 44// improves geometric fidelity) 45// 46// Each criterion votes UP the local-density score; high score → small 47// layer height. Industry uses single criterion; we use 4. 48// 49// ===================================================================== 50// 51// license_tier: ORIGINAL 52 53import "nx_syscalls.nx" 54import "nx_tier.nx" 55 56// ===== weights (Q14, sum = 16384 = 1.0) =========================== 57// 58// Weights chosen to: 59// - keep curvature primary (40%, matches Cura's single-input behavior 60// when other criteria are 0 → backwards-compatible default) 61// - load secondary (30%, composes pillar physics) 62// - bridge tertiary (20%, physical importance of sag control) 63// - overhang proximity smallest (10%, smoothing factor) 64 65const NX_ADAPT_CURVATURE_WEIGHT_Q14: i64 = 6554 // 0.40 66const NX_ADAPT_LOAD_WEIGHT_Q14: i64 = 4915 // 0.30 67const NX_ADAPT_BRIDGE_WEIGHT_Q14: i64 = 3277 // 0.20 68const NX_ADAPT_OVERHANG_WEIGHT_Q14: i64 = 1638 // 0.10 69// Sum = 16384 ✓ 70 71const NX_ADAPT_MIN_LAYER_DEFAULT_Q14: i64 = 1311 // 0.08 mm (fine detail) 72const NX_ADAPT_MAX_LAYER_DEFAULT_Q14: i64 = 4915 // 0.30 mm (coarse fast) 73 74const NX_ADAPT_Q14_ONE: i64 = 16384 75 76// ===== verdicts ==================================================== 77 78const NX_ADAPT_OK: i64 = 0 79const NX_ADAPT_ERR_BAD_INPUT: i64 = 1 80 81// ===== multi-criteria score ======================================= 82// 83// Combines 4 normalized [0, 16384] inputs into a single [0, 16384] 84// "fineness score". Clamps inputs into the valid Q14 range; returns 85// the weighted sum. Out-of-range inputs are silently clamped (the 86// caller's normalization is the contract). 87 88func nx_adaptive_layer_score(curvature_q14: i64, 89 load_above_q14: i64, 90 bridge_present: i64, 91 overhang_prox_q14: i64) -> i64 { 92 // Clamp inputs into [0, NX_ADAPT_Q14_ONE]. 93 var c: i64 = curvature_q14 94 if c < 0 { c = 0 } 95 if c > NX_ADAPT_Q14_ONE { c = NX_ADAPT_Q14_ONE } 96 var l: i64 = load_above_q14 97 if l < 0 { l = 0 } 98 if l > NX_ADAPT_Q14_ONE { l = NX_ADAPT_Q14_ONE } 99 var b: i64 = 0 100 if bridge_present != 0 { b = NX_ADAPT_Q14_ONE } 101 var o: i64 = overhang_prox_q14 102 if o < 0 { o = 0 } 103 if o > NX_ADAPT_Q14_ONE { o = NX_ADAPT_Q14_ONE } 104 105 // Weighted sum: (w_i * x_i) / 16384 (Q14 multiplication). 106 let sum_c_num: i64 = NX_ADAPT_CURVATURE_WEIGHT_Q14 * c 107 let sum_c: i64 = sum_c_num / NX_ADAPT_Q14_ONE 108 let sum_l_num: i64 = NX_ADAPT_LOAD_WEIGHT_Q14 * l 109 let sum_l: i64 = sum_l_num / NX_ADAPT_Q14_ONE 110 let sum_b_num: i64 = NX_ADAPT_BRIDGE_WEIGHT_Q14 * b 111 let sum_b: i64 = sum_b_num / NX_ADAPT_Q14_ONE 112 let sum_o_num: i64 = NX_ADAPT_OVERHANG_WEIGHT_Q14 * o 113 let sum_o: i64 = sum_o_num / NX_ADAPT_Q14_ONE 114 115 let sum_ab: i64 = sum_c + sum_l 116 let sum_cd: i64 = sum_b + sum_o 117 return sum_ab + sum_cd 118} 119 120// ===== layer height from score =================================== 121// 122// Maps score in [0, 16384] to layer height in [min_layer, max_layer]. 123// Score 0 (no criterion fires) -> max layer height (coarse fast). 124// Score 16384 (all criteria max) -> min layer height (fine detail). 125// Linear interpolation in Q14. 126// 127// Returns the recommended layer height in Q14 at this Z slice. 128 129func nx_adaptive_layer_height(curvature_q14: i64, 130 load_above_q14: i64, 131 bridge_present: i64, 132 overhang_prox_q14: i64, 133 min_layer_q14: i64, 134 max_layer_q14: i64) -> i64 { 135 // Defensive: if min > max, return min (degenerate but safe). 136 if min_layer_q14 <= 0 { return NX_ADAPT_MIN_LAYER_DEFAULT_Q14 } 137 if max_layer_q14 <= min_layer_q14 { return min_layer_q14 } 138 139 let score: i64 = nx_adaptive_layer_score(curvature_q14, 140 load_above_q14, 141 bridge_present, 142 overhang_prox_q14) 143 144 // layer_height = max - score * (max - min) / Q14 145 let span: i64 = max_layer_q14 - min_layer_q14 146 let reduction_num: i64 = score * span 147 let reduction: i64 = reduction_num / NX_ADAPT_Q14_ONE 148 let result: i64 = max_layer_q14 - reduction 149 150 // Clamp result into [min, max] (handle rounding edges). 151 if result < min_layer_q14 { return min_layer_q14 } 152 if result > max_layer_q14 { return max_layer_q14 } 153 return result 154}