
Bambu Slicer
- 2 installs
- 1 repo stars
- Updated July 30, 2026
- aojdevstudio/agentic-utilities
bambu-slicer is a skill that provides an end-to-end 3D printing pipeline for Bambu Lab printers, covering design, model browsing, slicing, plate arrangement, and printer control.
About
A unified 3D-printing pipeline skill for Bambu Lab printers. A developer uses it to design custom parts with the OpenSCAD MCP, find models on MakerWorld via a browser, slice STL files to print-ready 3MF using a bundled OrcaSlicer-backed CLI, arrange multi-object plates, and control the printer through the Bambu MCP. It includes a first-run setup check and strict guardrails against writing printer credentials or private data into shared files.
- End-to-end 3D printing pipeline for Bambu Lab printers: design, browse, slice, arrange, and print
- Designs parts with the OpenSCAD MCP, browses MakerWorld via agent-browser, and slices STL to 3MF with a bundled OrcaSlic
- Controls the printer through the Bambu MCP with explicit security and privacy guardrails
Bambu Slicer by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,839 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Jul 31, 2026 (Skillselion catalog sync)
bambu-slicer capabilities & compatibility
- Capabilities
- 3d slicing · cad design · printer control
- Use cases
- orchestration
- Platforms
- macOS
- Pricing
- Free
What bambu-slicer says it does
Unified 3D printing pipeline for Bambu Lab printers.
slicing STL to 3MF with the bundled OrcaSlicer-backed CLI
Gridfinity standard: 42 mm grid, 7 mm base height.
npx skills add https://github.com/aojdevstudio/agentic-utilities --skill bambu-slicerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 1 |
| Last updated | July 30, 2026 |
| Repository | aojdevstudio/agentic-utilities ↗ |
What it does
Run an end-to-end Bambu Lab 3D printing pipeline: design, browse, slice STL to 3MF, arrange plates, and print.
Who is it for?
Designing functional/geometric parts, slicing STL to 3MF, and controlling Bambu Lab printers end to end.
Skip if: Organic or sculpted shapes, which should be sourced from MakerWorld instead.
When should I use this skill?
When a user wants to print, slice, design a part, browse MakerWorld models, or control a Bambu Lab printer.
What you get
A print-ready 3MF and a controlled Bambu Lab print job produced from a design, photo, STL, or MakerWorld model.
- A print-ready 3MF file and/or a controlled printer job
By the numbers
- 6-step first-run setup check
- 42 mm gridfinity grid, 7 mm base height
- 1.2 mm minimum wall thickness
Files
bambu-slicer — Unified 3D Printing Pipeline
End-to-end workflow for Bambu Lab printers: design custom models, browse MakerWorld for existing ones, slice STLs to print-ready 3MF, arrange multi-object plates, and control the printer — all from Agent Skills plus surrounding MCPs.
Security and privacy guardrails
This skill is intended for public sharing. Keep it clean:
- Never write printer serial numbers, access codes, Bambu account credentials, cloud tokens, LAN IPs, Wi-Fi details, local usernames, private file paths, or customer/private model names into
SKILL.md,references/, package manifests, READMEs, or committed config. - Treat
.bambu-slicer.local.md,.env, downloaded private STLs, generated 3MF/G-code, printer logs, screenshots, and MakerWorld session data as local/private. - If MakerWorld login is required, use an existing browser session or explicit user-approved password-manager flow. Do not ask the user to paste credentials into chat, and do not store credentials in files.
- Use placeholders in public docs:
<printer-lan-ip>,<printer-serial>,<access-code>,<output-dir>. - Before publishing changes, run a secret scan over
skills/bambu-slicerand confirm only placeholders/defaults are present.
First-run check
Before slicing anything, verify setup once:
1. Locate this skill directory. In this repo it is skills/bambu-slicer; when installed globally it may be ~/.pi/agent/skills/bambu-slicer or another Agent Skills path. If needed, set:
export BAMBU_SLICER_SKILL_DIR="/path/to/skills/bambu-slicer"
export BAMBU_SLICER_CLI_DIR="$BAMBU_SLICER_SKILL_DIR/scripts/cli"2. CLI deps installed: $BAMBU_SLICER_CLI_DIR/node_modules exists. If not:
cd "$BAMBU_SLICER_CLI_DIR" && bun install3. OrcaSlicer installed at the configured path. Default: /Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer; override with ORCA_CLI_PATH. 4. Bambu Studio installed. The CLI patches its profile JSONs at slice time; Bambu Studio does not need to be running, but profiles must exist on disk. 5. Optional local config exists outside committed files, for example .bambu-slicer.local.md or ~/.config/bambu-slicer/local.md, with placeholders resolved by the user. The CLI itself reads environment variables: ORCA_CLI_PATH, BAMBU_PROFILE_BASE, BAMBU_MACHINE_PROFILE, BAMBU_OUTPUT_DIR, and BAMBU_DEFAULT_FILAMENT. 6. Generated print artifacts default to a temp generated-artifact directory. Set BAMBU_OUTPUT_DIR to the user's preferred generated-artifact directory when needed. Do not save generated STL/3MF/G-code files into ~/Downloads unless the user explicitly asks for a one-off there.
If any step fails, surface a clear error before attempting to slice.
Capabilities and workflows
1. Design custom parts with OpenSCAD MCP
When the user describes something they need or sends a photo, design it with the openscad MCP tools.
Best for: Functional/geometric parts — storage bins, cable organizers, shelf brackets, phone stands, gridfinity inserts, drawer dividers, wall mounts, enclosures, clips, hooks.
Not suited for: Organic/sculpted shapes. Use MakerWorld instead.
Workflow:
1. Understand dimensions and constraints from description/photo. 2. Write OpenSCAD code using openscad MCP → create_scad_script. 3. Render preview images → render_preview. Show the user for approval. 4. Iterate on design based on feedback. 5. Export STL → export_stl. 6. Slice with the bundled CLI.
Design tips:
- Always ask for or estimate dimensions. A part that does not fit is useless.
- Use
$fn=64or higher for smooth curves. - Add 0.2 mm tolerance for friction-fit joints.
- Wall thickness minimum: 1.2 mm, equivalent to 3 perimeters at a 0.4 mm nozzle.
- Gridfinity standard: 42 mm grid, 7 mm base height.
For tested constants on tray/shelf/riser-style parts, read references/gotchas.md.
2. Browse and download from MakerWorld with agent-browser
When the user wants to find existing models, use the agent-browser skill to drive MakerWorld.
Workflow:
1. Navigate to https://makerworld.com/en/search/models?keyword={query}. 2. Screenshot search results and present options with thumbnails. 3. User picks one → navigate to the model page. 4. Click "Download raw model files" to download STL/STEP files, or use the source 3MF if posted. See references/gotchas.md before remixing. 5. Save to a local working directory such as /tmp/makerworld/. 6. Slice with the bundled CLI.
Notes:
- Login may be required for downloads. Use only existing authenticated sessions or an explicit user-approved password-manager flow.
- MakerWorld has bot detection. Use realistic browsing patterns.
- Always show the user a screenshot of the model before downloading.
- Check whether the model includes a pre-sliced 3MF for the target printer. It may save time.
3. Slice STL to print-ready 3MF with bundled CLI
The core slicing engine takes STL files and produces print-ready 3MF using OrcaSlicer with patched Bambu Studio profiles.
Set the CLI path once per shell/session:
export BAMBU_SLICER_CLI_DIR="${BAMBU_SLICER_CLI_DIR:-$BAMBU_SLICER_SKILL_DIR/scripts/cli}"CLI usage:
# Single file, default settings: 0.20 mm Standard, Bambu PLA Basic
# Output defaults to a generated-artifact temp directory unless BAMBU_OUTPUT_DIR is set
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input model.stl
# Custom quality
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input model.stl --quality 0.12
# Custom filament
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input model.stl --filament "Bambu PETG Basic"
# Multiple objects on one plate, space-separated
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input "box.stl cylinder.stl hook.stl"
# List available profiles
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --list-profilesFor the full quality and filament tables, read references/profiles.md.
Output location: default to a generated-artifact temp directory. Use BAMBU_OUTPUT_DIR when the user explicitly chooses another generated-artifact directory. Do not write generated 3MF/G-code into ~/Downloads or committed skill files.
3a. Filament and AMS selection
Do not treat the slicer profile color as the user's loaded filament color. Bambu/Orca profile JSONs often carry a default display color (for example orange-ish PLA), which is not an AMS inventory check.
Before choosing a filament profile:
1. If bambu-printer MCP AMS tools are available, query the printer/AMS and pick the loaded spool that matches the required material. Prefer exact material + brand (Bambu PLA Basic, Bambu PETG Basic) over color. Report the selected tray/material/color. 2. If AMS tools are unavailable in this Pi session, preserve the source 3MF filament/material when possible. Otherwise ask the user which loaded material/profile to use before slicing multi-material or color-sensitive prints. 3. For separable vertical stacks, require an incompatible release interface: PLA parts → PETG interface; PETG parts → PLA interface. Verify the AMS has both materials before generating a print-ready file.
4. Multi-object plate arrangement and stacking
When the user has multiple parts to print, pick the densest packing the geometry allows before falling back to separate plates. Three patterns, in priority order:
Pattern A — Vertical stack (parts physically on top of each other). Use when parts have flat tops AND flat bottoms AND the upper bottom fits within the lower top. The previous part's top surface acts as the bed for the next. Right answer for repeated drawer/bin/lid/tray geometries.
Bambu Studio workflow (CLI cannot do this today — it is a slicer-UI operation):
1. Drag all stackable parts onto a single plate. 2. Select all → right-click → Merge (formerly "Assemble"). Each object becomes a "part" of one assembly. Bambu Studio only allows Z-axis movement on parts, not standalone objects. 3. Alt+Click (or use the object list) to select each part. 4. Move tool → set Z to part_height × index. Three identical 25 mm drawers stacked: Z = 0, 25, 50. 5. Slice as normal. The slicer treats the assembly as one tall object with correct layer transitions.
Geometric requirements:
- Flat top on lower part; flat bottom on upper part.
- Upper part's footprint ⊆ lower part's top (no overhang).
- Total stacked height ≤ printer Z range (e.g. 256 mm on the P2S).
- Same filament across the stack.
Pattern B — Sequential print-by-object (parts side-by-side, one finishes before the next starts). Use when parts won't vertically stack but are short enough that the head can clear already-printed neighbours. Failure-isolating: one falls off, the rest continue.
Bambu Studio workflow:
1. Place parts on one plate with ≥ extruder_clearance_max_radius (~67 mm on Bambu) XY spacing. 2. Process settings → Special mode → Print Schedule: By Object. 3. First-printed objects must have height < extruder_clearance_height_to_rod (~25 mm typical) unless they print last.
Pattern C — Multi-object same-layer plate (default, what the CLI does today). All parts print together layer-by-layer with travel between them.
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" \
--input "part-a.stl part-b.stl part-c.stl" \
--output combined-plate.3mfThe slicer auto-arranges and auto-orients all objects. See references/gotchas.md for bed-margin caveats and the identical-plate stacking detection rule.
5. Printer control with Bambu MCP
Use bambu-printer MCP tools for printer interaction when available.
| Task | MCP tool |
|---|---|
| Check printer status | get_printer_status |
| List printers | list_printers |
| Pause current print | pause_print |
| Resume paused print | resume_print |
| Stop/cancel print | stop_print |
| Connect via MQTT locally | mqtt_connect |
| Get real-time status | printer_get_status |
| Stream camera | Camera tools |
| Check AMS filament | AMS tools |
Developer Mode is required for full local control. If the user enables Developer Mode on the printer touchscreen, local MQTT control is available. Without it, cloud-based status monitoring may still work.
Do not expose or commit printer serial numbers, LAN IPs, access codes, or MQTT credentials.
6. Batch print night workflow
When the user wants to maximize a print session:
1. Collect all STLs, both designed and downloaded. 2. Group by filament type. Do not mix PLA and PETG on one plate. 3. Arrange each group on a plate using multi-object slicing. 4. Slice each plate. 5. Send to printer via approved printer-control flow, or open in Bambu Studio if local Developer Mode is off.
Decision tree
User wants to 3D print something
├── Has specific STL file(s)? → Slice
├── Wants something custom? → Design with OpenSCAD
├── Wants to browse for a model? → MakerWorld with agent-browser
├── Multiple things to print?
│ ├── Repeated flat-topped parts? → Vertical stack, Pattern A (Section 4)
│ ├── Short parts that won't stack? → Sequential by-object, Pattern B (Section 4)
│ └── Otherwise → Multi-object plate, Pattern C (Section 4)
├── Multi-plate 3MF handed in? → Identical-plate check (gotchas) → stack if eligible
├── Asking about printer state? → Bambu MCP printer control
└── "Print night" / batch session? → Batch workflowTechnical notes
- Slicer engine: OrcaSlicer. Bambu Studio CLI has a known segfault bug with P2S 0.4 mm nozzle profiles (OrcaSlicer #9636), so this skill uses OrcaSlicer with the bundled profile patcher.
- Profile patching: machine profiles are auto-patched at slice time. Bambu Studio gcode templates are replaced with OrcaSlicer-compatible versions, and
inheritschains are resolved inline so OrcaSlicer can read the profile out-of-tree. - Cloud vs. local: cloud-based status may work without printer config. Local MQTT control requires Developer Mode plus a configured
bambu-printerMCP.
Gotchas, lessons, and tested constants
Read references/gotchas.md whenever the user asks for a print, especially a MakerWorld remix. It covers:
- Bed-margin failures with
--arrangeon near-bed-sized parts. - Matching the source 3MF's profile when remixing.
- Two-up plate offers when geometry permits.
- Bambu Studio's harmless version-mapping dialog after opening an OrcaSlicer-produced 3MF.
- Tested fillet/lip/rib constants for tray-style functional parts.
New 3D-printing lessons go in references/gotchas.md. Keep entries tight: failure mode → root cause → fix → why it matters.
# Local/private config
.bambu-slicer.local.md
*.local.md
.env
.env.*
!.env.example
# Bun / Node
node_modules/
dist/
*.tsbuildinfo
# Slicer artifacts and working files
*.3mf
*.gcode
*.stl
*.step
*.stp
.bambu-tmp/
downloads/
outputs/
bambu-slicer
Agent Skill for end-to-end Bambu Lab 3D-printing workflows: design custom parts, browse MakerWorld, slice STL files into print-ready 3MF with OrcaSlicer, arrange multi-object plates, and control printers through a Bambu MCP when available.
Contents
skills/bambu-slicer/
├── SKILL.md
├── README.md
├── references/
│ ├── gotchas.md
│ └── profiles.md
└── scripts/cli/
├── cli.ts
├── profiles.ts
├── slicer.ts
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── tests/Setup
export BAMBU_SLICER_SKILL_DIR="/path/to/skills/bambu-slicer"
export BAMBU_SLICER_CLI_DIR="$BAMBU_SLICER_SKILL_DIR/scripts/cli"
cd "$BAMBU_SLICER_CLI_DIR"
bun install
bun run typecheck
bun testPrerequisites:
- Bambu Lab printer. Defaults target P2S with 0.4 mm nozzle.
- OrcaSlicer. Default binary:
/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer. - Bambu Studio, for local profile JSONs.
- Bun, for the TypeScript CLI.
- Optional: OpenSCAD MCP, Bambu printer MCP, and
agent-browserskill.
CLI quick start
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input model.stl --output model.3mf
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input model.stl --output model.3mf --quality 0.12
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input "a.stl b.stl c.stl" --output plate.3mf
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --list-profilesRuntime overrides:
export ORCA_CLI_PATH="/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer"
export BAMBU_PROFILE_BASE="$HOME/Library/Application Support/BambuStudio/system/BBL"
export BAMBU_MACHINE_PROFILE="Bambu Lab P2S 0.4 nozzle.json"Public-sharing safety
This skill is sanitized for open-source sharing. Keep local/private values out of the repo:
- No printer serials, access codes, LAN IPs, Bambu credentials, cloud tokens, Wi-Fi details, local usernames, or private file paths.
- Do not commit
.bambu-slicer.local.md,.env, generated*.3mf,*.gcode, downloaded private STLs, logs, or screenshots. - Use placeholders like
<printer-lan-ip>,<printer-serial>,<access-code>, and<output-dir>in docs.
Secret-scan before publishing:
rg -n --hidden -i \
"(api[_-]?key|secret|token|password|credential|serial|access[ _-]?code|lan[ _-]?ip|mqtt|192\\.168|10\\.|172\\.(1[6-9]|2[0-9]|3[0-1]))" \
skills/bambu-slicerGotchas & Lessons
Hard-won lessons from real prints. Read this whenever the user asks for a print — especially a MakerWorld remix.
Bed margins for --arrange
OrcaSlicer's --arrange step on a 256×256 mm bed needs at least ~10 mm of bed margin around a part to succeed. A 254 mm-wide part on a 256 mm bed will fail with "Nothing to be sliced ... no object is fully inside the print volume before apply" even though the dimensions technically fit.
Cap individual-part width at ~240 mm unless the user explicitly waives auto-arrange. Smaller parts can pack tighter; the issue is just margin around a single near-bed-sized object.
For non-256 mm beds (A1, A1 mini, X1C, etc.), apply the same ~6% margin rule of thumb.
Match the source's profile when remixing a MakerWorld 3MF
When the user hands you a source 3MF from MakerWorld and asks you to modify the model, default to the source 3MF's slicing profile (infill density, wall count, layer height) — not the CLI's defaults. The CLI defaults to 0.2 mm Standard / 15 % infill / Bambu PLA Basic, which is fine for most prints but is wrong for shell-style decorative pieces.
How to read the source profile:
1. Unzip the source .3mf and look at Metadata/project_settings.config for sparse_infill_density, wall_loops, layer_height, sparse_infill_pattern, top_shell_layers, bottom_shell_layers. 2. Or check the model description/title — MakerWorld designers often state "0.2 mm, 2 walls, 2 % infill" up front. 3. If unstated, ask before defaulting.
The CLI doesn't yet have --infill / --walls flags. Until it does, override via a patched process profile (see profiles.md for the pattern).
Real example. A 3-tier vase-style "perfume stand" was profiled "0.2 mm layer, 2 walls, 2 % infill". Slicing a remix at the CLI's 15 % default produced 21 h / 757 g — about 7× the original. Re-sliced at 2 % gyroid / 2 walls dropped it to 6.5 h / 211 g, in line with the source. Lesson: trust the original designer's profile for shell-style decorative pieces; don't blanket-apply your defaults.
Bambu Studio version-mapping dialog after opening a CLI 3MF
When the user opens an OrcaSlicer-produced 3MF in Bambu Studio, BS will show an info dialog mapping a few keys (ensure_vertical_shell_thickness → ensure_all, rectilinear ironing → zig-zag, etc.). This is harmless schema drift — print parameters are preserved. Tell the user to click OK and proceed.
Detect multi-plate 3MFs with identical parts and offer vertical stacking
When the user hands a .3mf with multiple plates, inspect the part files before slicing each plate separately. If two or more plates are the same flat-topped functional part (drawers, lids, bins, plates), default to offering the Pattern A vertical stack from SKILL.md Section 4 instead of N separate prints.
unzip -l source.3mf | grep '3D/Objects/'
mkdir -p /tmp/3mf-inspect && cd /tmp/3mf-inspect && unzip -qo source.3mf
ls -la 3D/Objects/ # equal byte sizes are a strong identical-part signal
cksum 3D/Objects/*.model # confirm geometric equalityWhy it matters: a 4-plate organizer download where plates 2 and 3 are the same drawer collapses to a single 1- or 2-plate print job. That's N-1 fewer bed preps and N-1 fewer first-layer-failure risk windows. The user wants this — make sure to surface it before queueing the original separate plates.
Failure modes to flag before stacking:
- Lower part has any top-surface overhang or curvature → Pattern A will not bond cleanly. Fall back to Pattern B (sequential by-object) or separate plates.
- Upper part footprint exceeds lower top → overhang on the print, abort the stack offer.
- Combined stack height > printer Z range → split into smaller stacks.
- Different filaments per layer → requires AMS swap or pause-resume. For separable stacks this is expected: use an incompatible release interface and verify AMS materials first.
Tooling status: pre-composing a stacked STL from N inputs is a candidate CLI enhancement (--stack-z taking N inputs and writing a Z-offset assembly STL). Today the operation is performed in Bambu Studio's UI via Merge → Move on Z.
Separable vertical stacks need a release interface
When the user asks to "stack" duplicate parts, do not assume same-material contact is acceptable. Same-material vertical stacks can fuse into one part. For separable PLA stacks, insert a one-layer PETG release/interface sheet at the contact plane and assign it to a PETG filament; for PETG parts, use PLA as the incompatible release material.
Failure mode: same-filament Pattern A stack bonds at the flat interface. Root cause: the upper first layer is printed directly onto the previous part's top surface. Fix: make the stack an assembly with bottom part = model filament, one release layer = incompatible filament, top part = model filament, then preview the interface layer before printing. Save the generated 3MF under the configured generated-artifact output directory, not Downloads. Why it matters: "stacked" usually means batched but separable, not fused.
Two-up plates when geometry permits
After confirming a single copy fits the bed with margin, proactively offer a 2-up (or N-up) pre-arranged 3MF — most users like amortizing bed prep across copies. Pass the same STL multiple times to the CLI:
bun run "$BAMBU_SLICER_CLI_DIR/cli.ts" --input "model.stl model.stl" --output 2up.3mfThe slicer auto-arranges. Don't auto-bake the multi-up though — confirm copy count first, since some prints (large, brittle, or one-of-a-kind) shouldn't be doubled.
Tested constants for tray/shelf/riser-style functional parts
For shelf risers, drawer organizers, and other "tray" pieces, these constants have been validated on real parts:
- Wall fillet radius: 2 mm. Use
hull()of 8 corner spheres at$fn=16— much faster thanminkowski(). - Lip fillet radius: 0.9 mm with 2 mm lip thickness.
- Front/side lip height: 8 mm.
- Rear lip height: 12 mm (when the back wall is reduced to a lip rather than a full wall).
- Rib spacing: 4 mm centers, 0.6 mm groove depth, 1.6 mm groove width — and inset rib z-range by the fillet radius so ribs don't intrude into the rounded edges.
These are starting points, not laws. Adjust for the specific part's loading and aesthetics.
Where this list lives — and why
This file is the durable home for printer + MakerWorld + slicer-CLI gotchas. It's part of the skill, not project memory, so improvements auto-load next session.
When you discover a new lesson, append it here and keep the entry tight: failure mode → root cause → fix → why it matters.
Profile Reference
Default profile names used by the bundled CLI, all targeting a Bambu Lab P2S with a 0.4 mm nozzle. Override via BAMBU_MACHINE_PROFILE and the maps in scripts/cli/profiles.ts for other Bambu printers. Generated output defaults to a temp generated-artifact directory; override with BAMBU_OUTPUT_DIR for a user-chosen generated-artifact directory.
Quality profiles (--quality, 0.4 mm nozzle)
| Flag | Profile | Use case |
|---|---|---|
| 0.08 | 0.08 mm High Quality | Display pieces, fine detail |
| 0.12 | 0.12 mm High Quality | Good detail, reasonable speed |
| 0.16 | 0.16 mm Standard | Balanced |
| 0.20 | 0.20 mm Standard (default) | Everyday prints |
| 0.24 | 0.24 mm Standard | Fast, functional parts |
Filament profiles (--filament)
| Name | Material | Notes |
|---|---|---|
| Bambu PLA Basic | PLA (default, unless BAMBU_DEFAULT_FILAMENT is set) | General purpose. Profile color is not AMS truth. |
| Bambu PLA Matte | PLA | Nice surface finish |
| Bambu PETG Basic | PETG | Heat/moisture resistant |
| Generic PLA | PLA | Third-party PLA |
| Generic PETG | PETG | Third-party PETG |
AMS and profile color caveat
The CLI loads filament profiles; it does not query the printer AMS by itself. The orange-ish color seen in generated 3MFs comes from Bambu/Orca profile metadata, not from a live spool lookup. When bambu-printer MCP AMS tools are available, query AMS first and choose the profile that matches the loaded material. When they are unavailable, preserve the source 3MF material/profile or ask the user.
Adding new profiles
The maps live in scripts/cli/profiles.ts (PROCESS_MAP, FILAMENT_MAP). To support a different Bambu printer (X1C, P1S, A1, …):
1. Find the printer's profile JSON files in ~/Library/Application Support/BambuStudio/system/BBL/{machine,process,filament}/. 2. Update MACHINE_PROFILE and the maps to match. Suffix conventions vary (@BBL P2S, @BBL X1C, …). 3. Override at runtime via env vars without editing source: BAMBU_MACHINE_PROFILE="Bambu Lab X1 Carbon 0.4 nozzle.json" and similar.
Process profile overrides for remixes
The CLI doesn't yet expose --infill / --walls flags. To override infill density, wall count, or shell layers when remixing, write a tmp lean-process JSON that overrides the relevant keys (sparse_infill_density, wall_loops, sparse_infill_pattern, top_shell_layers, bottom_shell_layers) and pass it as the second --load-settings semicolon entry. The pattern matches patchMachineProfile in scripts/cli/slicer.ts.
See gotchas.md for the failure mode that motivates this.
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "bambu-slicer-cli",
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5.7.0",
"vitest": "^3.0.0",
},
},
},
"packages": {
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.2", "", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.2", "", { "os": "android", "cpu": "arm64" }, "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.2", "", { "os": "none", "cpu": "arm64" }, "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA=="],
"@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
"@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
"@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
"@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
"@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
"check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
"esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="],
"rollup": ["rollup@4.60.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="],
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
"tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
"vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
}
}
#!/usr/bin/env bun
import { existsSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, extname, join, resolve } from "node:path";
import { parseArgs } from "node:util";
import { listProfiles, resolveProfiles } from "./profiles.js";
import { runSlicer } from "./slicer.js";
const DEFAULT_OUTPUT_DIR = process.env.BAMBU_OUTPUT_DIR || join(tmpdir(), "bambu-slicer-generated");
const DEFAULT_FILAMENT = process.env.BAMBU_DEFAULT_FILAMENT || "Bambu PLA Basic";
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
input: { type: "string" },
output: { type: "string" },
quality: { type: "string", default: "0.20" },
filament: { type: "string", default: DEFAULT_FILAMENT },
"list-profiles": { type: "boolean", default: false },
},
strict: true,
});
function timestamp(): string {
return new Date()
.toISOString()
.replace(/[-:]/g, "")
.replace(/\.\d{3}Z$/, "Z");
}
function defaultOutputFor(inputFiles: string[]): string {
mkdirSync(DEFAULT_OUTPUT_DIR, { recursive: true });
const stem =
inputFiles.length === 1
? basename(inputFiles[0], extname(inputFiles[0])).replace(/[^A-Za-z0-9._-]+/g, "_")
: `combined-${inputFiles.length}-objects`;
const candidate = join(DEFAULT_OUTPUT_DIR, `${stem}.3mf`);
if (!existsSync(candidate)) return candidate;
return join(DEFAULT_OUTPUT_DIR, `${stem}-${timestamp()}.3mf`);
}
if (values["list-profiles"]) {
const { qualities, filaments } = listProfiles();
console.log("Quality profiles (--quality):");
for (const q of qualities) console.log(` ${q}`);
console.log("\nFilament profiles (--filament):");
for (const f of filaments) console.log(` ${f}`);
process.exit(0);
}
if (!values.input) {
console.error('Usage: bun run cli.ts --input <stl> [--output <3mf>] [--quality 0.20] [--filament "Bambu PLA Basic"]');
console.error(" bun run cli.ts --list-profiles");
console.error(` default output dir: ${DEFAULT_OUTPUT_DIR}`);
process.exit(1);
}
// Resolve every path to an absolute one so OrcaSlicer's argv parser can't
// mistake a filename like "--export-3mf=evil.3mf" for a flag.
const inputFiles = values.input
.split(/\s+/)
.filter(Boolean)
.map((f) => resolve(f));
const outputFile = resolve(values.output || defaultOutputFor(inputFiles));
const profiles = resolveProfiles({
quality: values.quality!,
filament: values.filament!,
});
console.log(`Slicing ${inputFiles.length} file(s)...`);
console.log(` Quality: ${values.quality}`);
console.log(` Filament: ${values.filament}`);
console.log(` Output: ${outputFile}`);
const result = await runSlicer({
inputFiles,
outputFile,
...profiles,
});
if (result.success) {
const stat = Bun.file(result.outputFile);
const sizeKB = Math.round((await stat.size) / 1024);
console.log(`\nSuccess! Output: ${result.outputFile} (${sizeKB} KB)`);
} else {
console.error(`\nSlicing failed: ${result.error}`);
process.exit(1);
}
{
"name": "bambu-slicer-cli",
"version": "0.1.0",
"description": "TypeScript CLI for slicing STL files with OrcaSlicer using patched Bambu Studio profiles. Bundled with the bambu-slicer Agent Skill.",
"type": "module",
"scripts": {
"test": "vitest run",
"typecheck": "tsc --noEmit"
},
"license": "MIT",
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5.7.0",
"vitest": "^4.1.8"
}
}
import { homedir } from "node:os";
import { join } from "node:path";
// Override these via env vars to support non-P2S Bambu printers.
// Default targets a P2S with 0.4 mm nozzle on macOS.
const BASE_PATH =
process.env.BAMBU_PROFILE_BASE || join(homedir(), "Library/Application Support/BambuStudio/system/BBL");
const MACHINE_PROFILE = process.env.BAMBU_MACHINE_PROFILE || "Bambu Lab P2S 0.4 nozzle.json";
const PROCESS_MAP: Record<string, string> = {
"0.08": "0.08mm High Quality @BBL P2S.json",
"0.12": "0.12mm High Quality @BBL P2S.json",
"0.16": "0.16mm Standard @BBL P2S.json",
"0.20": "0.20mm Standard @BBL P2S.json",
"0.24": "0.24mm Standard @BBL P2S.json",
};
const FILAMENT_MAP: Record<string, string> = {
"Bambu PLA Basic": "Bambu PLA Basic @BBL P2S.json",
"Bambu PLA Matte": "Bambu PLA Matte @BBL P2S.json",
"Bambu PETG Basic": "Bambu PETG Basic @BBL P2S 0.4 nozzle.json",
"Generic PLA": "Generic PLA @BBL P2S.json",
"Generic PETG": "Generic PETG @BBL P2S.json",
};
export interface ProfileOptions {
quality: string;
filament: string;
}
export interface ResolvedProfiles {
machine: string;
process: string;
filament: string;
}
export function resolveProfiles(opts: ProfileOptions): ResolvedProfiles {
const processFile = PROCESS_MAP[opts.quality];
if (!processFile) {
throw new Error(`Unknown quality "${opts.quality}". Valid: ${Object.keys(PROCESS_MAP).join(", ")}`);
}
const filamentFile = FILAMENT_MAP[opts.filament];
if (!filamentFile) {
throw new Error(`Unknown filament "${opts.filament}". Valid: ${Object.keys(FILAMENT_MAP).join(", ")}`);
}
return {
machine: join(BASE_PATH, "machine", MACHINE_PROFILE),
process: join(BASE_PATH, "process", processFile),
filament: join(BASE_PATH, "filament", filamentFile),
};
}
export function listProfiles(): { qualities: string[]; filaments: string[] } {
return {
qualities: Object.keys(PROCESS_MAP),
filaments: Object.keys(FILAMENT_MAP),
};
}
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
// Keys that must never be copied from one object to another via dynamic
// assignment — assigning to them invokes setters that mutate the prototype.
const PROTO_KEYS = new Set(["__proto__", "constructor", "prototype"]);
// OrcaSlicer is used instead of Bambu Studio CLI because Bambu Studio CLI has a
// known segfault bug with P2S 0.4 mm nozzle profiles (OrcaSlicer issue #9636).
// Override via ORCA_CLI_PATH for non-default install locations.
const ORCA_CLI = process.env.ORCA_CLI_PATH || "/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer";
// Simplified gcode templates compatible with OrcaSlicer's parser.
// Bambu Studio profiles use variables like `min_vitrification_temperature` that
// OrcaSlicer doesn't support. These simplified versions produce valid gcode —
// the actual start/end gcode lives in the printer firmware.
const ORCA_MACHINE_START_GCODE =
"G28\\nG1 Z5 F5000\\nM104 S[nozzle_temperature_initial_layer]\\nM140 S[bed_temperature_initial_layer_single]\\nM109 S[nozzle_temperature_initial_layer]\\nM190 S[bed_temperature_initial_layer_single]\\nG92 E0\\n";
const ORCA_MACHINE_END_GCODE = "G1 E-2 F2400\\nG28 X\\nM104 S0\\nM140 S0\\nM84\\n";
const ORCA_LAYER_CHANGE_GCODE = "G92 E0\\n;LAYER_CHANGE\\n;Z:[layer_z]\\nM73 L[layer_num]\\n";
export interface SliceInput {
inputFiles: string[];
outputFile: string;
machine: string;
process: string;
filament: string;
}
export interface SliceResult {
success: boolean;
outputFile: string;
error?: string;
}
export function buildSliceArgs(input: SliceInput): string[] {
return [
...input.inputFiles,
"--load-settings",
`${input.machine};${input.process}`,
"--load-filaments",
input.filament,
"--arrange",
"1",
"--orient",
"1",
"--slice",
"0",
"--export-3mf",
input.outputFile,
];
}
/**
* Patches a Bambu Studio machine profile to be OrcaSlicer-compatible.
* Merges template includes inline and replaces Bambu Studio-specific gcode
* with simplified versions that OrcaSlicer can parse.
*/
function patchMachineProfile(machineProfilePath: string): string {
const profile = JSON.parse(readFileSync(machineProfilePath, "utf-8"));
const profileDir = machineProfilePath.replace(/\/[^/]+$/, "");
// Resolve `inherits` chain — OrcaSlicer can't follow inherits across a
// patched profile written outside the vendor tree, so merge parent fields
// inline. Child fields win over parent.
let cursor = profile.inherits as string | undefined;
const seen = new Set<string>();
while (cursor && !seen.has(cursor)) {
seen.add(cursor);
const parentPath = join(profileDir, `${cursor}.json`);
if (!existsSync(parentPath)) break;
const parent = JSON.parse(readFileSync(parentPath, "utf-8"));
for (const [k, v] of Object.entries(parent)) {
if (PROTO_KEYS.has(k)) continue;
if (!Object.hasOwn(profile, k) && !["name", "instantiation", "type", "from", "inherits"].includes(k)) {
profile[k] = v;
}
}
cursor = parent.inherits;
}
delete profile.inherits;
// Inline template includes
if (profile.include) {
for (const inc of profile.include) {
const tpath = join(profileDir, `${inc}.json`);
if (existsSync(tpath)) {
const template = JSON.parse(readFileSync(tpath, "utf-8"));
for (const [k, v] of Object.entries(template)) {
if (PROTO_KEYS.has(k)) continue;
if (!["name", "instantiation", "type", "from"].includes(k)) {
profile[k] = v;
}
}
}
}
delete profile.include;
}
// Replace Bambu Studio-specific gcode with OrcaSlicer-compatible versions
profile.machine_start_gcode = ORCA_MACHINE_START_GCODE;
profile.machine_end_gcode = ORCA_MACHINE_END_GCODE;
profile.layer_change_gcode = ORCA_LAYER_CHANGE_GCODE;
// Ensure nozzle_volume_type exists
if (!profile.nozzle_volume_type) {
profile.nozzle_volume_type = ["0"];
}
const tmpDir = mkdtempSync(join(tmpdir(), "bambu-slicer-"));
const patchedPath = join(tmpDir, "machine-patched.json");
writeFileSync(patchedPath, JSON.stringify(profile, null, 2));
return patchedPath;
}
export async function runSlicer(input: SliceInput): Promise<SliceResult> {
if (!existsSync(ORCA_CLI)) {
return {
success: false,
outputFile: input.outputFile,
error: `OrcaSlicer CLI not found at ${ORCA_CLI}. Install: brew install --cask orcaslicer (or set ORCA_CLI_PATH)`,
};
}
for (const f of input.inputFiles) {
if (!existsSync(f)) {
return {
success: false,
outputFile: input.outputFile,
error: `Input file not found: ${f}`,
};
}
}
// Patch machine profile for OrcaSlicer compatibility
const patchedMachine = patchMachineProfile(input.machine);
const patchedInput = { ...input, machine: patchedMachine };
const args = buildSliceArgs(patchedInput);
const proc = Bun.spawn([ORCA_CLI, ...args], {
stdout: "pipe",
stderr: "pipe",
});
const stdout = await new Response(proc.stdout).text();
const stderr = await new Response(proc.stderr).text();
const exitCode = await proc.exited;
if (exitCode !== 0) {
return {
success: false,
outputFile: input.outputFile,
error: `OrcaSlicer exited with code ${exitCode}\nstdout: ${stdout}\nstderr: ${stderr}`,
};
}
if (!existsSync(input.outputFile)) {
return {
success: false,
outputFile: input.outputFile,
error: `Slicing completed but output file not found at ${input.outputFile}\nstdout: ${stdout}\nstderr: ${stderr}`,
};
}
return {
success: true,
outputFile: input.outputFile,
};
}
import { describe, expect, it } from "vitest";
import { resolveProfiles } from "../profiles.js";
describe("resolveProfiles", () => {
it("returns correct paths for default settings", () => {
const r = resolveProfiles({ quality: "0.20", filament: "Bambu PLA Basic" });
expect(r.machine).toContain("Bambu Lab P2S 0.4 nozzle.json");
expect(r.process).toContain("0.20mm Standard @BBL P2S.json");
expect(r.filament).toContain("Bambu PLA Basic @BBL P2S.json");
});
it("resolves PETG with 0.4 nozzle suffix", () => {
const r = resolveProfiles({ quality: "0.20", filament: "Bambu PETG Basic" });
expect(r.filament).toContain("Bambu PETG Basic @BBL P2S 0.4 nozzle.json");
});
it("resolves PLA Matte", () => {
const r = resolveProfiles({ quality: "0.16", filament: "Bambu PLA Matte" });
expect(r.filament).toContain("Bambu PLA Matte @BBL P2S.json");
expect(r.process).toContain("0.16mm Standard @BBL P2S.json");
});
it("resolves Generic PLA", () => {
const r = resolveProfiles({ quality: "0.12", filament: "Generic PLA" });
expect(r.filament).toContain("Generic PLA @BBL P2S.json");
expect(r.process).toContain("0.12mm High Quality @BBL P2S.json");
});
it("resolves Generic PETG", () => {
const r = resolveProfiles({ quality: "0.24", filament: "Generic PETG" });
expect(r.filament).toContain("Generic PETG @BBL P2S.json");
expect(r.process).toContain("0.24mm Standard @BBL P2S.json");
});
it("throws on unknown quality", () => {
expect(() => resolveProfiles({ quality: "0.99", filament: "Bambu PLA Basic" })).toThrow("Unknown quality");
});
it("throws on unknown filament", () => {
expect(() => resolveProfiles({ quality: "0.20", filament: "Mystery" })).toThrow("Unknown filament");
});
});
import { describe, expect, it } from "vitest";
import { buildSliceArgs } from "../slicer.js";
describe("buildSliceArgs", () => {
it("builds correct argument array for single STL", () => {
const args = buildSliceArgs({
inputFiles: ["/tmp/model.stl"],
outputFile: "/tmp/model.3mf",
machine: "/profiles/machine.json",
process: "/profiles/process.json",
filament: "/profiles/filament.json",
});
expect(args).toEqual([
"/tmp/model.stl",
"--load-settings",
"/profiles/machine.json;/profiles/process.json",
"--load-filaments",
"/profiles/filament.json",
"--arrange",
"1",
"--orient",
"1",
"--slice",
"0",
"--export-3mf",
"/tmp/model.3mf",
]);
});
it("builds correct argument array for multiple STLs", () => {
const args = buildSliceArgs({
inputFiles: ["/tmp/box.stl", "/tmp/cylinder.stl"],
outputFile: "/tmp/multi.3mf",
machine: "/profiles/machine.json",
process: "/profiles/process.json",
filament: "/profiles/filament.json",
});
// Multiple input files should all appear before --load-settings
expect(args[0]).toBe("/tmp/box.stl");
expect(args[1]).toBe("/tmp/cylinder.stl");
expect(args).toContain("--export-3mf");
expect(args[args.length - 1]).toBe("/tmp/multi.3mf");
});
});
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": ".",
"types": ["bun"]
},
"include": ["*.ts", "tests/**/*.ts"]
}
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["tests/**/*.test.ts"],
},
});
Related skills
FAQ
What is it best suited for designing?
Functional/geometric parts like storage bins, brackets, phone stands, gridfinity inserts, and mounts; organic shapes should come from MakerWorld.
What does the slicing engine produce?
Print-ready 3MF from STL files using OrcaSlicer with patched Bambu Studio profiles.