code wiki / _hdl_build / nx_matstruct.nx
nx_matstruct.nx source
↩ module page · 259 lines · 15164 B
1// nx_matstruct.nx -- ★THE STRUCTURAL MATERIAL REGISTRY: what a part is MADE OF, and what that implies.
2//
3// ★THE GAP THIS CLOSES (seq1417, measured 2026-07-30): the ecosystem HAS a real structural primitive --
4// nx_stress computes sigma = F/A in integer Pascals and returns SEALED verdicts SAFE/WORKING/YIELDED/
5// FRACTURED -- but it imports nx_material_profile.nx and inherits ITS enum, which is the SLICER'S FILAMENT
6// REGISTRY (PLA, PETG, ABS, ASA, PC, TPU, NYLON, PA-CF, PEEK, carrying hotend temps and retraction).
7// So the entire structural-material universe was 3D-PRINTER FILAMENT. 'This bracket is 1018 steel' was
8// INEXPRESSIBLE, and an underbuilt verdict on a car was impossible no matter how good the geometry got.
9//
10// ★★WHY THIS IS A NEW ORGAN AND NOT SIX MORE ROWS IN NX_MAT_*: that enum is OWNED BY THE PRINT LANE and
11// is backed by a struct of hotend temperature, cooling and pressure advance. Adding steel to it would owe
12// a hotend temperature for steel. A filament class and a structural-material class are DIFFERENT THINGS
13// THAT SHARE A NAME; binding a general primitive to a domain-specific registry was the category error.
14// This registry keeps the print lane's data intact and simply stops being the only answer.
15//
16// ★★★NOTHING IS STRIPPED (rule 25 -- the answer is always better data, never fewer options): indices 0..8
17// ARE the nine filaments, in nx_stress's exact order, carrying its exact shipped yields and ultimates, so
18// this is a strict SUPERSET and a caller can migrate by index. T6 proves that correspondence value by
19// value against the literals nx_stress ships, so a transcription slip cannot pass.
20//
21// nx_matstruct selftest
22// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26).
23import "nx_gate_verdict.nx"
24const MS_MAGIC_1240: i64 = 1240
25const MS_MAGIC_3500: i64 = 3500
26const MS_MAGIC_1270: i64 = 1270
27const MS_MAGIC_2100: i64 = 2100
28const MS_MAGIC_1040: i64 = 1040
29const MS_MAGIC_2200: i64 = 2200
30const MS_MAGIC_1070: i64 = 1070
31const MS_MAGIC_2300: i64 = 2300
32const MS_MAGIC_1200: i64 = 1200
33const MS_MAGIC_1210: i64 = 1210
34const MS_MAGIC_1140: i64 = 1140
35const MS_MAGIC_2000: i64 = 2000
36const MS_MAGIC_1300: i64 = 1300
37const MS_MAGIC_7000: i64 = 7000
38const MS_MAGIC_1320: i64 = 1320
39const MS_MAGIC_3600: i64 = 3600
40const MS_MAGIC_7870: i64 = 7870
41const MS_MAGIC_205000: i64 = 205000
42const MS_MAGIC_7850: i64 = 7850
43const MS_MAGIC_4140: i64 = 4140
44const MS_MAGIC_2700: i64 = 2700
45const MS_MAGIC_68900: i64 = 68900
46const MS_MAGIC_6061: i64 = 6061
47const MS_MAGIC_2680: i64 = 2680
48const MS_MAGIC_70300: i64 = 70300
49const MS_MAGIC_5052: i64 = 5052
50const MS_MAGIC_7100: i64 = 7100
51const MS_MAGIC_168000: i64 = 168000
52const MS_MAGIC_1810: i64 = 1810
53const MS_MAGIC_45000: i64 = 45000
54const MS_MAGIC_4430: i64 = 4430
55const MS_MAGIC_114000: i64 = 114000
56const MS_MAGIC_1000000000: i64 = 1000000000
57const MS_MAGIC_7870000000: i64 = 7870000000
58const MS_MAGIC_2700000000: i64 = 2700000000
59
60// ---- families ------------------------------------------------------------------------------
61const MS_FAM_POLYMER: i64 = 0
62const MS_FAM_STEEL: i64 = 1
63const MS_FAM_ALUMINIUM: i64 = 2
64const MS_FAM_IRON: i64 = 3
65const MS_FAM_MAGNESIUM: i64 = 4
66const MS_FAM_TITANIUM: i64 = 5
67
68// ---- verdicts: the SAME sealed values nx_stress already returns, so the two compose ----------
69const MS_SAFE: i64 = 0 // below half yield -- ample margin
70const MS_WORKING: i64 = 1 // [yield/2, yield) -- the design range
71const MS_YIELDED: i64 = 2 // [yield, ultimate) -- permanent deformation
72const MS_FRACTURED: i64 = 3 // at or past ultimate
73const MS_UNKNOWN: i64 = 4 // not in the registry -- REFUSED, never guessed
74
75const MS_NM: i64 = 16 // materials modelled -- honest, and QUERYABLE
76const MS_S: i64 = 5 // row: family, density kg/m3, yield MPa, ultimate MPa, modulus MPa
77const MS_BAD: i64 = 0 - 1
78const MS_MPA: i64 = 1000000 // MPa -> Pa, applied at the accessor so the TABLE stays readable
79
80// ★ONE TABLE. Yields and ultimates in MPa; density kg/m3; modulus MPa. Engineering-handbook consensus
81// values for the common structural grades, and for 0..8 the EXACT figures nx_stress already ships.
82func ms_table(T: *i64) -> i64 {
83 var i: i64 = 0
84 // family dens yield ult E
85 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1240; T[i+2]=50; T[i+3]=65; T[i+4]=MS_MAGIC_3500; i=i+MS_S // 0 PLA
86 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1270; T[i+2]=45; T[i+3]=50; T[i+4]=MS_MAGIC_2100; i=i+MS_S // 1 PETG
87 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1040; T[i+2]=35; T[i+3]=45; T[i+4]=MS_MAGIC_2200; i=i+MS_S // 2 ABS
88 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1070; T[i+2]=35; T[i+3]=45; T[i+4]=MS_MAGIC_2300; i=i+MS_S // 3 ASA
89 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1200; T[i+2]=65; T[i+3]=70; T[i+4]=MS_MAGIC_2300; i=i+MS_S // 4 PC
90 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1210; T[i+2]=30; T[i+3]=40; T[i+4]=26; i=i+MS_S // 5 TPU 95A
91 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1140; T[i+2]=60; T[i+3]=80; T[i+4]=MS_MAGIC_2000; i=i+MS_S // 6 NYLON
92 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1300; T[i+2]=80; T[i+3]=120; T[i+4]=MS_MAGIC_7000; i=i+MS_S // 7 PA-CF
93 T[i]=MS_FAM_POLYMER; T[i+1]=MS_MAGIC_1320; T[i+2]=95; T[i+3]=100; T[i+4]=MS_MAGIC_3600; i=i+MS_S // 8 PEEK
94 // ★THE METALS A CAR IS ACTUALLY MADE OF -- the reason this organ exists.
95 T[i]=MS_FAM_STEEL; T[i+1]=MS_MAGIC_7870; T[i+2]=370; T[i+3]=440; T[i+4]=MS_MAGIC_205000; i=i+MS_S // 9 steel 1018 mild
96 T[i]=MS_FAM_STEEL; T[i+1]=MS_MAGIC_7850; T[i+2]=655; T[i+3]=1020; T[i+4]=MS_MAGIC_205000; i=i+MS_S // 10 steel MS_MAGIC_4140 alloy
97 T[i]=MS_FAM_ALUMINIUM; T[i+1]=MS_MAGIC_2700; T[i+2]=276; T[i+3]=310; T[i+4]=MS_MAGIC_68900; i=i+MS_S // 11 alu MS_MAGIC_6061-T6
98 T[i]=MS_FAM_ALUMINIUM; T[i+1]=MS_MAGIC_2680; T[i+2]=193; T[i+3]=228; T[i+4]=MS_MAGIC_70300; i=i+MS_S // 12 alu MS_MAGIC_5052-H32
99 T[i]=MS_FAM_IRON; T[i+1]=MS_MAGIC_7100; T[i+2]=310; T[i+3]=450; T[i+4]=MS_MAGIC_168000; i=i+MS_S // 13 ductile iron 65-45-12
100 T[i]=MS_FAM_MAGNESIUM; T[i+1]=MS_MAGIC_1810; T[i+2]=160; T[i+3]=230; T[i+4]=MS_MAGIC_45000; i=i+MS_S // 14 magnesium AZ91D
101 T[i]=MS_FAM_TITANIUM; T[i+1]=MS_MAGIC_4430; T[i+2]=880; T[i+3]=950; T[i+4]=MS_MAGIC_114000; i=i+MS_S // 15 Ti-6Al-4V
102 return 0
103}
104
105// ---- FAIL-CLOSED ACCESSORS (the nx_myotable law: an index outside the set is REFUSED, never answered
106// with the last row -- a phantom material that looks completely real is the bug that table cured) ------
107func ms_count() -> i64 { return MS_NM }
108func ms_ok(m: i64) -> i64 { if m < 0 { return 0 } if m >= MS_NM { return 0 } return 1 }
109func ms_family(T: *i64, m: i64) -> i64 { if ms_ok(m)==0 { return MS_BAD } return T[m*MS_S] }
110func ms_density(T: *i64, m: i64) -> i64 { if ms_ok(m)==0 { return MS_BAD } return T[m*MS_S+1] }
111func ms_yield_mpa(T: *i64, m: i64) -> i64 { if ms_ok(m)==0 { return MS_BAD } return T[m*MS_S+2] }
112func ms_ult_mpa(T: *i64, m: i64) -> i64 { if ms_ok(m)==0 { return MS_BAD } return T[m*MS_S+3] }
113func ms_mod_mpa(T: *i64, m: i64) -> i64 { if ms_ok(m)==0 { return MS_BAD } return T[m*MS_S+4] }
114// Pascals at the boundary, because nx_stress speaks Pa and the table stays readable in MPa
115func ms_yield_pa(T: *i64, m: i64) -> i64 { if ms_ok(m)==0 { return MS_BAD } return T[m*MS_S+2]*MS_MPA }
116func ms_ult_pa(T: *i64, m: i64) -> i64 { if ms_ok(m)==0 { return MS_BAD } return T[m*MS_S+3]*MS_MPA }
117
118// ★THE VERDICT, in the part's ACTUAL material. Same thresholds and same sealed values as nx_stress.
119func ms_verdict(T: *i64, m: i64, sigma_pa: i64) -> i64 {
120 if ms_ok(m) == 0 { return MS_UNKNOWN }
121 let y: i64 = ms_yield_pa(T,m)
122 let u: i64 = ms_ult_pa(T,m)
123 if sigma_pa < y/2 { return MS_SAFE }
124 if sigma_pa < y { return MS_WORKING }
125 if sigma_pa < u { return MS_YIELDED }
126 return MS_FRACTURED
127}
128
129// ★MASS FROM GEOMETRY -- the other half of 'material and everything'. Volume in mm3 (the twin's own unit
130// via nx_unitconv's fx256mm grid), result in MILLIGRAMS so it stays integer-exact:
131// kg/m3 x mm3 = kg/m3 x 1e-9 m3 = 1e-9 kg = 1e-6 g = 1e-3 mg => mg = density*vol/1000
132func ms_mass_mg(T: *i64, m: i64, vol_mm3: i64) -> i64 {
133 if ms_ok(m) == 0 { return MS_BAD }
134 if vol_mm3 < 0 { return MS_BAD }
135 return ms_density(T,m) * vol_mm3 / 1000
136}
137
138func ms_name(m: i64) -> *u8 {
139 if m == 0 { return "PLA" as *u8 }
140 if m == 1 { return "PETG" as *u8 }
141 if m == 2 { return "ABS" as *u8 }
142 if m == 3 { return "ASA" as *u8 }
143 if m == 4 { return "PC" as *u8 }
144 if m == 5 { return "TPU-95A" as *u8 }
145 if m == 6 { return "NYLON" as *u8 }
146 if m == 7 { return "PA-CF" as *u8 }
147 if m == 8 { return "PEEK" as *u8 }
148 if m == 9 { return "steel-1018-mild" as *u8 }
149 if m == 10 { return "steel-4140-alloy" as *u8 }
150 if m == 11 { return "aluminium-6061-T6" as *u8 }
151 if m == 12 { return "aluminium-5052-H32" as *u8 }
152 if m == 13 { return "ductile-iron-65-45-12" as *u8 }
153 if m == 14 { return "magnesium-AZ91D" as *u8 }
154 if m == 15 { return "titanium-Ti-6Al-4V" as *u8 }
155 return "REFUSED-UNKNOWN-MATERIAL" as *u8
156}
157
158func main(argc: i64, argv: *i64) -> i64 {
159 let ctr: *i64 = gv_ctr()
160 gv_head("nx_matstruct selftest -- structural materials as one fail-closed table; metals exist now" as *u8)
161 let T: *i64 = sys_mmap(MS_NM*MS_S*8) as *i64
162 ms_table(T)
163
164 // T1 the metals the car twin needs are actually present and in the right families
165 var t1: i64 = 1
166 if ms_family(T,9) != MS_FAM_STEEL { t1 = 0 }
167 if ms_family(T,11) != MS_FAM_ALUMINIUM { t1 = 0 }
168 if ms_family(T,13) != MS_FAM_IRON { t1 = 0 }
169 if ms_family(T,15) != MS_FAM_TITANIUM { t1 = 0 }
170 if ms_count() != MS_NM { t1 = 0 }
171 gv_check("T1 the structural metals EXIST and carry their family" as *u8, t1, ctr)
172
173 // T2 fail-closed: an unknown material is REFUSED, never answered with the last row
174 var t2: i64 = 1
175 if ms_density(T, MS_NM) != MS_BAD { t2 = 0 }
176 if ms_yield_mpa(T, 999) != MS_BAD { t2 = 0 }
177 if ms_family(T, 0-1) != MS_BAD { t2 = 0 }
178 if ms_mass_mg(T, 999, 1000) != MS_BAD { t2 = 0 }
179 if ms_verdict(T, 999, 1000) != MS_UNKNOWN { t2 = 0 }
180 gv_check("T2 FAIL-CLOSED: an unknown material is REFUSED and the verdict says UNKNOWN" as *u8, t2, ctr)
181
182 // ★★★T3 THE DISCRIMINATING TOOTH. The SAME stress in steel and in aluminium must give DIFFERENT
183 // verdicts, or the material is decoration and the whole registry is a lookup nobody needed.
184 // 300 MPa: comfortably inside 1018 steel's design range, PAST 6061-T6 aluminium's yield.
185 let sig: i64 = 300*MS_MPA
186 let vsteel: i64 = ms_verdict(T, 9, sig)
187 let valu: i64 = ms_verdict(T, 11, sig)
188 var t3: i64 = 0
189 if vsteel == MS_WORKING { if valu == MS_YIELDED { t3 = 1 } }
190 gv_check("T3 SAME STRESS, DIFFERENT MATERIAL, DIFFERENT VERDICT (steel WORKING, alu YIELDED)" as *u8, t3, ctr)
191
192 // T4 the verdict walks forward with rising stress and never backwards
193 var t4: i64 = 1
194 if ms_verdict(T, 9, 10*MS_MPA) != MS_SAFE { t4 = 0 }
195 if ms_verdict(T, 9, 300*MS_MPA) != MS_WORKING { t4 = 0 }
196 if ms_verdict(T, 9, 400*MS_MPA) != MS_YIELDED { t4 = 0 }
197 if ms_verdict(T, 9, 500*MS_MPA) != MS_FRACTURED { t4 = 0 }
198 gv_check("T4 MONOTONE: rising stress walks SAFE -> WORKING -> YIELDED -> FRACTURED" as *u8, t4, ctr)
199
200 // T5 physics ordering: a registry that let aluminium outrank steel would be worse than none
201 var t5: i64 = 1
202 if ms_yield_mpa(T,9) <= ms_yield_mpa(T,11) { t5 = 0 } // steel stronger than aluminium
203 if ms_yield_mpa(T,11) <= ms_yield_mpa(T,0) { t5 = 0 } // aluminium stronger than PLA
204 if ms_density(T,9) <= ms_density(T,11) { t5 = 0 } // steel denser than aluminium
205 if ms_density(T,11) <= ms_density(T,0) { t5 = 0 } // aluminium denser than PLA
206 if ms_mod_mpa(T,9) <= ms_mod_mpa(T,11) { t5 = 0 } // steel stiffer than aluminium
207 gv_check("T5 PHYSICS ORDER holds: steel > aluminium > polymer in strength, density and stiffness" as *u8, t5, ctr)
208
209 // ★★T6 NOTHING WAS STRIPPED. Indices 0..8 must reproduce nx_stress's NINE shipped filament values
210 // EXACTLY -- checked against the literals it ships, so a transcription slip cannot pass. This is what
211 // makes the registry a strict SUPERSET rather than a replacement that quietly loses the print lane.
212 var t6: i64 = 1
213 if ms_yield_mpa(T,0) != 50 { t6 = 0 }
214 if ms_ult_mpa(T,0) != 65 { t6 = 0 }
215 if ms_yield_mpa(T,1) != 45 { t6 = 0 }
216 if ms_ult_mpa(T,1) != 50 { t6 = 0 }
217 if ms_yield_mpa(T,2) != 35 { t6 = 0 }
218 if ms_yield_mpa(T,3) != 35 { t6 = 0 }
219 if ms_yield_mpa(T,4) != 65 { t6 = 0 }
220 if ms_ult_mpa(T,4) != 70 { t6 = 0 }
221 if ms_yield_mpa(T,5) != 30 { t6 = 0 }
222 if ms_yield_mpa(T,6) != 60 { t6 = 0 }
223 if ms_ult_mpa(T,6) != 80 { t6 = 0 }
224 if ms_yield_mpa(T,7) != 80 { t6 = 0 }
225 if ms_ult_mpa(T,7) != 120 { t6 = 0 }
226 if ms_yield_mpa(T,8) != 95 { t6 = 0 }
227 if ms_ult_mpa(T,8) != 100 { t6 = 0 }
228 gv_check("T6 SUPERSET: all NINE nx_stress filament yields reproduced EXACTLY, nothing stripped" as *u8, t6, ctr)
229
230 // ★★T7 ANCHORED TO AN ABSOLUTE PHYSICAL QUANTITY, not to another of my own readings. A cubic metre
231 // (1e9 mm3) of mild steel weighs 7870 kg and of 6061 aluminium 2700 kg. ★nx_facs' law: every
232 // instrument needs at least ONE tooth tied to a named external quantity, because a self-consistent
233 // comparison cannot catch a wrong field mapping -- seven of its eight teeth missed exactly that.
234 let cubic_m: i64 = MS_MAGIC_1000000000
235 let msteel: i64 = ms_mass_mg(T, 9, cubic_m)
236 let malu: i64 = ms_mass_mg(T, 11, cubic_m)
237 var t7: i64 = 0
238 if msteel == MS_MAGIC_7870000000 { if malu == MS_MAGIC_2700000000 { t7 = 1 } }
239 gv_check("T7 ABSOLUTE: 1 m3 of steel = 7870 kg and of aluminium = 2700 kg, computed not asserted" as *u8, t7, ctr)
240
241 gv_puts("\n A 2 mm wall carrying 300 MPa, by material:\n" as *u8)
242 var m: i64 = 9
243 while m < MS_NM {
244 gv_puts(" " as *u8)
245 gv_puts(ms_name(m))
246 gv_puts(" yield " as *u8)
247 gv_num(ms_yield_mpa(T,m))
248 gv_puts(" MPa verdict " as *u8)
249 gv_num(ms_verdict(T, m, sig))
250 gv_puts(" (0 safe 1 working 2 yielded 3 fractured)\n" as *u8)
251 m = m + 1
252 }
253 gv_puts("\n HONEST COVERAGE: " as *u8)
254 gv_num(ms_count())
255 gv_puts(" materials -- 9 polymers carried over from nx_stress plus 7 structural metals. That is the common automotive set, NOT a materials database: no tempers beyond those listed, no temperature dependence, no fatigue curve, no anisotropy. The FLIP CHAIN can now close -- nx_meshseg3d parts, nx_meshthick min wall, THIS verdict in the real alloy, joined to nx_underbuilt_board's NHTSA data -- and the wiring into nx_stress is a SEPARATE announced step because that primitive is shared with the print lane.\n" as *u8)
256
257 return gv_verdict("MATSTRUCT-GATE" as *u8, ctr,
258 "structural materials as one fail-closed table; the same stress in steel and aluminium now returns different verdicts" as *u8)
259}