"""Job cost and time estimation from STL geometry.""" import math import os # g/cm³ per material _DENSITY: dict[str, float] = { "PLA": 1.24, "PETG": 1.27, "ABS": 1.04, "ASA": 1.07, "TPU": 1.20, "Nylon": 1.14, "PC": 1.20, } _DEFAULT_DENSITY = 1.24 # Volume (cm³) at which MATERIAL_FACTOR applies exactly. # Calibrated to Bambu Lab Studio: 26 cm³ lid → 50 % factor → 16.2 g (actual 16.24 g). _REF_VOLUME = 25.0 # Factor change per decade of volume. # Each 10× increase in volume → factor drops by this amount (models get fluffier). # Each 10× decrease → factor rises by this amount (models get denser). # 0.15 gives: 1 cm³ → +0.21, 250 cm³ → −0.15 relative to base. _SIZE_SLOPE = 0.15 def _effective_factor(volume_ccm: float, base: float) -> float: """Scale material factor by model size using log-linear interpolation.""" factor = base - math.log10(volume_ccm / _REF_VOLUME) * _SIZE_SLOPE return round(max(0.25, min(0.85, factor)), 3) def estimate(volume_ccm: float, material: str | None) -> dict: """ Returns a cost/time estimate dict for one job. MATERIAL_FACTOR (env, default 0.50): Base fraction of model volume that becomes filament, calibrated at 25 cm³. Scaled up for small models (wall-dominated) and down for large models (infill-dominated). Approximate scale: ~1 cm³ → ×1.4 (tiny clip, almost all walls) ~25 cm³ → ×1.0 (reference — validated against slicer) ~250 cm³ → ×0.7 (large box, infill dominates) PRINT_SPEED_G_PER_HOUR (env, default 32): ~32 g/h matches Bambu Lab at normal quality; use ~20 for slower printers. """ vat_rate = float(os.environ.get("VAT_RATE", 19)) price_per_kg = float(os.environ.get("FILAMENT_PRICE_PER_KG", 15)) base_factor = float(os.environ.get("MATERIAL_FACTOR", 0.50)) print_speed = float(os.environ.get("PRINT_SPEED_G_PER_HOUR", 32)) density = _DENSITY.get(material or "", _DEFAULT_DENSITY) eff_factor = _effective_factor(volume_ccm, base_factor) material_ccm = volume_ccm * eff_factor weight_g = material_ccm * density print_minutes = round((weight_g / print_speed) * 60) filament_cost = weight_g / 1000 * price_per_kg after_misprint = filament_cost * 1.20 net_price = after_misprint * 1.30 gross_price = net_price * (1 + vat_rate / 100) return { "material_ccm": round(material_ccm, 2), "weight_g": round(weight_g, 1), "print_minutes": print_minutes, "filament_cost": round(filament_cost, 2), "net_price": round(net_price, 2), "gross_price": round(gross_price, 2), "vat_rate": vat_rate, "price_per_kg": price_per_kg, "material_factor": eff_factor, "print_speed": print_speed, }