code wiki / (root) / nx_machine_graph.nx

nx_machine_graph.nx source

↩ module page · 373 lines · 15479 B

1// nx_machine_graph.nx -- data-driven printer description: build 2// volume, kinematics, motion envelope, thermal limits, hardware 3// geometry. No magic numbers anywhere in the slicer (Cardinal 11) 4// -- every printer-specific value lives here, parameterized. 5// 6// Per cardinal NISHI_3D_PRINT_ROADMAP §2.8 + bits-up-exceed axis 7// "cross-printer profiles don't port": adding a new printer to the 8// substrate's slicer is DESCRIBING its NxMachineGraph, NOT rewriting 9// any slicer code. This is the architecture promise that makes 10// P6/P7 multi-printer proofs achievable, not aspirational. 11// 12// Distinct from nx_motion.nx (which is Lucas-Kanade optical flow 13// for video -- totally different concept; printer "motion" here is 14// the kinematic envelope, not frame-to-frame estimation). 15// 16// Unit conventions per field (documented at struct definition): 17// - Build volume in integer millimetres (no sub-mm precision needed) 18// - Nozzle / filament diameter in integer micrometres 19// - Velocities in integer mm/s 20// - Accelerations in integer mm/s² 21// - Temperatures in integer °C 22// - Q14 fixed-point only where ratios are needed (none here yet) 23// 24// Why mixed integer units rather than uniform Q14: 25// Real-world printer specs are quoted in mm/s, mm/s², °C -- the 26// integer units match operator + manufacturer conventions. Slicer 27// composes nx_fp32_q14 + nx_machine_graph at code points where 28// it needs cross-unit math, with explicit conversion. 29// 30// Operator-verified defaults: nx_machine_graph_qidi_xmax3() returns 31// values verified 2026-05-19 against official Qidi US store specs. 32// Operators MUST verify against their actual unit before any real 33// print -- printer-to-printer variance exists even within model. 34// 35// license_tier: ORIGINAL 36 37import "nx_syscalls.nx" 38import "nx_print_process.nx" 39 40// ===== sealed-enum: kinematics_type ================================ 41 42const NX_KIN_CARTESIAN: i64 = 0 43const NX_KIN_COREXY: i64 = 1 44const NX_KIN_DELTA: i64 = 2 45const NX_KIN_SCARA: i64 = 3 46const NX_KIN_HBOT: i64 = 4 47// Non-FDM-typical kinematics added for machine-agnostic architecture 48// (2026-05-20): the substrate slicer + emit stack is reusable across 49// any additive machinery, not just Cartesian/CoreXY FDM printers. 50const NX_KIN_ROBOT_6DOF: i64 = 5 // arm-mounted nozzle (DIW concrete, 51 // robotic FDM, multi-axis SLA) 52const NX_KIN_GANTRY: i64 = 6 // large-format gantry (concrete 53 // construction-scale printers, 54 // overhead crane DIW) 55const NX_KIN_CONVEYOR: i64 = 7 // belt printer (Creality CR-30, 56 // White Knight; infinite-Z via 57 // angled gantry on conveyor) 58const NX_KIN_N: i64 = 8 59 60func nx_kin_is_valid(k: i64) -> i64 { 61 if k < 0 { return 0 } 62 if k >= NX_KIN_N { return 0 } 63 return 1 64} 65 66// ===== kinematics-class predicates (architecture-reusable) ========= 67// 68// Each predicate is O(1) so the slicer can dispatch on kinematics 69// physics without hardcoding per-machine assumptions. Predicates 70// answer the "what does THIS kinematics allow?" questions that any 71// reusable architecture needs. 72 73// 1 if the kinematics has a finite Z build envelope (false for 74// CONVEYOR which has effectively unbounded Z via belt advancement). 75func nx_kinematics_finite_z(k: i64) -> i64 { 76 if k == NX_KIN_CONVEYOR { return 0 } 77 return 1 78} 79 80// 1 if X and Y motors move independently (Cartesian/Gantry/SCARA/ 81// Robot). CoreXY/HBot/Delta couple motors so jerk envelopes differ. 82func nx_kinematics_xy_decoupled(k: i64) -> i64 { 83 if k == NX_KIN_CARTESIAN { return 1 } 84 if k == NX_KIN_GANTRY { return 1 } 85 if k == NX_KIN_SCARA { return 1 } 86 if k == NX_KIN_ROBOT_6DOF { return 1 } 87 if k == NX_KIN_CONVEYOR { return 1 } // gantry on conveyor 88 return 0 89} 90 91// 1 if the kinematics has >= 5 degrees of freedom (multi-axis enables 92// non-planar slicing, overhang-free toolpaths, robotic-arm-mounted 93// FDM/DIW). Only Robot-6DOF qualifies for now. 94func nx_kinematics_5_axis_or_more(k: i64) -> i64 { 95 if k == NX_KIN_ROBOT_6DOF { return 1 } 96 return 0 97} 98 99// 1 if the build platform moves (belt for CONVEYOR; some Cartesians 100// have a moving bed in Y). Useful for the slicer when computing 101// cooling-airflow direction relative to the deposited filament. 102func nx_kinematics_bed_translates(k: i64) -> i64 { 103 if k == NX_KIN_CONVEYOR { return 1 } 104 if k == NX_KIN_CARTESIAN { return 1 } // Prusa-style moving bed 105 return 0 106} 107 108// ===== sealed-enum: extruder_type ================================== 109 110const NX_EXT_BOWDEN: i64 = 0 111const NX_EXT_DIRECT_DRIVE: i64 = 1 112const NX_EXT_TOOLCHANGER: i64 = 2 113const NX_EXT_N: i64 = 3 114 115// ===== struct ====================================================== 116 117struct NxMachineGraph { 118 // Build volume (mm) 119 build_x_mm: i64, 120 build_y_mm: i64, 121 build_z_mm: i64, 122 123 // Kinematics 124 kinematics: i64, // NX_KIN_* 125 126 // Motion envelope 127 max_velocity_xy_mms: i64, // mm/s 128 max_velocity_z_mms: i64, 129 max_velocity_e_mms: i64, // extruder 130 max_accel_mms2: i64, // mm/s² 131 max_jerk_mms: i64, // mm/s (junction velocity) 132 133 // Thermal envelope 134 hotend_max_c: i64, 135 hotend_min_c: i64, 136 bed_max_c: i64, 137 chamber_max_c: i64, // 0 if no heated chamber 138 139 // Hardware geometry 140 nozzle_dia_um: i64, // 400 = 0.4 mm 141 filament_dia_um: i64, // 1750 = 1.75 mm 142 extruder_type: i64, // NX_EXT_* 143 max_flow_mm3s: i64, // hotend high-flow ceiling 144 145 // Probing + features 146 has_auto_bed_mesh: i64, // 0 or 1 147 has_z_probe: i64, 148 has_input_shaping: i64, // Klipper / Marlin 2.1+ 149 150 // Process class -- which additive-manufacturing process this 151 // machine performs (FDM polymer / DIW cement / DMLS / etc.). 152 // See runtime/nx_print_process.nx for the sealed enum. Set on 153 // every factory so the slicer can dispatch to the right emit 154 // backend without per-machine specialization. (Architecture- 155 // reusable axis 2026-05-20: substrate is machine-agnostic; 156 // Qidi X-Max 3 happens to be the FDM-polymer reference impl.) 157 process_class: i64, 158} 159 160const NX_MACHINE_BYTES: i64 = 176 // 22 fields × 8 161 162// ===== allocation ================================================== 163 164func nx_machine_graph_alloc() -> *NxMachineGraph { 165 return (sys_mmap(NX_MACHINE_BYTES)) as *NxMachineGraph 166} 167 168// ===== validation ================================================== 169 170const NX_MACHINE_OK: i64 = 0 171const NX_MACHINE_ERR_BAD_KIN: i64 = 1 172const NX_MACHINE_ERR_BAD_EXT: i64 = 2 173const NX_MACHINE_ERR_BAD_BUILD: i64 = 3 174const NX_MACHINE_ERR_BAD_THERMAL: i64 = 4 175const NX_MACHINE_ERR_BAD_MOTION: i64 = 5 176const NX_MACHINE_ERR_BAD_HARDWARE: i64 = 6 177 178// Returns NX_MACHINE_OK or first verdict that fails. Defends the 179// slicer boundary (Cardinal 12) from operator-typo machine graphs. 180func nx_machine_graph_validate(g: *NxMachineGraph) -> i64 { 181 if nx_kin_is_valid(g.kinematics) != 1 { return NX_MACHINE_ERR_BAD_KIN } 182 if g.extruder_type < 0 { return NX_MACHINE_ERR_BAD_EXT } 183 if g.extruder_type >= NX_EXT_N { return NX_MACHINE_ERR_BAD_EXT } 184 185 if g.build_x_mm <= 0 { return NX_MACHINE_ERR_BAD_BUILD } 186 if g.build_y_mm <= 0 { return NX_MACHINE_ERR_BAD_BUILD } 187 if g.build_z_mm <= 0 { return NX_MACHINE_ERR_BAD_BUILD } 188 189 if g.hotend_max_c <= g.hotend_min_c { return NX_MACHINE_ERR_BAD_THERMAL } 190 if g.hotend_max_c <= 0 { return NX_MACHINE_ERR_BAD_THERMAL } 191 if g.bed_max_c < 0 { return NX_MACHINE_ERR_BAD_THERMAL } 192 if g.chamber_max_c < 0 { return NX_MACHINE_ERR_BAD_THERMAL } 193 194 if g.max_velocity_xy_mms <= 0 { return NX_MACHINE_ERR_BAD_MOTION } 195 if g.max_velocity_z_mms <= 0 { return NX_MACHINE_ERR_BAD_MOTION } 196 if g.max_velocity_e_mms <= 0 { return NX_MACHINE_ERR_BAD_MOTION } 197 if g.max_accel_mms2 <= 0 { return NX_MACHINE_ERR_BAD_MOTION } 198 if g.max_jerk_mms < 0 { return NX_MACHINE_ERR_BAD_MOTION } 199 200 if g.nozzle_dia_um <= 0 { return NX_MACHINE_ERR_BAD_HARDWARE } 201 if g.filament_dia_um <= 0 { return NX_MACHINE_ERR_BAD_HARDWARE } 202 if g.max_flow_mm3s <= 0 { return NX_MACHINE_ERR_BAD_HARDWARE } 203 204 return NX_MACHINE_OK 205} 206 207// ===== factory: Qidi X-Max 3 ======================================= 208// 209// Verified against https://qidi3d.com/pages/x-max3 2026-05-19. 210// Build volume + accel + hotend + chamber + max_flow all match 211// official spec. Z velocity, jerk, min temp are conservative 212// substrate defaults (operator should refine via nx_calibrate when 213// P5 ships). 214 215func nx_machine_graph_qidi_xmax3() -> *NxMachineGraph { 216 let g: *NxMachineGraph = nx_machine_graph_alloc() 217 g.build_x_mm = 325 // verified 218 g.build_y_mm = 325 // verified 219 g.build_z_mm = 315 // verified 220 g.kinematics = NX_KIN_COREXY 221 g.max_velocity_xy_mms = 600 // verified (advertised peak) 222 g.max_velocity_z_mms = 30 // conservative 223 g.max_velocity_e_mms = 100 // conservative 224 g.max_accel_mms2 = 20000 // verified 225 g.max_jerk_mms = 12 // Klipper square_corner_velocity equiv 226 g.hotend_max_c = 350 // verified 227 g.hotend_min_c = 170 // PLA minimum 228 g.bed_max_c = 120 // typical for class 229 g.chamber_max_c = 65 // verified 230 g.nozzle_dia_um = 400 // default 0.4 mm 231 g.filament_dia_um = 1750 // 1.75 mm 232 g.extruder_type = NX_EXT_DIRECT_DRIVE 233 g.max_flow_mm3s = 35 // verified (high-flow hotend) 234 g.has_auto_bed_mesh = 1 235 g.has_z_probe = 1 236 g.has_input_shaping = 1 // Klipper firmware 237 g.process_class = NX_PROCESS_FDM_POLYMER 238 return g 239} 240 241// ===== factory: Prusa MK4 / MK4S =================================== 242// 243// Verified specs (Prusa Research 2026 public data): i3-class bed- 244// slinger (NOT CoreXY -- bed is Y-axis), Nextruder loadcell (gold- 245// standard first-layer), input shaping in 5.0.0+ firmware, high-flow 246// optional 0.6mm nozzle, all-metal hotend. no heated chamber. 247// 248// Prusa-MK4-specific feature: loadcell first-layer means 249// has_z_probe=1 reflects PHYSICAL touch-the-bed measurement, NOT 250// inductive probe -- the most accurate first-layer in the industry 251// per Prusa Research's loadcell research. 252 253func nx_machine_graph_prusa_mk4() -> *NxMachineGraph { 254 let g: *NxMachineGraph = nx_machine_graph_alloc() 255 g.build_x_mm = 250 256 g.build_y_mm = 210 257 g.build_z_mm = 220 258 g.kinematics = NX_KIN_CARTESIAN 259 g.max_velocity_xy_mms = 200 260 g.max_velocity_z_mms = 12 261 g.max_velocity_e_mms = 80 262 g.max_accel_mms2 = 2000 263 g.max_jerk_mms = 5 264 g.hotend_max_c = 290 265 g.hotend_min_c = 170 266 g.bed_max_c = 120 267 g.chamber_max_c = 0 // no chamber (open-frame i3) 268 g.nozzle_dia_um = 400 269 g.filament_dia_um = 1750 270 g.extruder_type = NX_EXT_DIRECT_DRIVE 271 g.max_flow_mm3s = 35 // Nextruder high-flow capability 272 g.has_auto_bed_mesh = 1 273 g.has_z_probe = 1 // loadcell first-layer 274 g.has_input_shaping = 1 // firmware 5.0.0+ 275 g.process_class = NX_PROCESS_FDM_POLYMER 276 return g 277} 278 279// ===== factory: Voron 2.4 (300mm canonical) ======================== 280// 281// Voron-design canonical 300mm CoreXY build. Operator-built so 282// specifics vary; this captures the most common config (Stealthburner 283// + Phaetus Rapido + 300mm bed + optional 60°C chamber). Klipper 284// firmware (Voron canonical) -- input shaping + bed mesh standard. 285 286func nx_machine_graph_voron_2_4() -> *NxMachineGraph { 287 let g: *NxMachineGraph = nx_machine_graph_alloc() 288 g.build_x_mm = 300 289 g.build_y_mm = 300 290 g.build_z_mm = 300 291 g.kinematics = NX_KIN_COREXY 292 g.max_velocity_xy_mms = 500 293 g.max_velocity_z_mms = 30 294 g.max_velocity_e_mms = 100 295 g.max_accel_mms2 = 10000 296 g.max_jerk_mms = 12 297 g.hotend_max_c = 300 // Rapido HF typical 298 g.hotend_min_c = 170 299 g.bed_max_c = 120 300 g.chamber_max_c = 60 // operator-installed; common build 301 g.nozzle_dia_um = 400 302 g.filament_dia_um = 1750 303 g.extruder_type = NX_EXT_DIRECT_DRIVE 304 g.max_flow_mm3s = 25 // conservative; HF hotend goes higher 305 g.has_auto_bed_mesh = 1 306 g.has_z_probe = 1 307 g.has_input_shaping = 1 308 g.process_class = NX_PROCESS_FDM_POLYMER 309 return g 310} 311 312// ===== factory: Bambu Lab X1 Carbon ================================ 313// 314// Verified vs https://bambulab.com/en-us/x1 (2026-05). CoreXY, 315// active vibration compensation, micro-LiDAR (closed-loop first- 316// layer + flow calibration, hardware-bound), AMS multi-material 317// integration. Substrate composes the Klipper-equivalent endpoints 318// only -- LiDAR + AMS are operator-side proprietary features. 319 320func nx_machine_graph_bambu_x1c() -> *NxMachineGraph { 321 let g: *NxMachineGraph = nx_machine_graph_alloc() 322 g.build_x_mm = 256 323 g.build_y_mm = 256 324 g.build_z_mm = 256 325 g.kinematics = NX_KIN_COREXY 326 g.max_velocity_xy_mms = 500 327 g.max_velocity_z_mms = 30 328 g.max_velocity_e_mms = 100 329 g.max_accel_mms2 = 20000 330 g.max_jerk_mms = 12 331 g.hotend_max_c = 300 // hardened steel default 332 g.hotend_min_c = 170 333 g.bed_max_c = 110 334 g.chamber_max_c = 60 // passive (no heater); reaches via hotend + bed 335 g.nozzle_dia_um = 400 336 g.filament_dia_um = 1750 337 g.extruder_type = NX_EXT_DIRECT_DRIVE 338 g.max_flow_mm3s = 35 339 g.has_auto_bed_mesh = 1 // LiDAR-based 340 g.has_z_probe = 1 // LiDAR + force sensors 341 g.has_input_shaping = 1 // active vibration compensation 342 g.process_class = NX_PROCESS_FDM_POLYMER 343 return g 344} 345 346// ===== process_class setter (architecture-reusable) ================ 347// 348// Allow callers to construct a machine graph for a non-FDM process 349// (e.g., DIW concrete printer) by composing the existing struct 350// plus the process_class field. No FDM-specific assumption is 351// baked into the substrate. 352 353func nx_machine_set_process(g: *NxMachineGraph, process_class: i64) -> i64 { 354 if (g as i64) == 0 { return -1 } 355 if nx_process_is_valid(process_class) == 0 { return -1 } 356 g.process_class = process_class 357 return 0 358} 359 360// ===== sealed-enum verdict names ================================== 361// 362// Used by audit dashboards + smokes for human-readable failures. 363 364func nx_machine_verdict_name(v: i64) -> *u8 { 365 if v == NX_MACHINE_OK { return "OK" } 366 if v == NX_MACHINE_ERR_BAD_KIN { return "BAD_KIN" } 367 if v == NX_MACHINE_ERR_BAD_EXT { return "BAD_EXT" } 368 if v == NX_MACHINE_ERR_BAD_BUILD { return "BAD_BUILD" } 369 if v == NX_MACHINE_ERR_BAD_THERMAL { return "BAD_THERMAL" } 370 if v == NX_MACHINE_ERR_BAD_MOTION { return "BAD_MOTION" } 371 if v == NX_MACHINE_ERR_BAD_HARDWARE { return "BAD_HARDWARE" } 372 return "UNKNOWN" 373}