code wiki / (root) / nx_gcode_extrude.nx

nx_gcode_extrude.nx source

↩ module page · 142 lines · 6218 B

1// nx_gcode_extrude.nx -- extrusion-distance math for FDM G-code. 2// 3// Computes how many millimetres of filament must be fed for a given 4// XY move at given line width + layer height to satisfy volume 5// conservation (incompressible deposit): 6// 7// extruded_volume_mm3 = line_width_mm * layer_height_mm * move_length_mm 8// filament_cross_section = π * (filament_radius_mm)^2 9// E_delta_mm = extruded_volume_mm3 / filament_cross_section 10// 11// Per cardinal feedback-engineering-sciences-bits-up-3d-print-first: 12// this primitive is MATH (geometric volume) + PHYSICS (continuity 13// equation: mass-in == mass-out for incompressible flow). Future 14// physics refinements where this composes: 15// - Rheology: shear-rate-dependent viscosity (PLA at 210°C), 16// pressure-drop through nozzle, max sustainable flow rate 17// - Thermal: melt-zone heat budget bounding max_flow_mm3s 18// - Material science: glass-transition temperature gating bridge 19// fan timing 20// All of those would refine THIS primitive's outputs. v1 ships the 21// volume-conservation core; further physics queued. 22// 23// Coordinate convention: Q14 fixed-point throughout (1 unit = 1/16384). 24// 25// Reusability: this primitive is also valid for CNC paste extruders, 26// food printers, concrete printers, bioprinters -- anywhere a moving 27// nozzle deposits a controlled volume per unit length. Hence runtime/ 28// (multi-purpose), not nishi-print/ (3D-print-specific) hub. 29// 30// Numerical robustness: 31// - Uses nx_isqrt for move-length computation (no float sqrt). 32// - Uses nx_muldiv_i64 for intermediate products that would 33// overflow plain i64 (filament_xsect computation can reach 2^44). 34// 35// license_tier: ORIGINAL 36 37import "nx_syscalls.nx" 38import "nx_isqrt.nx" 39import "nx_i128.nx" 40 41const NX_PI_Q14: i64 = 51472 // π × 16384, rounded 42const NX_GCEX_Q14_ONE: i64 = 16384 43 44// ===== verdicts ==================================================== 45 46const NX_GCEX_OK: i64 = 0 47const NX_GCEX_ERR_BAD_GEOMETRY: i64 = 1 48const NX_GCEX_ERR_BAD_FILAMENT: i64 = 2 49 50func nx_gcex_verdict_name(v: i64) -> *u8 { 51 if v == NX_GCEX_OK { return "OK" } 52 if v == NX_GCEX_ERR_BAD_GEOMETRY { return "BAD_GEOMETRY" } 53 if v == NX_GCEX_ERR_BAD_FILAMENT { return "BAD_FILAMENT" } 54 return "UNKNOWN" 55} 56 57// ===== filament cross-section ====================================== 58// 59// Returns the filament's cross-sectional area in Q14 mm². Caller 60// passes filament diameter in micrometres (per nx_machine_graph's 61// filament_dia_um field convention). 62// 63// Algebra (in Q14): 64// radius_q14 = (dia_um * Q14_ONE / 2) / 1000 (µm -> mm in Q14) 65// xsect_q14 = π_q14 * radius_q14 * radius_q14 / (Q14_ONE * Q14_ONE) 66// 67// For 1.75mm filament: dia_um=1750, radius_q14=14336, xsect ≈ 2.405 mm² 68// (Q14 value ≈ 39408). 69 70func nx_gcex_filament_xsect_q14(filament_dia_um: i64) -> i64 { 71 if filament_dia_um <= 0 { return 0 } 72 // radius in Q14 mm: dia_um/2 * Q14_ONE / 1000. Multiply first 73 // to preserve precision (875 * 16384 = 14336000 fits in i64). 74 let half_um: i64 = filament_dia_um / 2 75 let radius_q14: i64 = (half_um * NX_GCEX_Q14_ONE) / 1000 76 // xsect = π * r² / Q14_ONE (because π is Q14, r is Q14, r*r is 77 // Q28, divide by Q14 to get back to Q14). Use muldiv for the 78 // π * r * r path to keep intermediates safe. 79 let r_squared_q28: i64 = radius_q14 * radius_q14 80 let xsect_q14: i64 = nx_muldiv_i64(NX_PI_Q14, r_squared_q28, NX_GCEX_Q14_ONE * NX_GCEX_Q14_ONE) 81 return xsect_q14 82} 83 84// ===== move length ================================================= 85// 86// 2D Euclidean distance between two Q14 points. Returns Q14 mm. 87// Uses nx_isqrt; result is exact to integer Q14 (truncated). 88 89func nx_gcex_move_length_q14(x1: i64, y1: i64, x2: i64, y2: i64) -> i64 { 90 let dx: i64 = x2 - x1 91 let dy: i64 = y2 - y1 92 // dx, dy are Q14; squares are Q28; sum is Q28; nx_isqrt of Q28 93 // gives Q14. 94 let lsq: i64 = dx * dx + dy * dy 95 return nx_isqrt(lsq) 96} 97 98// ===== extrusion delta ============================================= 99// 100// Returns the additional mm of filament (Q14) to feed for a move of 101// `move_length_q14` mm at the given line width + layer height. 102// 103// E_delta_q14 = volume_q14 * Q14_ONE / filament_xsect_q14 104// 105// where volume_q14 = (line_width_q14 * layer_height_q14 / Q14_ONE) 106// * (move_length_q14 / Q14_ONE) 107// * Q14_ONE 108// 109// Combined: E_delta_q14 = (line_width * layer_height * move_length) 110// / (Q14_ONE * filament_xsect) 111// 112// Uses nx_muldiv pattern to avoid intermediate overflow on the 113// triple product (worst case slicer-scale ~2^42, fits i64 fine, but 114// safer via i128 if line_width or move_length is unusual). 115 116func nx_gcex_E_delta_q14(line_width_q14: i64, layer_height_q14: i64, 117 move_length_q14: i64, 118 filament_xsect_q14: i64) -> i64 { 119 if filament_xsect_q14 <= 0 { return 0 } 120 if move_length_q14 <= 0 { return 0 } 121 // Compute (line_width * layer_height) first; this is mm² in Q28. 122 let cross_area_q28: i64 = line_width_q14 * layer_height_q14 123 // (cross_area_q28 / Q14_ONE) * move_length_q14 = volume in Q28. 124 // We want E_delta in Q14 mm: volume_q28 / (xsect_q14 * Q14_ONE) 125 // = volume_q28 / filament_xsect * 1/Q14_ONE 126 // Equivalent: muldiv(cross_area_q28, move_length_q14, filament_xsect_q14 * Q14_ONE) 127 let denom: i64 = filament_xsect_q14 * NX_GCEX_Q14_ONE 128 return nx_muldiv_i64(cross_area_q28, move_length_q14, denom) 129} 130 131// ===== one-shot helper ============================================= 132// 133// Convenience: compute E_delta for a segment between two points. 134// Composes nx_gcex_move_length_q14 + nx_gcex_E_delta_q14. 135 136func nx_gcex_E_delta_for_move(x1: i64, y1: i64, x2: i64, y2: i64, 137 line_width_q14: i64, layer_height_q14: i64, 138 filament_xsect_q14: i64) -> i64 { 139 let move_len: i64 = nx_gcex_move_length_q14(x1, y1, x2, y2) 140 return nx_gcex_E_delta_q14(line_width_q14, layer_height_q14, 141 move_len, filament_xsect_q14) 142}