Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fdm_price_calc-0.1.0.tar.gz (25.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fdm_price_calc-0.1.0-py3-none-any.whl (38.7 kB view details)

Uploaded Python 3

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

Hashes for fdm_price_calc-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5027b20a150ef2e638533a14e0ff7ea5bddd90875e9378692d319c13358a73e8
MD5 8f9f838a77c972f1cd3f49ef65d067df
BLAKE2b-256 93ac9114529861e77b2442ad0eb894a0593834d3b0ac772a3c0d3565bf910e9f

See more details on using hashes here.

Provenance

The following attestation bundles were made for fdm_price_calc-0.1.0.tar.gz:

Publisher: publish.yml on Kaifungamedev/fdm-price-calc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for fdm_price_calc-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 35422589d73b8f8306f0d789c9607fc98bcb95d7eed784f2fb8da8084aec9e03
MD5 fc1c65085cf45e155f4d1f5476a681c2
BLAKE2b-256 7d7e37374c4ed23a02d2ed4a6136d7e95ffb2968250907e5f4b7a14e191fdd86

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page