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}