nx_machine_arch_test.nx source
↩ module page · 128 lines · 6532 B
1// nx_machine_arch_test.nx -- verify the machine-graph + kinematics
2// + process-class abstractions are reusable across any additive
3// machinery, with FDM polymer (Qidi X-Max 3) as the reference impl.
4//
5// Operator directive (2026-05-20, verbatim):
6// "we are focused on qidi but i wanted the architecture mapped out
7// so it was reusable on any additive machinery"
8//
9// Closed-form invariants:
10// (a) Existing FDM factories (Qidi/Prusa/Voron/Bambu) ALL carry
11// process_class == NX_PROCESS_FDM_POLYMER (architecture
12// compliance with the multi-process taxonomy)
13// (b) Each FDM factory has a valid kinematics class
14// (c) Kinematics predicates classify all 8 classes consistently:
15// - finite_z: only CONVEYOR is false (belt = unbounded Z)
16// - xy_decoupled: Cartesian/Gantry/SCARA/Robot/Conveyor true;
17// CoreXY/HBot/Delta false
18// - 5_axis_or_more: only Robot_6DOF
19// - bed_translates: Conveyor + Cartesian (bed-slinger)
20// (d) nx_machine_set_process accepts valid processes, rejects invalid
21// (e) Hypothetical DIW concrete machine can be constructed from the
22// same NxMachineGraph primitive by composing a generic
23// allocation + setter chain -- proves the substrate is NOT
24// FDM-specific
25//
26// expect_exit: 0
27// license_tier: ORIGINAL
28
29import "nx_syscalls.nx"
30import "nx_print_process.nx"
31import "nx_machine_graph.nx"
32
33func main() -> i64 {
34 // --- (a) Every shipped FDM factory carries the right process class ---
35 let qidi: *NxMachineGraph = nx_machine_graph_qidi_xmax3()
36 let prusa: *NxMachineGraph = nx_machine_graph_prusa_mk4()
37 let voron: *NxMachineGraph = nx_machine_graph_voron_2_4()
38 let bambu: *NxMachineGraph = nx_machine_graph_bambu_x1c()
39
40 if qidi.process_class != NX_PROCESS_FDM_POLYMER { return 10 }
41 if prusa.process_class != NX_PROCESS_FDM_POLYMER { return 11 }
42 if voron.process_class != NX_PROCESS_FDM_POLYMER { return 12 }
43 if bambu.process_class != NX_PROCESS_FDM_POLYMER { return 13 }
44
45 // --- (b) Kinematics class is valid for every factory ---
46 if nx_kin_is_valid(qidi.kinematics) != 1 { return 20 }
47 if nx_kin_is_valid(prusa.kinematics) != 1 { return 21 }
48 if nx_kin_is_valid(voron.kinematics) != 1 { return 22 }
49 if nx_kin_is_valid(bambu.kinematics) != 1 { return 23 }
50
51 // --- (c) Kinematics-class predicates classify all 8 classes ---
52 // finite_z: only CONVEYOR is false
53 if nx_kinematics_finite_z(NX_KIN_CARTESIAN) != 1 { return 30 }
54 if nx_kinematics_finite_z(NX_KIN_COREXY) != 1 { return 31 }
55 if nx_kinematics_finite_z(NX_KIN_DELTA) != 1 { return 32 }
56 if nx_kinematics_finite_z(NX_KIN_GANTRY) != 1 { return 33 }
57 if nx_kinematics_finite_z(NX_KIN_CONVEYOR) != 0 { return 34 }
58
59 // xy_decoupled: Cartesian + Gantry + SCARA + Robot + Conveyor are true
60 if nx_kinematics_xy_decoupled(NX_KIN_CARTESIAN) != 1 { return 40 }
61 if nx_kinematics_xy_decoupled(NX_KIN_GANTRY) != 1 { return 41 }
62 if nx_kinematics_xy_decoupled(NX_KIN_SCARA) != 1 { return 42 }
63 if nx_kinematics_xy_decoupled(NX_KIN_ROBOT_6DOF) != 1 { return 43 }
64 // CoreXY + HBot + Delta couple motors
65 if nx_kinematics_xy_decoupled(NX_KIN_COREXY) != 0 { return 44 }
66 if nx_kinematics_xy_decoupled(NX_KIN_HBOT) != 0 { return 45 }
67 if nx_kinematics_xy_decoupled(NX_KIN_DELTA) != 0 { return 46 }
68
69 // 5_axis_or_more: only Robot_6DOF
70 if nx_kinematics_5_axis_or_more(NX_KIN_ROBOT_6DOF) != 1 { return 50 }
71 if nx_kinematics_5_axis_or_more(NX_KIN_CARTESIAN) != 0 { return 51 }
72 if nx_kinematics_5_axis_or_more(NX_KIN_COREXY) != 0 { return 52 }
73 if nx_kinematics_5_axis_or_more(NX_KIN_DELTA) != 0 { return 53 }
74
75 // bed_translates: Conveyor (belt) + Cartesian (i3-class moving bed)
76 if nx_kinematics_bed_translates(NX_KIN_CONVEYOR) != 1 { return 60 }
77 if nx_kinematics_bed_translates(NX_KIN_CARTESIAN) != 1 { return 61 }
78 if nx_kinematics_bed_translates(NX_KIN_COREXY) != 0 { return 62 } // bed stationary
79 if nx_kinematics_bed_translates(NX_KIN_GANTRY) != 0 { return 63 } // bed stationary
80
81 // --- (d) Setter accepts valid, rejects invalid ---
82 if nx_machine_set_process(qidi, NX_PROCESS_FDM_METAL) != 0 { return 70 }
83 if qidi.process_class != NX_PROCESS_FDM_METAL { return 71 }
84 // Reset to FDM_POLYMER (the canonical Qidi mode)
85 if nx_machine_set_process(qidi, NX_PROCESS_FDM_POLYMER) != 0 { return 72 }
86 // Reject bad process
87 if nx_machine_set_process(qidi, -1) == 0 { return 73 }
88 if nx_machine_set_process(qidi, 999) == 0 { return 74 }
89
90 // --- (e) Hypothetical DIW concrete machine (LARGE GANTRY,
91 // DIW_CEMENT process) constructable from the SAME
92 // NxMachineGraph primitive ---
93 let diw: *NxMachineGraph = nx_machine_graph_alloc()
94 diw.build_x_mm = 5000 // 5m construction-scale
95 diw.build_y_mm = 5000
96 diw.build_z_mm = 3000
97 diw.kinematics = NX_KIN_GANTRY
98 diw.max_velocity_xy_mms = 200 // slow for cement
99 diw.max_velocity_z_mms = 50
100 diw.max_velocity_e_mms = 100 // pump rate
101 diw.max_accel_mms2 = 1000
102 diw.max_jerk_mms = 10
103 diw.hotend_max_c = 0 // no heater for cement
104 diw.hotend_min_c = 0
105 diw.bed_max_c = 0
106 diw.chamber_max_c = 0
107 diw.nozzle_dia_um = 25000 // 25 mm bead for cement
108 diw.filament_dia_um = 0
109 diw.extruder_type = 0
110 diw.max_flow_mm3s = 5000 // 5 cm³/s pump
111 diw.has_auto_bed_mesh = 0
112 diw.has_z_probe = 0
113 diw.has_input_shaping = 0
114 if nx_machine_set_process(diw, NX_PROCESS_DIW_CEMENT) != 0 { return 80 }
115
116 // Verify the architecture treats DIW machine consistently:
117 if diw.process_class != NX_PROCESS_DIW_CEMENT { return 81 }
118 if nx_kin_is_valid(diw.kinematics) != 1 { return 82 }
119 if nx_kinematics_finite_z(diw.kinematics) != 1 { return 83 }
120 if nx_kinematics_xy_decoupled(diw.kinematics) != 1 { return 84 }
121 if nx_process_is_valid(diw.process_class) != 1 { return 85 }
122 if nx_process_uses_powder_bed(diw.process_class) != 0 { return 86 }
123 if nx_process_needs_post_cure(diw.process_class) != 1 { return 87 }
124 if nx_process_is_direct_write(diw.process_class) != 1 { return 88 }
125 if nx_process_robot_pickup_ready(diw.process_class) != 0 { return 89 }
126
127 return 0
128}