code wiki / _hdl_build / nx_jam_check.nx

nx_jam_check.nx source

↩ module page · 196 lines · 9341 B

1// nx_jam_check.nx -- JAM-PHYSICS PRE-FLIGHT GATE (operator: the QIDI X-Max 3 "constantly jams"; 2// identify WHERE the hardware is failing to prevent it, "aka too cold or what"). The #1 jam physics: 3// a move DEMANDS volumetric flow (mm^3/s) = extruded filament volume / move time; the hotend can only 4// MELT a temperature-dependent maximum. Demand > capacity -> under-extrusion -> heatbreak back-pressure 5// -> JAM. QIDIStudio/slicers don't gate on this; we do, per G-code line, with the exact cause and fix. 6// 7// Scans G-code text: tracks M104/M109 S<temp> + G1 F/X/Y/E; for each extruding move computes 8// flow in MILLI-mm^3/s (integer fixed-point, 1.75mm filament cross-section = 2.405 mm^2) and 9// compares against a DATA-DRIVEN capacity table (Rule 11: profile data, not magic numbers). 10// Verdicts name the line, the demanded flow, the capacity, and the FIX (temp to set OR that 11// it's beyond the hotend = slow down). Also counts retraction churn (heat-creep advisory). 12// 13// HONEST BOUNDARY: the PLA capacity table is PROVISIONAL (community-class values); the team's 14// A7 calibration print measures OUR X-Max 3's real curve and replaces the table -- the GATE 15// LOGIC + parser + math are exact and KAT-tested here regardless of the table values. 16// LAWS: struct-free, integer-only, no floats; composes with nx_klipper_gcode_validator (syntax) 17// as the physics gate beside it. license_tier: ORIGINAL 18import "nx_syscalls.nx" 19import "nx_vecmath.nx" 20const JC_MAGIC_6000: i64 = 6000 21const JC_MAGIC_8000: i64 = 8000 22const JC_MAGIC_10000: i64 = 10000 23const JC_MAGIC_11500: i64 = 11500 24const JC_MAGIC_13000: i64 = 13000 25const JC_MAGIC_60000: i64 = 60000 26 27// verdict codes (structured, machine-readable) 28const JC_OK: i64 = 0 29const JC_TOO_COLD: i64 = 1 // demand > capacity at set temp, but a hotter table entry covers it 30const JC_TOO_FAST: i64 = 2 // demand > capacity even at the table's max temp -> must slow down 31const JC_NO_TEMP: i64 = 3 // extruding move before any M104/M109 -> cold extrusion = certain jam 32 33// filament cross-section for 1.75mm: pi * 0.875^2 = 2.40528 mm^2 -> 2405 micro-mm^2-per-um scale: 34// volume_milli_mm3 = e_um * 2405 / 1000 35const JC_XSEC_MILLI: i64 = 2405 36 37// ---- PROVISIONAL PLA capacity table (temp C -> max flow in milli-mm^3/s) ---- 38// Community-class values for a stock high-flow-ish 0.4mm hotend; REPLACE with A7-measured curve. 39const JC_NTAB: i64 = 5 40func jc_tab_temp(i: i64) -> i64 { 41 if i == 0 { return 190 } 42 if i == 1 { return 200 } 43 if i == 2 { return 210 } 44 if i == 3 { return 220 } 45 return 230 46} 47func jc_tab_cap(i: i64) -> i64 { 48 if i == 0 { return JC_MAGIC_6000 } 49 if i == 1 { return JC_MAGIC_8000 } 50 if i == 2 { return JC_MAGIC_10000 } 51 if i == 3 { return JC_MAGIC_11500 } 52 return JC_MAGIC_13000 53} 54 55// capacity at temp (linear interpolation; clamp below/above table ends) 56func jc_capacity_milli(temp_c: i64) -> i64 { 57 if temp_c <= jc_tab_temp(0) { return jc_tab_cap(0) } 58 if temp_c >= jc_tab_temp(JC_NTAB - 1) { return jc_tab_cap(JC_NTAB - 1) } 59 var i: i64 = 1 60 while i < JC_NTAB { 61 if temp_c <= jc_tab_temp(i) { 62 let t0: i64 = jc_tab_temp(i - 1); let t1: i64 = jc_tab_temp(i) 63 let c0: i64 = jc_tab_cap(i - 1); let c1: i64 = jc_tab_cap(i) 64 return c0 + ((c1 - c0) * (temp_c - t0)) / (t1 - t0) 65 } 66 i = i + 1 67 } 68 return jc_tab_cap(JC_NTAB - 1) 69} 70 71// lowest TABLE temp whose capacity covers the demanded flow; -1 if none (must slow down) 72func jc_temp_for_flow(flow_milli: i64) -> i64 { 73 var i: i64 = 0 74 while i < JC_NTAB { 75 if jc_tab_cap(i) >= flow_milli { return jc_tab_temp(i) } 76 i = i + 1 77 } 78 return 0 - 1 79} 80 81// integer sqrt (Newton) for XY distance 82func jc_isqrt(x: i64) -> i64 { return vm_isqrt(x) } 83 84// ---- G-code text scanning helpers (integer micro-units: 1 unit = 0.001 of the printed token) ---- 85 86// parse a decimal number "123.456" / "-0.33" at pos -> value in thousandths; advances pos 87func jc_parse_milli(src: *u8, pos: *i64, n: i64) -> i64 { 88 var p: i64 = pos[0] 89 var neg: i64 = 0 90 if p < n { if src[p] == (45 as u8) { neg = 1; p = p + 1 } } 91 var ip: i64 = 0 92 while p < n { 93 let c: i64 = src[p] as i64 94 if c >= 48 && c <= 57 { ip = ip * 10 + (c - 48); p = p + 1 } else { break } 95 } 96 var fr: i64 = 0 97 var fd: i64 = 0 98 if p < n { if src[p] == (46 as u8) { // '.' 99 p = p + 1 100 while p < n { 101 let d: i64 = src[p] as i64 102 if d >= 48 && d <= 57 { 103 if fd < 3 { fr = fr * 10 + (d - 48); fd = fd + 1 } 104 p = p + 1 // consume extra digits beyond 3 105 } else { break } 106 } 107 } } 108 while fd < 3 { fr = fr * 10; fd = fd + 1 } // right-pad to thousandths 109 pos[0] = p 110 var v: i64 = ip * 1000 + fr 111 if neg == 1 { v = 0 - v } 112 return v 113} 114 115// ---- the gate: scan gcode[0..n), write worst verdict + detail into out (i64[8]) ---- 116// out[0]=verdict out[1]=line out[2]=flow_milli out[3]=cap_milli out[4]=fix_temp out[5]=n_extr_moves 117// out[6]=n_retracts out[7]=worst_flow_milli ; returns out[0] 118func jc_scan(g: *u8, n: i64, out: *i64) -> i64 { 119 var temp: i64 = 0 - 1 // no temp set yet 120 var feed_mm_min: i64 = 0 // F (mm/min, milli-scaled /1000) 121 var x: i64 = 0; var y: i64 = 0 // current pos, um 122 var line: i64 = 1 123 var worst: i64 = JC_OK 124 var wline: i64 = 0; var wflow: i64 = 0; var wcap: i64 = 0; var wfix: i64 = 0 125 var nextr: i64 = 0; var nretr: i64 = 0; var maxflow: i64 = 0 126 var i: i64 = 0 127 while i < n { 128 // line start: classify 129 var isG1: i64 = 0 130 var isM10x: i64 = 0 131 if g[i] == (77 as u8) { // 'M' 132 if i + 4 < n { 133 if g[i+1] == (49 as u8) { if g[i+2] == (48 as u8) { 134 if g[i+3] == (52 as u8) { isM10x = 1 } // M104 135 if g[i+3] == (57 as u8) { isM10x = 1 } // M109 136 } } 137 } 138 } 139 if g[i] == (71 as u8) { if i + 1 < n { if g[i+1] == (49 as u8) { isG1 = 1 } } } // 'G1' 140 // walk the line, harvesting words 141 var nx2: i64 = x; var ny: i64 = y; var e_milli: i64 = 0; var has_e: i64 = 0 142 var j: i64 = i 143 while j < n { 144 if g[j] == (10 as u8) { break } // end of line 145 let c: i64 = g[j] as i64 146 if isM10x == 1 { if c == 83 { // 'S' 147 let pb: *i64 = sys_mmap(16) as *i64; pb[0] = j + 1 148 temp = jc_parse_milli(g, pb, n) / 1000 149 j = pb[0] - 1 150 } } 151 if isG1 == 1 { 152 if c == 70 { let pb1: *i64 = sys_mmap(16) as *i64; pb1[0] = j + 1; feed_mm_min = jc_parse_milli(g, pb1, n); j = pb1[0] - 1 } // F 153 if c == 88 { let pb2: *i64 = sys_mmap(16) as *i64; pb2[0] = j + 1; nx2 = jc_parse_milli(g, pb2, n); j = pb2[0] - 1 } // X -> um 154 if c == 89 { let pb3: *i64 = sys_mmap(16) as *i64; pb3[0] = j + 1; ny = jc_parse_milli(g, pb3, n); j = pb3[0] - 1 } // Y -> um 155 if c == 69 { let pb4: *i64 = sys_mmap(16) as *i64; pb4[0] = j + 1; e_milli = jc_parse_milli(g, pb4, n); has_e = 1; j = pb4[0] - 1 } // E -> um 156 } 157 j = j + 1 158 } 159 // evaluate the move 160 if isG1 == 1 { if has_e == 1 { 161 if e_milli < 0 { nretr = nretr + 1 } 162 if e_milli > 0 { 163 nextr = nextr + 1 164 let dx: i64 = nx2 - x; let dy: i64 = ny - y 165 let dist_um: i64 = jc_isqrt(dx*dx + dy*dy) 166 if dist_um > 0 { if feed_mm_min > 0 { 167 // time_ms = dist_um * 60 / (feed_mm_min/1000) = dist_um * 60000 / feed_milli 168 let time_ms: i64 = (dist_um * JC_MAGIC_60000) / feed_mm_min 169 if time_ms > 0 { 170 let vol_milli: i64 = (e_milli * JC_XSEC_MILLI) / 1000 171 let flow_milli: i64 = (vol_milli * 1000) / time_ms 172 if flow_milli > maxflow { maxflow = flow_milli } 173 if temp < 0 { 174 if worst < JC_NO_TEMP { worst = JC_NO_TEMP; wline = line; wflow = flow_milli; wcap = 0; wfix = jc_tab_temp(0) } 175 } 176 if temp >= 0 { 177 let cap: i64 = jc_capacity_milli(temp) 178 if flow_milli > cap { 179 let fix: i64 = jc_temp_for_flow(flow_milli) 180 if fix >= 0 { if worst < JC_TOO_COLD { worst = JC_TOO_COLD; wline = line; wflow = flow_milli; wcap = cap; wfix = fix } } 181 if fix < 0 { if worst < JC_TOO_FAST { worst = JC_TOO_FAST; wline = line; wflow = flow_milli; wcap = cap; wfix = 0 - 1 } } 182 } 183 } 184 } 185 } } 186 } 187 } } 188 x = nx2; y = ny 189 // advance to next line 190 while i < n { if g[i] == (10 as u8) { i = i + 1; break } i = i + 1 } 191 line = line + 1 192 } 193 out[0] = worst; out[1] = wline; out[2] = wflow; out[3] = wcap 194 out[4] = wfix; out[5] = nextr; out[6] = nretr; out[7] = maxflow 195 return worst 196}