Fix estimation: replace 20% infill with 50% material factor, 32 g/h speed
The 20% infill assumption ignored walls and solid layers which dominate most prints. Validated against Bambu Lab Studio for a GF lid: model volume 26.09 cm³ × 0.50 × 1.24 g/cm³ = 16.2 g (actual: 16.24 g) 16.2 g / 32 g/h × 60 = 30 min (actual: 30.5 min) New env vars (read at request time, no rebuild needed): MATERIAL_FACTOR=0.50 fraction of model volume that becomes filament PRINT_SPEED_G_PER_HOUR=32 matches Bambu Lab at normal quality Card header now shows active assumptions including speed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,3 +8,5 @@ TZ=Europe/Berlin
|
|||||||
FILAMENTDB_URL=https://web.filamentdb.orb.local
|
FILAMENTDB_URL=https://web.filamentdb.orb.local
|
||||||
VAT_RATE=19
|
VAT_RATE=19
|
||||||
FILAMENT_PRICE_PER_KG=15
|
FILAMENT_PRICE_PER_KG=15
|
||||||
|
MATERIAL_FACTOR=0.50
|
||||||
|
PRINT_SPEED_G_PER_HOUR=32
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Job cost and time estimation from STL geometry."""
|
"""Job cost and time estimation from STL geometry."""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# g/cm³ per material — used to convert volume → weight
|
# g/cm³ per material
|
||||||
_DENSITY: dict[str, float] = {
|
_DENSITY: dict[str, float] = {
|
||||||
"PLA": 1.24,
|
"PLA": 1.24,
|
||||||
"PETG": 1.27,
|
"PETG": 1.27,
|
||||||
@@ -13,30 +13,31 @@ _DENSITY: dict[str, float] = {
|
|||||||
}
|
}
|
||||||
_DEFAULT_DENSITY = 1.24
|
_DEFAULT_DENSITY = 1.24
|
||||||
|
|
||||||
# Conservative average FDM print speed for weight-based time estimate
|
|
||||||
_PRINT_SPEED_G_PER_HOUR = 20.0
|
|
||||||
|
|
||||||
|
|
||||||
def estimate(volume_ccm: float, material: str | None) -> dict:
|
def estimate(volume_ccm: float, material: str | None) -> dict:
|
||||||
"""
|
"""
|
||||||
Returns a cost/time estimate dict for one job.
|
Returns a cost/time estimate dict for one job.
|
||||||
|
|
||||||
Assumptions:
|
MATERIAL_FACTOR (env, default 0.50):
|
||||||
- 20 % infill → material used = volume * 0.20
|
Fraction of model volume that becomes actual filament.
|
||||||
- filament density from _DENSITY (default 1.24 g/cm³)
|
'20 % infill' in the slicer does NOT mean 20 % of volume is printed —
|
||||||
- print speed: 20 g/h (conservative FDM average)
|
walls, top/bottom solid layers, and shells are 100 % solid and typically
|
||||||
- filament cost: FILAMENT_PRICE_PER_KG EUR / kg (env, default 15)
|
dominate. Empirically ~50 % of model volume is printed for typical FDM
|
||||||
- misprint buffer: +20 %
|
parts (validated against Bambu Lab Studio output).
|
||||||
- gross profit margin: +30 %
|
|
||||||
- VAT: VAT_RATE % (env, default 19)
|
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.
|
||||||
"""
|
"""
|
||||||
vat_rate = float(os.environ.get("VAT_RATE", 19))
|
vat_rate = float(os.environ.get("VAT_RATE", 19))
|
||||||
price_per_kg = float(os.environ.get("FILAMENT_PRICE_PER_KG", 15))
|
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)
|
density = _DENSITY.get(material or "", _DEFAULT_DENSITY)
|
||||||
|
|
||||||
material_ccm = volume_ccm * 0.20
|
material_ccm = volume_ccm * material_factor
|
||||||
weight_g = material_ccm * density
|
weight_g = material_ccm * density
|
||||||
print_minutes = round((weight_g / _PRINT_SPEED_G_PER_HOUR) * 60)
|
print_minutes = round((weight_g / print_speed) * 60)
|
||||||
|
|
||||||
filament_cost = weight_g / 1000 * price_per_kg
|
filament_cost = weight_g / 1000 * price_per_kg
|
||||||
after_misprint = filament_cost * 1.20
|
after_misprint = filament_cost * 1.20
|
||||||
@@ -52,4 +53,6 @@ def estimate(volume_ccm: float, material: str | None) -> dict:
|
|||||||
"gross_price": round(gross_price, 2),
|
"gross_price": round(gross_price, 2),
|
||||||
"vat_rate": vat_rate,
|
"vat_rate": vat_rate,
|
||||||
"price_per_kg": price_per_kg,
|
"price_per_kg": price_per_kg,
|
||||||
|
"material_factor": material_factor,
|
||||||
|
"print_speed": print_speed,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,10 @@
|
|||||||
<div class="card-header text-secondary small text-uppercase fw-normal" style="border-color:#21262d">
|
<div class="card-header text-secondary small text-uppercase fw-normal" style="border-color:#21262d">
|
||||||
Estimate
|
Estimate
|
||||||
<span class="text-secondary fw-normal ms-2" style="font-size:.7rem">
|
<span class="text-secondary fw-normal ms-2" style="font-size:.7rem">
|
||||||
20 % infill · {{ estimation.price_per_kg }} €/kg · {{ estimation.vat_rate }}% VAT
|
{{ (estimation.material_factor * 100) | round | int }}% material factor
|
||||||
|
· {{ estimation.print_speed | int }} g/h
|
||||||
|
· {{ estimation.price_per_kg }} €/kg
|
||||||
|
· {{ estimation.vat_rate }}% VAT
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
Reference in New Issue
Block a user