code wiki / (root) / nx_estimate.nx

nx_estimate.nx source

↩ module page · 149 lines · 6303 B

1// nx_estimate.nx -- pre-print operator visibility (filament + time). 2// 3// Closes the operator question "is this a 10-min print or 18 hours?" 4// BEFORE committing to a slice + upload. Industry slicers show 5// time + filament in their preview pane; substrate provides the 6// same info without the full slice cost. 7// 8// Algorithm (rough but fast, sub-millisecond): 9// 1. Compute mesh bbox volume (mm³) 10// 2. Estimate solid printed volume = bbox × (perim_factor + (1 - 11// perim_factor) × infill_pct / 100) 12// 3. Filament_mm = solid_volume / filament_cross_section_mm² 13// 4. Print_minutes = solid_volume / typical_flow_mm³/s / 60 14// 15// More accurate version (queued v2): run nx_print_session_run + read 16// emitter.E_acc_q14 + count G-code lines. v2 is exact but slow; 17// v1 is rough but instantaneous (operator can call BEFORE deciding 18// to commit to the full slice). 19// 20// Verdicts surface operator-actionable warnings: 21// OK -- print is reasonable 22// WARN_LONG -- > 24h estimated (operator should reconsider scope) 23// WARN_FIL -- > 250m estimated (typical PLA spool is ~330m for 1kg) 24// WARN_TINY -- < 1g of filament (probably too small to print) 25// 26// Per [[feedback-engineering-sciences-bits-up-3d-print-first]]: 27// pre-print visibility is operator's right; substrate composes 28// nx_mesh bbox + nx_gcode_extrude filament-cross-section helper to 29// answer without third-party slicer dependency. 30// 31// license_tier: ORIGINAL 32 33import "nx_syscalls.nx" 34import "nx_mesh.nx" 35import "nx_mesh_print_check.nx" 36import "nx_machine_graph.nx" 37import "nx_material_profile.nx" 38import "nx_gcode_extrude.nx" 39const NX_MAGIC_1440: i64 = 1440 40const NX_MAGIC_250000: i64 = 250000 41 42const NX_EST_Q14: i64 = 16384 43 44// ===== sealed-enum verdicts ======================================== 45 46const NX_EST_OK: i64 = 0 47const NX_EST_WARN_LONG: i64 = 1 48const NX_EST_WARN_FIL: i64 = 2 49const NX_EST_WARN_TINY: i64 = 3 50const NX_EST_ERR_INPUT: i64 = 4 51 52func nx_est_verdict_name(v: i64) -> *u8 { 53 if v == NX_EST_OK { return "OK" } 54 if v == NX_EST_WARN_LONG { return "WARN_LONG" } 55 if v == NX_EST_WARN_FIL { return "WARN_FIL" } 56 if v == NX_EST_WARN_TINY { return "WARN_TINY" } 57 if v == NX_EST_ERR_INPUT { return "ERR_INPUT" } 58 return "UNKNOWN" 59} 60 61// ===== result struct =============================================== 62 63struct NxPrintEstimate { 64 filament_mm: i64, // estimated filament needed (integer mm) 65 print_minutes: i64, // estimated print time (integer minutes) 66 volume_mm3: i64, // estimated solid printed volume 67 verdict: i64, 68} 69 70const NX_ESTIMATE_BYTES: i64 = 32 71 72// ===== main entry ================================================= 73// 74// Returns rough pre-print estimate without running the full slicer. 75// For a typical print: 76// shell takes ~5-10% of bbox volume (perim_factor 0.08 default) 77// interior infill takes the rest at infill_pct density 78// 79// Composes nx_mesh_bbox_compute + nx_gcex_filament_xsect_q14. 80 81func nx_estimate_print(mesh: *NxMesh, 82 machine: *NxMachineGraph, 83 material: *NxMaterialProfile, 84 infill_density_pct: i64) -> *NxPrintEstimate { 85 let r: *NxPrintEstimate = (sys_mmap(NX_ESTIMATE_BYTES)) as *NxPrintEstimate 86 r.filament_mm = 0 87 r.print_minutes = 0 88 r.volume_mm3 = 0 89 r.verdict = NX_EST_OK 90 91 if (mesh as i64) == 0 { r.verdict = NX_EST_ERR_INPUT; return r } 92 if (machine as i64) == 0 { r.verdict = NX_EST_ERR_INPUT; return r } 93 if (material as i64) == 0 { r.verdict = NX_EST_ERR_INPUT; return r } 94 if infill_density_pct < 0 { r.verdict = NX_EST_ERR_INPUT; return r } 95 if infill_density_pct > 100 { r.verdict = NX_EST_ERR_INPUT; return r } 96 97 // Bbox dimensions in mm. 98 let bbox: *NxMeshBBox = nx_mesh_bbox_compute(mesh) 99 if bbox.valid != 1 { r.verdict = NX_EST_ERR_INPUT; return r } 100 101 let extent_x_mm: i64 = (bbox.max_x - bbox.min_x) / NX_EST_Q14 102 let extent_y_mm: i64 = (bbox.max_y - bbox.min_y) / NX_EST_Q14 103 let extent_z_mm: i64 = (bbox.max_z - bbox.min_z) / NX_EST_Q14 104 let bbox_volume_mm3: i64 = extent_x_mm * extent_y_mm * extent_z_mm 105 106 // Solid volume estimate. Shell ~8% of bbox + infill fraction of 107 // remainder. Heuristic, accurate within ~30% for typical prints. 108 let perim_fraction_pct: i64 = 8 109 let infill_fraction_pct: i64 = (100 - perim_fraction_pct) * infill_density_pct / 100 110 let solid_pct: i64 = perim_fraction_pct + infill_fraction_pct 111 let solid_volume_mm3: i64 = bbox_volume_mm3 * solid_pct / 100 112 r.volume_mm3 = solid_volume_mm3 113 114 // Filament length: volume / cross-section. filament_xsect_q14 115 // is in Q14 mm²; convert to mm² for division. 116 let xsect_q14: i64 = nx_gcex_filament_xsect_q14(machine.filament_dia_um) 117 if xsect_q14 <= 0 { r.verdict = NX_EST_ERR_INPUT; return r } 118 // xsect_mm2_scaled = xsect_q14 / Q14 (~2 mm² for 1.75mm filament) 119 // filament_mm = solid_volume_mm3 / xsect_mm2_scaled 120 // = solid_volume_mm3 * Q14 / xsect_q14 121 if solid_volume_mm3 > 0 { 122 r.filament_mm = (solid_volume_mm3 * NX_EST_Q14) / xsect_q14 123 } 124 125 // Print time estimate. Volume / sustainable flow gives total 126 // extrusion seconds; add ~25% overhead for travels + heating + 127 // first layer. Use min(material.typical_max_flow_mm3s, 128 // machine.max_flow_mm3s) as the sustainable flow rate. 129 var flow_mm3s: i64 = machine.max_flow_mm3s 130 if material.typical_max_flow_mm3s < flow_mm3s { 131 flow_mm3s = material.typical_max_flow_mm3s 132 } 133 if flow_mm3s > 0 { 134 let extrude_sec: i64 = solid_volume_mm3 / flow_mm3s 135 let with_overhead_sec: i64 = (extrude_sec * 125) / 100 136 r.print_minutes = with_overhead_sec / 60 137 } 138 139 // Verdict tiers: 140 if r.print_minutes > NX_MAGIC_1440 { r.verdict = NX_EST_WARN_LONG; return r } 141 if r.filament_mm > NX_MAGIC_250000 { r.verdict = NX_EST_WARN_FIL; return r } 142 // Honest "not worth firing up the printer" cutoff: < 50mm filament 143 // (~0.15g for PLA). A 10mm calibration cube is ~870mm so well 144 // above; only sub-finger-sized doodads register WARN_TINY. 145 if r.filament_mm < 50 { 146 if r.volume_mm3 > 0 { r.verdict = NX_EST_WARN_TINY; return r } 147 } 148 return r 149}