CLI and library for calculating FDM 3D print job costs
Project description
fdm-price-calc
A CLI tool and Python library for calculating FDM 3D print job costs. Implements the same pricing model as a standard product pricing worksheet: filament cost × efficiency factor + machine time cost + labour = total landed cost, with suggested sell prices at 50 / 60 / 70 % margin.
Installation
uv tool install fdm-price-calc # install globally
# or inside a project
uv add fdm-price-calc
AI Usage policy
- If you use an AI agent to contrubute to this project you must varify that your changes are tested thoroughly and YOU read an understand the code before merging.
- You must write commit messages, not the AI angent.
- When you use an AI agent you must add the
Co-authored-by:tag to your commit messages. - No AI-generated media is allowed (art, images, videos, audio, etc.). Text and code are the only acceptable AI-generated content.
CLI
fdmpricecalc [FILE] [PRINTER] [FILAMENT] [options]
fdmpricecalc new-printer | new-filament | list
FILE is a sliced .3mf file. PRINTER and FILAMENT are the names of saved
presets (see Presets). All three positional arguments are optional —
missing values are filled in interactively.
# Full run from a sliced file with saved presets
fdmpricecalc job.3mf my_printer pla_basic -n "Widget v2" -a Alice
# No file — enter print stats manually via prompts
fdmpricecalc --no-file my_printer pla_basic
# No file — fully scripted, zero prompts
fdmpricecalc --no-file my_printer pla_basic -w 224.93 -t "5h6m12s" \
--material PLA --color "#FF8800" -n "Pages" -a me -l 10 -q 2 -y
# Override a preset value for this run only
fdmpricecalc job.3mf my_printer pla_basic --efficiency 1.2 --filament-cost 35
# Save result to JSON — minimal by default (no plate data, presets by name)
fdmpricecalc job.3mf bambu_p1s bambu_pla_basic -n "Widget" -o result.json
# Include raw per-plate slicer data
fdmpricecalc job.3mf bambu_p1s bambu_pla_basic -n "Widget" -o result.json -f
# Embed full preset data so the file is self-contained
fdmpricecalc job.3mf bambu_p1s bambu_pla_basic -n "Widget" -o result.json -s
If an unsupported file type is given, the tool falls back to manual entry automatically.
The default JSON output is minimal — just the inputs you provided (printer,
filament, labor, quantity, extra costs) and the calculated costs. The raw
per-plate slicer data is omitted since it can always be re-read from the source
file. Add -f to include it, or -s to embed full preset data for archiving.
Multi-filament
For .3mf files with multiple filament slots, the CLI will automatically try
to match each slot to a saved preset by material type (e.g. a slot reporting
PLA will match any preset whose material field is PLA). You can also
map slots manually:
# Map slot 1 to orange PLA, slot 2 to white PLA; all other slots fall back to pla_basic
fdmpricecalc job.3mf my_printer pla_basic \
--filament-map 1:pla_orange \
--filament-map 2:pla_white
# Override cost per kg for all PLA slots in this run only (doesn't change saved presets)
fdmpricecalc job.3mf my_printer pla_basic --material-cost PLA:18
All flags
-y, --non-interactive Never prompt — use provided args and defaults.
Errors if required args (printer, filament) are
missing. Defaults: labor=0, qty=1, no extra costs.
Multi-filament slots are auto-matched to saved presets.
job metadata:
-n, --name NAME Job name
-a, --author AUTHOR Job author
-o, --output FILE Save results to JSON file
-f, --full Include raw per-plate slicer data in the JSON
(plates + totals; omitted by default as they can be
re-read from the source file)
-s, --self-contained Embed full preset data in the JSON instead of
referencing by name (useful for archiving jobs)
-l, --labor MIN Post-processing labour time in minutes
--labor-rate $/HR Labour hourly rate (overrides preset value for this run)
-q, --qty N Quantity to produce
--extra NAME:COST[:QTY]
Extra per-unit cost (hardware, inserts, etc.).
Repeatable: --extra 'Magnet:0.50:2' --extra 'Insert:0.08'
manual print stats (use with --no-file or as file fallback):
-m, --no-file Skip file parsing; enter stats via args or prompts
-w, --weight G Total filament weight in grams
-t, --time DURATION Total print time (90, 1h30m, 1:30, 5h6m12s, …)
--material TYPE Filament material (PLA, PETG, …)
--color HEX Filament color hex (e.g. #FF8800)
filament preset overrides:
--filament-cost $/KG Override filament cost per kg (default/single preset)
--filament-map ID:PRESET
Map a filament slot ID to a preset (repeatable)
--material-cost MATERIAL:$/KG
Override cost per kg for all slots of a given material
type (e.g. PLA:18). Repeatable.
printer preset overrides:
--efficiency X Material efficiency factor (default 1.5)
--print-rate $/HR Override machine cost rate ($/hr)
--printer-cost $ Printer purchase cost
--upfront $ Additional upfront cost
--maintenance $/YR Estimated annual maintenance
--life YRS Estimated printer life in years
--uptime PCT Estimated uptime 0–100
--power W Printer power consumption in watts
--electricity $/KWH Electricity cost per kWh
--buffer X Printer cost buffer factor
Other commands
fdmpricecalc new-printer # interactive printer preset wizard
fdmpricecalc new-filament # interactive filament preset wizard
fdmpricecalc list # list all saved presets
fdmpricecalc list printers
fdmpricecalc list filaments
Presets
The package ships with built-in presets for common Bambu and Prusa printers and filaments. They are available immediately after install with no setup required.
Bundled printers: bambu_a1_mini, bambu_a1, bambu_p1p, bambu_p1s,
bambu_p2s, bambu_x1c, bambu_x1e, bambu_h2d, bambu_h2s, bambu_h2c,
prusa_mini_plus, prusa_mk4s, prusa_xl, prusa_core_one
Bundled filaments: bambu_pla_basic, bambu_petg_basic, bambu_abs,
bambu_tpu, prusament_pla, prusament_petg, prusament_asa
User presets are stored as plain JSON files and always take priority over bundled presets with the same name:
~/.local/share/fdmpricecalc/
printers/ ← one .json per printer preset
filaments/ ← one .json per filament preset
To customise a bundled preset, save it under the same name and your version will be used from then on:
fdmpricecalc new-printer # wizard saves to your user dir
# or edit the JSON directly at the path shown by fdmpricecalc --help
Run fdmpricecalc list to see all available presets (bundled + user).
Run fdmpricecalc --help to see the exact user preset paths on your system.
Printer preset fields (printers/my_printer.json):
| Field | Default | Description |
|---|---|---|
name |
— | Display name |
printer_cost |
800 | Purchase cost ($) |
additional_upfront_cost |
0 | Upgrades at purchase ($) |
annual_maintenance |
80 | Estimated yearly upkeep ($) |
estimated_life_years |
5 | Expected printer lifespan |
estimated_uptime_pct |
0.5 | Fraction of hours the printer runs |
power_watts |
160 | Average power draw (W) |
electricity_cost_per_kwh |
0.14 | Electricity rate ($/kWh) |
printer_cost_buffer_factor |
1.3 | Multiplier for unexpected costs |
material_efficiency_factor |
1.5 | Filament waste multiplier |
labor_hourly_rate |
15 | Post-processing labour rate ($/hr) |
print_time_rate_override |
null | Skip auto-calc and use a fixed $/hr |
Filament preset fields (filaments/my_filament.json):
| Field | Default | Description |
|---|---|---|
name |
— | Display name |
material |
PLA | Material type string |
cost_per_kg |
20 | Cost per kilogram ($) |
Pricing model
filament_cost = (cost_per_kg / 1000) × weight_g × efficiency_factor × qty
machine_cost = print_time_rate × print_time_hr × qty
labour_cost = (labour_min / 60) × labour_hourly_rate × qty
extra_cost = Σ (unit_cost × item_qty) × qty ← hardware, inserts, etc.
total_landed = filament_cost + machine_cost + labour_cost + extra_cost
sell_price = total_landed / (1 − margin)
e.g. 50% margin → total_landed × 2
60% margin → total_landed × 2.5
70% margin → total_landed × 3.33
print_time_rate (auto) = (capital_cost_per_hr + electrical_cost_per_hr)
× buffer_factor
Supported file formats
| Format | Notes |
|---|---|
.gcode.3mf |
Bambu Studio sliced projects; reads slice_info.config |
.json |
Json file produced by the --output option |
.gcode |
PrusaSlicer text G-code; reads embedded config comments |
.bgcode |
PrusaSlicer binary G-code; reads PRINTER_METADATA and PRINT_METADATA blocks |
Adding a new format takes one class:
from fdm_price_calc import BaseParser, JobData
class MyParser(BaseParser):
# Set True if your format provides per-slot filament breakdown data
multi_filament: bool = False
@staticmethod
def supports(path: str) -> bool:
return path.endswith(".myformat")
def parse(self) -> JobData:
... # return a JobData
# Register so fdm.parse() picks it up
import fdm_price_calc.parsers as _p
_p._PARSERS.append(MyParser)
Python library
All pricing inputs are set directly on the JobData object before calling
calculate(). Parsers populate the file data (plates, weight, print time);
your code attaches the pricing config.
import fdm_price_calc as fdm
# Parse a sliced .3mf
job = fdm.parse("job.3mf")
# Or build job data manually
job = fdm.manual_job(
weight_g=224.93,
print_time_s=fdm.parse_duration("5h6m12s"),
material="PLA",
)
# Load saved presets (or create inline)
printer = fdm.PrinterPreset.load("bambu_p1s")
filament = fdm.FilamentPreset.load("bambu_pla_basic")
# Attach pricing config to the job
job.printer = printer
job.filament = filament
job.labor_minutes = 10
job.quantity = 3
job.extra_costs = [
fdm.ExtraCost(name="Magnet", unit_cost=0.50, qty=2),
fdm.ExtraCost(name="M3 insert", unit_cost=0.08, qty=4),
]
result = fdm.calculate(job)
print(f"Landed cost: ${result.total_landed_cost:.2f}")
print(f"50% margin: ${result.price_at_margin['50%']:.2f}")
print(f"60% margin: ${result.price_at_margin['60%']:.2f}")
# Save presets for later
printer.save()
filament.save()
Multi-filament (per-slot pricing)
For .3mf files where each slot may use a different filament:
job = fdm.parse("multi_color.gcode.3mf")
orange = fdm.FilamentPreset(name="Orange PLA", cost_per_kg=22)
white = fdm.FilamentPreset(name="White PLA", cost_per_kg=25)
pla = fdm.FilamentPreset.load("pla_basic")
job.printer = printer
job.filament = {1: orange, 2: white} # dict[slot_id, preset]
job.default_filament = pla # fallback for unmapped slots
job.labor_minutes = 5
job.quantity = 1
result = fdm.calculate(job)
# result.filament_breakdown — list of (label, cost) per slot
Public API
| Symbol | Type | Description |
|---|---|---|
parse(path) |
function | Parse a supported file → JobData |
manual_job(weight_g, print_time_s, ...) |
function | Build JobData without a file |
calculate(job) |
function | Returns CostBreakdown; all inputs read from job |
parse_duration(s) |
function | "1h30m" → seconds (int) |
JobData |
dataclass | Job data + pricing config (see fields below) |
PlateData |
dataclass | Per-plate print time, weight, filaments |
FilamentUsage |
dataclass | Per-filament usage within a plate |
ExtraCost |
dataclass | Extra per-unit cost item (name, unit_cost, qty) |
PrinterPreset |
dataclass | Printer config with cost calculations |
FilamentPreset |
dataclass | Filament name, material, $/kg |
CostBreakdown |
dataclass | Cost components + margin prices |
BaseParser |
ABC | Base class for adding new file formats |
list_printers() |
function | Names of saved printer presets |
list_filaments() |
function | Names of saved filament presets |
data_dir() |
function | Root preset storage path |
printers_dir() |
function | Printer preset directory |
filaments_dir() |
function | Filament preset directory |
JobData pricing fields (set by caller, not by parsers):
| Field | Type | Default | Description |
|---|---|---|---|
printer |
PrinterPreset | None |
None |
Required before calculate() |
filament |
FilamentPreset | dict[int, FilamentPreset] | None |
None |
Single preset or per-slot mapping |
default_filament |
FilamentPreset | None |
None |
Fallback for unmapped slots (dict mode) |
labor_minutes |
float |
0.0 |
Post-processing labour time |
quantity |
int |
1 |
Number of copies |
extra_costs |
list[ExtraCost] |
[] |
Extra per-unit cost items (hardware, inserts, etc.) |
name |
str |
"" |
Job display name |
author |
str |
"" |
Job author |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fdm_price_calc-0.1.0.tar.gz.
File metadata
- Download URL: fdm_price_calc-0.1.0.tar.gz
- Upload date:
- Size: 25.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5027b20a150ef2e638533a14e0ff7ea5bddd90875e9378692d319c13358a73e8
|
|
| MD5 |
8f9f838a77c972f1cd3f49ef65d067df
|
|
| BLAKE2b-256 |
93ac9114529861e77b2442ad0eb894a0593834d3b0ac772a3c0d3565bf910e9f
|
Provenance
The following attestation bundles were made for fdm_price_calc-0.1.0.tar.gz:
Publisher:
publish.yml on Kaifungamedev/fdm-price-calc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fdm_price_calc-0.1.0.tar.gz -
Subject digest:
5027b20a150ef2e638533a14e0ff7ea5bddd90875e9378692d319c13358a73e8 - Sigstore transparency entry: 1550142522
- Sigstore integration time:
-
Permalink:
Kaifungamedev/fdm-price-calc@3fa4df71cbe066e4c90ba03aaded317687ee7457 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Kaifungamedev
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fa4df71cbe066e4c90ba03aaded317687ee7457 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fdm_price_calc-0.1.0-py3-none-any.whl.
File metadata
- Download URL: fdm_price_calc-0.1.0-py3-none-any.whl
- Upload date:
- Size: 38.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35422589d73b8f8306f0d789c9607fc98bcb95d7eed784f2fb8da8084aec9e03
|
|
| MD5 |
fc1c65085cf45e155f4d1f5476a681c2
|
|
| BLAKE2b-256 |
7d7e37374c4ed23a02d2ed4a6136d7e95ffb2968250907e5f4b7a14e191fdd86
|
Provenance
The following attestation bundles were made for fdm_price_calc-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Kaifungamedev/fdm-price-calc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fdm_price_calc-0.1.0-py3-none-any.whl -
Subject digest:
35422589d73b8f8306f0d789c9607fc98bcb95d7eed784f2fb8da8084aec9e03 - Sigstore transparency entry: 1550142547
- Sigstore integration time:
-
Permalink:
Kaifungamedev/fdm-price-calc@3fa4df71cbe066e4c90ba03aaded317687ee7457 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Kaifungamedev
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fa4df71cbe066e4c90ba03aaded317687ee7457 -
Trigger Event:
release
-
Statement type: