Scale material factor by model size (smaller=denser, larger=fluffier)
Walls are fixed thickness regardless of model size, so small models are
wall-dominated (high material factor) while large models are infill-dominated.
_effective_factor() uses log-linear interpolation:
factor = MATERIAL_FACTOR - log10(volume / 25 cm³) × 0.15
Example curve:
0.5 cm³ → 0.76 1 cm³ → 0.71 10 cm³ → 0.56
26 cm³ → 0.50 100 cm³ → 0.41 500 cm³ → 0.31
MATERIAL_FACTOR env var is now the anchor at 25 cm³ (validated reference).
The card header shows the effective (size-adjusted) factor for the specific job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""Job cost and time estimation from STL geometry."""
|
||||
import math
|
||||
import os
|
||||
|
||||
# g/cm³ per material
|
||||
@@ -13,29 +14,46 @@ _DENSITY: dict[str, float] = {
|
||||
}
|
||||
_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):
|
||||
Fraction of model volume that becomes actual filament.
|
||||
'20 % infill' in the slicer does NOT mean 20 % of volume is printed —
|
||||
walls, top/bottom solid layers, and shells are 100 % solid and typically
|
||||
dominate. Empirically ~50 % of model volume is printed for typical FDM
|
||||
parts (validated against Bambu Lab Studio output).
|
||||
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):
|
||||
Average material throughput in g/h. ~32 g/h matches Bambu Lab printers
|
||||
at normal quality; slower printers (~20 g/h) can be set here.
|
||||
~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))
|
||||
material_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)
|
||||
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)
|
||||
|
||||
material_ccm = volume_ccm * material_factor
|
||||
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)
|
||||
|
||||
@@ -45,14 +63,14 @@ def estimate(volume_ccm: float, material: str | None) -> dict:
|
||||
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": material_factor,
|
||||
"print_speed": print_speed,
|
||||
"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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user