bambu-printer-mcp 1.1.10 → 1.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTORS.md +1 -1
- package/README.md +48 -64
- package/dist/index.js +1 -1
- package/dist/slicer/profile-flatten.d.ts +5 -1
- package/dist/slicer/profile-flatten.js +31 -12
- package/dist/stl/stl-manipulator.js +36 -15
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/slicer/profile-flatten.ts +37 -12
- package/src/stl/stl-manipulator.ts +42 -18
package/CONTRIBUTORS.md
CHANGED
|
@@ -19,7 +19,7 @@ Thank you to everyone who builds, tests, reports problems, and shares real print
|
|
|
19
19
|
|
|
20
20
|
Thank you also to the existing contributors whose work the project builds on:
|
|
21
21
|
|
|
22
|
-
- [David Montgomery (DMontgomery40)](https://github.com/DMontgomery40) — project maintainer.
|
|
22
|
+
- [David Montgomery (DMontgomery40)](https://github.com/DMontgomery40) — project maintainer; flags the FULU/Orca missing-machine-preset safety gap during article review, prompting the 1.1.11 fix.
|
|
23
23
|
- [rowbotik](https://github.com/rowbotik) — printer, AMS, slicing, and operational work across the existing release history.
|
|
24
24
|
- [len-foss](https://github.com/len-foss) — project code contribution.
|
|
25
25
|
- [thebitrock](https://github.com/thebitrock) — project code contribution.
|
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
A Bambu Lab-focused MCP server for controlling Bambu printers, manipulating STL files, and managing end-to-end 3MF print workflows from Claude Desktop, Claude Code, or any MCP-compatible client.
|
|
15
15
|
|
|
16
|
+
**[Browse the documentation site](https://dmontgomery40.github.io/bambu-printer-mcp/)** for searchable setup guides, slicing and AMS guidance, and the full tool reference. It is generated from this README and the docs folder.
|
|
17
|
+
|
|
16
18
|
Built with help from our [contributors](./CONTRIBUTORS.md). Huge thanks to everyone sharing fixes, careful bug reports, and real printer testing!
|
|
17
19
|
|
|
18
20
|
This is a stripped-down, Bambu-only fork of [mcp-3D-printer-server](https://github.com/DMontgomery40/mcp-3D-printer-server). All OctoPrint, Klipper, Duet, Repetier, Prusa Connect, and Creality Cloud support has been removed. What remains is a focused, lean implementation for Bambu Lab hardware.
|
|
@@ -43,8 +45,8 @@ or tokens in chat. Never guess the printer model.
|
|
|
43
45
|
|
|
44
46
|
Use direct LAN printing by default. Explain any LAN/Developer Mode setting
|
|
45
47
|
I need to enable. Prefer FULU OrcaSlicer-bambulab GUI slicing/export; discover
|
|
46
|
-
an existing slicer before suggesting an install.
|
|
47
|
-
|
|
48
|
+
an existing slicer before suggesting an install. For FULU/Orca CLI auto-slicing,
|
|
49
|
+
require MCP 1.1.11+ and its matching installed profile tree (see guide).
|
|
48
50
|
A slicer is not needed here to print a pre-sliced file. Configure the optional FULU
|
|
49
51
|
BambuNetwork bridge only if I choose it, and explain its runtime/auth needs.
|
|
50
52
|
X2D supports status and slicing here, but direct printing is not supported.
|
|
@@ -60,6 +62,43 @@ worked and whether I need to restart or reload the harness.
|
|
|
60
62
|
|
|
61
63
|
[Setup reference](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SETUP.md) · [FULU guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md) · [Optional code mode](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SETUP.md#optional-code-mode)
|
|
62
64
|
|
|
65
|
+
## What to ask your agent
|
|
66
|
+
|
|
67
|
+
Ask for the result you want, not the steps. Current models plan across tools: they search the web, read photos, look up exact dimensions, edit models, slice, and print. Expect a question or two when a choice matters, such as which filament to use or whether to start the print.
|
|
68
|
+
|
|
69
|
+
This server provides the printer, slicing, AMS, and mesh tools. Web search, photos, and Blender edits come from your agent and its other connections, such as a [Blender MCP server](#blender-mcp). Printing a model your agent edited uses CLI slicing with your installed slicer presets; see the [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
|
|
70
|
+
|
|
71
|
+
### Start from anything
|
|
72
|
+
|
|
73
|
+
- **"Here's a phone stand on MakerWorld. Make it fit the new iPhone and print it in black."**\
|
|
74
|
+
Your agent looks up Apple's published dimensions, adjusts the stand in Blender, slices it for your printer, and finds the AMS tray with black filament.
|
|
75
|
+
- **"I want one of these."** *(with a photo of a planter you saw at a café)*\
|
|
76
|
+
It works out the shape and size from the photo, finds or models a matching design, and asks about anything the photo can't show.
|
|
77
|
+
- **"Can you print a replacement?"** *(with a photo of a snapped dishwasher rack clip)*\
|
|
78
|
+
It asks for a measurement or two where the fit matters, models the part, and prints it in a loaded material that suits the job.
|
|
79
|
+
|
|
80
|
+
### From anywhere, while it prints
|
|
81
|
+
|
|
82
|
+
If your agent is always on, such as OpenClaw or Hermes Agent running on a computer at home, message it from Telegram or any other chat app. For direct LAN printing, the server runs on the printer's local network; you don't have to.
|
|
83
|
+
|
|
84
|
+
- **"How's the print going? Send me a picture."**\
|
|
85
|
+
It reports progress and time remaining, and sends a snapshot from the chamber camera.
|
|
86
|
+
- **"The corner is lifting. Pause it."**\
|
|
87
|
+
It pauses the job so you can decide whether to resume or cancel.
|
|
88
|
+
- **"One of the parts came loose. Skip it and keep printing the rest."**\
|
|
89
|
+
It finds that object on the plate and skips only that one.
|
|
90
|
+
- **"Start drying the PETG so it's ready when I get home."**\
|
|
91
|
+
On a heated AMS, it starts the drying cycle for the unit holding that spool.
|
|
92
|
+
|
|
93
|
+
### Before you print
|
|
94
|
+
|
|
95
|
+
- **"Take a picture and make sure the bed is clear, then start the bracket I sliced last night."**\
|
|
96
|
+
It checks the camera image before it sends the job.
|
|
97
|
+
- **"What's loaded in the AMS? Print this in whatever black I have."**\
|
|
98
|
+
It reads the live AMS inventory and matches the file's filaments to your trays.
|
|
99
|
+
- **"Are there any errors on the printer?"**\
|
|
100
|
+
It reads the printer's HMS diagnostics and explains them.
|
|
101
|
+
|
|
63
102
|
<details>
|
|
64
103
|
<summary><strong>Start here</strong></summary>
|
|
65
104
|
|
|
@@ -69,6 +108,7 @@ worked and whether I need to restart or reload the harness.
|
|
|
69
108
|
|---|---|
|
|
70
109
|
| Use open-source slicing and a cloud-free print workflow | [FULU setup: slicer, LAN, and optional bridge](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md) |
|
|
71
110
|
| Connect this MCP to my agent | [Copy the setup request](#set-up-with-your-agent) |
|
|
111
|
+
| See what my agent can do with it | [What to ask your agent](#what-to-ask-your-agent) |
|
|
72
112
|
| Troubleshoot setup or configure it manually | [Installation, environment variables, and LAN reference](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SETUP.md) |
|
|
73
113
|
| Prepare a printable file or troubleshoot slicing | [Slicing guide and model routing](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md) |
|
|
74
114
|
| Choose filament trays or inspect a printer | [AMS setup](#ams-automatic-material-system-setup) and [printer tools](#printer-control-tools) |
|
|
@@ -96,6 +136,7 @@ See the [changelog](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main
|
|
|
96
136
|
- [FULU and open-source printing](#fulu-and-open-source-printing)
|
|
97
137
|
- [Features](#features)
|
|
98
138
|
- [Set up with your agent](#set-up-with-your-agent)
|
|
139
|
+
- [What to ask your agent](#what-to-ask-your-agent)
|
|
99
140
|
- [AMS (Automatic Material System) Setup](#ams-automatic-material-system-setup)
|
|
100
141
|
- [Bambu Communication Notes (MQTT and FTP)](#bambu-communication-notes-mqtt-and-ftp)
|
|
101
142
|
- [What this fork fixes](#what-this-fork-fixes)
|
|
@@ -106,7 +147,6 @@ See the [changelog](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main
|
|
|
106
147
|
- [Slicing Tools](#slicing-tools)
|
|
107
148
|
- [Advanced Tools](#advanced-tools)
|
|
108
149
|
- [Available Resources](#available-resources)
|
|
109
|
-
- [Example Commands for Claude](#example-commands-for-claude)
|
|
110
150
|
- [Bambu Lab Printer Limitations](#bambu-lab-printer-limitations)
|
|
111
151
|
- [General Limitations and Considerations](#general-limitations-and-considerations)
|
|
112
152
|
- [Memory usage](#memory-usage)
|
|
@@ -137,7 +177,7 @@ See the [changelog](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main
|
|
|
137
177
|
|
|
138
178
|
## FULU and open-source printing
|
|
139
179
|
|
|
140
|
-
[FULU OrcaSlicer-bambulab](https://github.com/FULU-Foundation/OrcaSlicer-bambulab) is a supported slicer target (`SLICER_TYPE=orcaslicer-bambulab`; aliases include `fulu-orca` and `orca-studio`). Use its GUI to export a sliced project for direct LAN printing
|
|
180
|
+
[FULU OrcaSlicer-bambulab](https://github.com/FULU-Foundation/OrcaSlicer-bambulab) is a supported slicer target (`SLICER_TYPE=orcaslicer-bambulab`; aliases include `fulu-orca` and `orca-studio`). Use its GUI to export a sliced project for direct LAN printing, or configure its CLI with matching installed profiles. From 1.1.11, FULU/Orca share the [machine-preset gate and profile safety checks](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
|
|
141
181
|
|
|
142
182
|
The optional FULU **BambuNetwork bridge** exposes `bambu_network_bridge_status`, `bambu_network_call`, and `print_3mf_bambu_network`, also reachable through `print_3mf` with `connection_mode: "bambu_network"`. Slicer selection does not enable the bridge. It needs a separately installed FULU runtime and an explicit launch command; cloud printing also needs an authenticated BambuNetwork session.
|
|
143
183
|
|
|
@@ -169,7 +209,7 @@ The optional FULU **BambuNetwork bridge** exposes `bambu_network_bridge_status`,
|
|
|
169
209
|
- Start G-code files already stored on the printer
|
|
170
210
|
- **Collar charm print wrapper** (`print_collar_charm`) — specialized two-color workflow with fixed tray policy for inner (black, AMS 1 slot 1) and outer (white, AMS 2 slot 1) charm parts
|
|
171
211
|
- STL manipulation: scale, rotate, extend base, merge vertices, center at origin, lay flat, and inspect model info
|
|
172
|
-
- Slice STL or 3MF files using an external CLI.
|
|
212
|
+
- Slice STL or 3MF files using an external CLI. BambuStudio, FULU, and Orca require the exact machine preset and resolve BBL dependencies before slicing; see [FULU/Orca CLI setup and validation limits](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
|
|
173
213
|
- Inspect slicer settings from a saved 3MF template or extracted profile via `get_slice_settings`
|
|
174
214
|
- Enumerate saved slicing templates from the local registry via `list_templates`
|
|
175
215
|
- Save templates into the local registry via `save_template`
|
|
@@ -856,7 +896,7 @@ The primary tool for starting a Bambu print. **Recommended input: a pre-sliced `
|
|
|
856
896
|
}
|
|
857
897
|
```
|
|
858
898
|
|
|
859
|
-
`bambu_model` is **required** for model-specific routing and preset selection. It does not by itself validate
|
|
899
|
+
`bambu_model` is **required** for model-specific routing and preset selection. It does not by itself validate pre-sliced G-code. BambuStudio, FULU, and Orca CLI preparation additionally require the exact model/nozzle machine preset and reject incomplete profiles. Using the wrong model can damage hardware. If `bambu_model` is not provided in the tool call and `BAMBU_MODEL` is not set in the environment, the server will ask you interactively via MCP elicitation (if your client supports it) or return a clear error.
|
|
860
900
|
|
|
861
901
|
`bed_type` defaults to `textured_plate` if omitted. `ams_slots` is the preferred override input; `ams_mapping` remains the raw escape hatch. On AMS-equipped H2 printers, `use_ams: false` does not suppress mapping lookup if the sliced file declares filaments. If no mapping is provided for an H2 pre-sliced job with declared filaments, the server fails before sending; pass explicit `ams_slots`, raw `ams_mapping`, or `auto_match_ams: true`.
|
|
862
902
|
|
|
@@ -1015,7 +1055,7 @@ Slice an STL or 3MF file using an external slicer and return the path to the out
|
|
|
1015
1055
|
|
|
1016
1056
|
`slicer_type` options: `bambustudio`, `orcaslicer`, `orcaslicer-bambulab` (FULU), `prusaslicer`, `cura`, `slic3r`. Aliases include `fulu-orca` and `orca-studio`. When omitted, the value from the `SLICER_TYPE` environment variable is used (default: `bambustudio`).
|
|
1017
1057
|
|
|
1018
|
-
**FULU/Orca CLI
|
|
1058
|
+
**FULU/Orca CLI safety:** from 1.1.11, these backends require the exact model/nozzle preset from the selected installation and stop on profile preparation failures. A process override cannot bypass that gate. See [configuration and validation limits](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
|
|
1019
1059
|
|
|
1020
1060
|
`slicer_path` and `slicer_profile` fall back to the `SLICER_PATH` and `SLICER_PROFILE` environment variables when omitted. Per-call `slicer_path` overrides require `MCP_ALLOW_EXECUTABLE_ARG=1`.
|
|
1021
1061
|
|
|
@@ -1165,62 +1205,6 @@ Resources follow the MCP resource protocol and can be read by calling `ReadResou
|
|
|
1165
1205
|
|
|
1166
1206
|
</details>
|
|
1167
1207
|
|
|
1168
|
-
<details>
|
|
1169
|
-
<summary><strong>Example Commands for Claude</strong></summary>
|
|
1170
|
-
|
|
1171
|
-
## Example Commands for Claude
|
|
1172
|
-
|
|
1173
|
-
After connecting the MCP server in Claude Desktop or Claude Code, you can ask Claude to perform these operations directly in conversation.
|
|
1174
|
-
|
|
1175
|
-
### Printer status and control
|
|
1176
|
-
|
|
1177
|
-
- "What is the current status of my Bambu printer?"
|
|
1178
|
-
- "What temperature is the bed at right now?"
|
|
1179
|
-
- "Show me the files on my printer's SD card."
|
|
1180
|
-
- "Cancel the current print job."
|
|
1181
|
-
- "Set the nozzle temperature to 220 degrees."
|
|
1182
|
-
- "Set the bed to 65 degrees."
|
|
1183
|
-
- "Turn the chamber light on."
|
|
1184
|
-
- "Set the chamber fan to 40 percent."
|
|
1185
|
-
- "List the object IDs in this sliced 3MF."
|
|
1186
|
-
- "Skip object 6495 on the current print."
|
|
1187
|
-
- "Start the AMS drying cycle on AMS 0."
|
|
1188
|
-
- "Stop drying on AMS 1."
|
|
1189
|
-
- "Match the AMS slots for this 3MF against my loaded filaments without printing."
|
|
1190
|
-
- "Auto-match AMS slots and print this 3MF."
|
|
1191
|
-
- "Take a camera snapshot of the print bed."
|
|
1192
|
-
- "Show me the HMS error codes on the printer."
|
|
1193
|
-
- "What speed mode is the printer in?"
|
|
1194
|
-
- "Set the airduct to cooling mode."
|
|
1195
|
-
|
|
1196
|
-
### Printing 3MF files
|
|
1197
|
-
|
|
1198
|
-
- "Print the file at ~/Downloads/bracket.3mf on my Bambu printer."
|
|
1199
|
-
- "Upload bracket.3mf to the printer and start printing with AMS slots 0 and 1."
|
|
1200
|
-
- "Print my_model.3mf with bed leveling enabled and vibration calibration off."
|
|
1201
|
-
- "Upload this 3MF without printing it yet."
|
|
1202
|
-
- "Use FULU OrcaSlicer-bambulab to prepare this model, then show me the sliced output before printing."
|
|
1203
|
-
- "Probe the FULU BambuNetwork bridge without starting a print."
|
|
1204
|
-
|
|
1205
|
-
### STL manipulation
|
|
1206
|
-
|
|
1207
|
-
- "What are the dimensions of this STL file?"
|
|
1208
|
-
- "Scale model.stl to twice its current size."
|
|
1209
|
-
- "Scale this model so it is 150% as wide but stays the same height."
|
|
1210
|
-
- "Rotate this STL 90 degrees around the Z axis."
|
|
1211
|
-
- "Extend the base of this model by 3mm so it sticks to the bed better."
|
|
1212
|
-
- "Center this model at the origin."
|
|
1213
|
-
- "Orient this model so its largest flat face is on the bottom."
|
|
1214
|
-
- "Merge any near-duplicate vertices in this STL to clean it up."
|
|
1215
|
-
|
|
1216
|
-
### Combined workflows
|
|
1217
|
-
|
|
1218
|
-
- "Rotate model.stl 45 degrees around Z, extend the base by 2mm, then print it on my Bambu P1S."
|
|
1219
|
-
- "Take this unsliced 3MF, slice it with BambuStudio, and print the result."
|
|
1220
|
-
- "Scale this part to 80% of its size, lay it flat, and start a print."
|
|
1221
|
-
|
|
1222
|
-
</details>
|
|
1223
|
-
|
|
1224
1208
|
<details>
|
|
1225
1209
|
<summary><strong>Bambu Lab Printer Limitations</strong></summary>
|
|
1226
1210
|
|
|
@@ -1228,7 +1212,7 @@ After connecting the MCP server in Claude Desktop or Claude Code, you can ask Cl
|
|
|
1228
1212
|
|
|
1229
1213
|
Understanding these constraints will help you avoid frustrating errors and set appropriate expectations.
|
|
1230
1214
|
|
|
1231
|
-
1. **Printable 3MF required for print_3mf.** The `print_3mf` tool expects a sliced 3MF containing at least one `Metadata/plate_<n>.gcode` entry. If you pass an unsliced 3MF (one exported from a CAD tool without slicing), the server attempts auto-slicing.
|
|
1215
|
+
1. **Printable 3MF required for print_3mf.** The `print_3mf` tool expects a sliced 3MF containing at least one `Metadata/plate_<n>.gcode` entry. If you pass an unsliced 3MF (one exported from a CAD tool without slicing), the server attempts auto-slicing. BambuStudio, FULU, and Orca CLI paths require the matching machine preset and complete profile preparation; any inspection or slicing failure stops before upload. For a previewable workflow, pre-slice in FULU OrcaSlicer-bambulab, OrcaSlicer, or Bambu Studio and pass the resulting `.gcode.3mf`. See [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md) for the full procedure.
|
|
1232
1216
|
|
|
1233
1217
|
2. **Layer height, temperature, and slicer settings are baked in.** The `project_file` MQTT command tells the printer which plate to run. It does not support overriding layer height, temperature targets, infill percentage, or other slicing parameters at print time. These must be set in your slicer before generating the 3MF.
|
|
1234
1218
|
|
package/dist/index.js
CHANGED
|
@@ -1599,7 +1599,7 @@ class BambuPrinterMCPServer {
|
|
|
1599
1599
|
description: "Type of slicer to use. Bambu-compatible choices (bambustudio, orcaslicer, orcaslicer-bambulab) export sliced 3MF; aliases such as fulu-orca and orca-studio are accepted."
|
|
1600
1600
|
},
|
|
1601
1601
|
slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
|
|
1602
|
-
slicer_profile: { type: "string", description: "Path to
|
|
1602
|
+
slicer_profile: { type: "string", description: "Path to an optional process profile/config file. The exact bambu_model/nozzle machine preset is still required." },
|
|
1603
1603
|
template_3mf_path: { type: "string", description: "Optional template 3MF whose embedded Bambu slicer settings should be reused when slicing a new STL or 3MF." },
|
|
1604
1604
|
template_name: { type: "string", description: "Optional named template from the local registry. Resolves to template_3mf_path automatically." },
|
|
1605
1605
|
template_dir: { type: "string", description: "Optional template directory override when resolving template_name." },
|
|
@@ -49,6 +49,8 @@ export interface FlattenOptions {
|
|
|
49
49
|
filamentLeaves: string[];
|
|
50
50
|
/** Absolute path to `.../Resources/profiles`. */
|
|
51
51
|
profilesRoot: string;
|
|
52
|
+
/** Configured BBL directories for custom process/filament dependencies only. */
|
|
53
|
+
userProfileRoots?: string[];
|
|
52
54
|
/** Where to write flattened temp files. */
|
|
53
55
|
tempDir: string;
|
|
54
56
|
/** Vendor subdir under profilesRoot. Currently only "BBL" supported. */
|
|
@@ -82,6 +84,8 @@ export interface FlattenOptions {
|
|
|
82
84
|
wipe_tower_y?: unknown;
|
|
83
85
|
};
|
|
84
86
|
}
|
|
87
|
+
/** Resolve bundled machine defaults before choosing process and filament leaves. */
|
|
88
|
+
export declare function resolveBblMachineProfile(profilesRoot: string, machineLeaf: string): Promise<Record<string, unknown>>;
|
|
85
89
|
/** Best-effort extruder count for fallback nozzle_volume_type sizing. */
|
|
86
90
|
/** BambuStudio's built-in filament_colour default. */
|
|
87
91
|
export declare const DEFAULT_FILAMENT_COLOUR = "#00AE42";
|
|
@@ -97,4 +101,4 @@ export declare function flattenForCli(opts: FlattenOptions): Promise<FlattenedPr
|
|
|
97
101
|
*
|
|
98
102
|
* Override via BAMBU_PROFILES_ROOT env.
|
|
99
103
|
*/
|
|
100
|
-
export declare function detectProfilesRoot(slicerPath?: string): string;
|
|
104
|
+
export declare function detectProfilesRoot(slicerPath?: string, slicerType?: string): string;
|
|
@@ -39,11 +39,14 @@ import crypto from "node:crypto";
|
|
|
39
39
|
* abstract bases like `fdm_machine_common` and `fdm_bbl_3dp_001_common`,
|
|
40
40
|
* because those are the parents we'll walk to.
|
|
41
41
|
*/
|
|
42
|
-
async function buildNameIndex(profilesRoot, vendor) {
|
|
42
|
+
async function buildNameIndex(profilesRoot, vendor, userProfileRoots = []) {
|
|
43
43
|
const index = new Map();
|
|
44
44
|
const subdirs = ["machine", "process", "filament"];
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const directories = [
|
|
46
|
+
...subdirs.map(sub => path.join(profilesRoot, vendor, sub)),
|
|
47
|
+
...userProfileRoots.flatMap(root => ['process', 'filament'].map(sub => path.join(root, sub))),
|
|
48
|
+
];
|
|
49
|
+
for (const dir of directories) {
|
|
47
50
|
let entries;
|
|
48
51
|
try {
|
|
49
52
|
entries = await fs.readdir(dir);
|
|
@@ -72,6 +75,8 @@ async function buildNameIndex(profilesRoot, vendor) {
|
|
|
72
75
|
// Malformed profile -- skip, don't poison the index.
|
|
73
76
|
continue;
|
|
74
77
|
}
|
|
78
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
79
|
+
continue;
|
|
75
80
|
const name = data["name"];
|
|
76
81
|
if (typeof name !== "string" || name.length === 0)
|
|
77
82
|
continue;
|
|
@@ -123,6 +128,10 @@ function flattenData(data, index, visiting = new Set()) {
|
|
|
123
128
|
delete merged["include"];
|
|
124
129
|
return merged;
|
|
125
130
|
}
|
|
131
|
+
/** Resolve bundled machine defaults before choosing process and filament leaves. */
|
|
132
|
+
export async function resolveBblMachineProfile(profilesRoot, machineLeaf) {
|
|
133
|
+
return flattenByName(machineLeaf, await buildNameIndex(profilesRoot, 'BBL'));
|
|
134
|
+
}
|
|
126
135
|
/** Keys of an include template that describe the template, not the config. */
|
|
127
136
|
const INCLUDE_METADATA_KEYS = new Set(["name", "type", "from", "instantiation", "inherits", "include", "setting_id"]);
|
|
128
137
|
/**
|
|
@@ -514,14 +523,18 @@ export async function flattenForCli(opts) {
|
|
|
514
523
|
throw new Error(`profile-flatten: profilesRoot "${opts.profilesRoot}" does not contain "${vendor}/machine". ` +
|
|
515
524
|
`Set BAMBU_PROFILES_ROOT or check your BambuStudio install.`);
|
|
516
525
|
}
|
|
517
|
-
|
|
526
|
+
// Machine inheritance must never resolve through user process/filament files.
|
|
527
|
+
const machineIndex = await buildNameIndex(opts.profilesRoot, vendor);
|
|
528
|
+
const index = opts.userProfileRoots?.length
|
|
529
|
+
? await buildNameIndex(opts.profilesRoot, vendor, opts.userProfileRoots)
|
|
530
|
+
: machineIndex;
|
|
518
531
|
// Flatten each leaf.
|
|
519
532
|
if (opts.sourceProfiles?.filaments && opts.sourceProfiles.filaments.length !== opts.filamentLeaves.length) {
|
|
520
533
|
throw new Error("Every filament slot must have a source profile.");
|
|
521
534
|
}
|
|
522
535
|
const machineFlat = opts.sourceProfiles?.machine
|
|
523
|
-
? flattenData(opts.sourceProfiles.machine,
|
|
524
|
-
: flattenByName(opts.machineLeaf,
|
|
536
|
+
? flattenData(opts.sourceProfiles.machine, machineIndex)
|
|
537
|
+
: flattenByName(opts.machineLeaf, machineIndex);
|
|
525
538
|
const processFlat = opts.sourceProfiles?.process
|
|
526
539
|
? flattenData(opts.sourceProfiles.process, index)
|
|
527
540
|
: flattenByName(opts.processLeaf, index);
|
|
@@ -531,7 +544,7 @@ export async function flattenForCli(opts) {
|
|
|
531
544
|
});
|
|
532
545
|
// CLI-specific post-processing on machine profile only.
|
|
533
546
|
deriveNozzleVolumeType(machineFlat, opts.nozzleVolumeType);
|
|
534
|
-
applyMachineModelBedMetadata(machineFlat,
|
|
547
|
+
applyMachineModelBedMetadata(machineFlat, machineIndex);
|
|
535
548
|
// A standalone custom machine can accompany process/filament profiles that
|
|
536
549
|
// need BBL resolution; do not impose bundled model config on that machine.
|
|
537
550
|
const sourceMachine = opts.sourceProfiles?.machine;
|
|
@@ -603,7 +616,7 @@ async function writeTemp(tempDir, kind, leafName, data) {
|
|
|
603
616
|
*
|
|
604
617
|
* Override via BAMBU_PROFILES_ROOT env.
|
|
605
618
|
*/
|
|
606
|
-
export function detectProfilesRoot(slicerPath) {
|
|
619
|
+
export function detectProfilesRoot(slicerPath, slicerType = 'bambustudio') {
|
|
607
620
|
if (process.env["BAMBU_PROFILES_ROOT"]) {
|
|
608
621
|
return process.env["BAMBU_PROFILES_ROOT"];
|
|
609
622
|
}
|
|
@@ -622,14 +635,18 @@ export function detectProfilesRoot(slicerPath) {
|
|
|
622
635
|
}
|
|
623
636
|
catch { /* Probe the supplied path below. */ }
|
|
624
637
|
const bin = path.dirname(executable);
|
|
638
|
+
const installNames = slicerType === 'bambustudio'
|
|
639
|
+
? ['BambuStudio', 'bambu-studio']
|
|
640
|
+
: ['OrcaSlicer', 'orca-slicer', 'OrcaStudio', 'orca-studio'];
|
|
625
641
|
const candidates = path.basename(bin) === 'MacOS'
|
|
626
642
|
? [path.resolve(bin, '..', 'Resources', 'profiles')]
|
|
627
643
|
: [
|
|
628
644
|
path.join(bin, 'resources', 'profiles'),
|
|
629
645
|
path.join(bin, 'Resources', 'profiles'),
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
646
|
+
...installNames.flatMap(name => [
|
|
647
|
+
path.resolve(bin, '..', 'share', name, 'profiles'),
|
|
648
|
+
path.resolve(bin, '..', 'share', name, 'resources', 'profiles'),
|
|
649
|
+
]),
|
|
633
650
|
];
|
|
634
651
|
const found = candidates.find(root => existsSync(path.join(root, 'BBL', 'machine')));
|
|
635
652
|
// Do not select another installation if this executable has no profile tree.
|
|
@@ -637,5 +654,7 @@ export function detectProfilesRoot(slicerPath) {
|
|
|
637
654
|
return found ?? candidates[0];
|
|
638
655
|
}
|
|
639
656
|
// Default macOS install.
|
|
640
|
-
return
|
|
657
|
+
return slicerType === 'bambustudio'
|
|
658
|
+
? '/Applications/BambuStudio.app/Contents/Resources/profiles'
|
|
659
|
+
: '/Applications/OrcaSlicer.app/Contents/Resources/profiles';
|
|
641
660
|
}
|
|
@@ -9,7 +9,7 @@ import { promisify } from 'util';
|
|
|
9
9
|
import { EventEmitter } from 'events';
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import { execFile } from 'child_process';
|
|
12
|
-
import { flattenForCli, detectProfilesRoot } from '../slicer/profile-flatten.js';
|
|
12
|
+
import { flattenForCli, detectProfilesRoot, resolveBblMachineProfile } from '../slicer/profile-flatten.js';
|
|
13
13
|
const readFileAsync = promisify(fs.readFile);
|
|
14
14
|
const writeFileAsync = promisify(fs.writeFile);
|
|
15
15
|
const BAMBU_PROFILE_ROOTS = [
|
|
@@ -206,16 +206,31 @@ export class STLManipulator extends EventEmitter {
|
|
|
206
206
|
fs.writeFileSync(outPath, serialized);
|
|
207
207
|
return outPath;
|
|
208
208
|
}
|
|
209
|
-
resolveBambuLikeSettingsBundle(outputBase, slicerType, slicerProfile, printerPreset, bambuOptions, activeProfilesRoot) {
|
|
209
|
+
async resolveBambuLikeSettingsBundle(outputBase, slicerType, slicerProfile, printerPreset, bambuOptions, activeProfilesRoot) {
|
|
210
210
|
const roots = activeProfilesRoot
|
|
211
|
-
? [path.join(activeProfilesRoot, 'BBL')
|
|
211
|
+
? [path.join(activeProfilesRoot, 'BBL')]
|
|
212
212
|
: this.getAvailableProfileRoots();
|
|
213
|
-
const findProfile = (kind, name) => this.findProfileFile(kind, name, roots);
|
|
213
|
+
const findProfile = (kind, name) => this.findProfileFile(kind, name, kind === 'machine' ? roots : [...roots, ...configuredBambuProfileDirs()]);
|
|
214
|
+
if (!printerPreset) {
|
|
215
|
+
throw new Error('Printer preset is required for Bambu-compatible CLI slicing; select the exact bambu_model and nozzle_diameter.');
|
|
216
|
+
}
|
|
214
217
|
const machinePath = findProfile('machine', printerPreset);
|
|
215
|
-
if (
|
|
216
|
-
throw new Error(`Printer profile "${printerPreset}" was not found. Set BAMBU_PROFILES_ROOT to the matching
|
|
218
|
+
if (!machinePath) {
|
|
219
|
+
throw new Error(`Printer profile "${printerPreset}" was not found. Set BAMBU_PROFILES_ROOT to the matching ${slicerType} profile tree; refusing to use printer defaults.`);
|
|
220
|
+
}
|
|
221
|
+
let machineConfig;
|
|
222
|
+
try {
|
|
223
|
+
machineConfig = this.readJsonFile(machinePath);
|
|
224
|
+
if (!machineConfig || Array.isArray(machineConfig) || machineConfig.name !== printerPreset) {
|
|
225
|
+
throw new Error('expected a JSON machine profile with the selected preset name');
|
|
226
|
+
}
|
|
217
227
|
}
|
|
218
|
-
|
|
228
|
+
catch (error) {
|
|
229
|
+
throw new Error(`Printer profile "${printerPreset}" is invalid: ${error?.message ?? error}`);
|
|
230
|
+
}
|
|
231
|
+
// Some FULU/Orca machine leaves inherit their default process/filaments.
|
|
232
|
+
// Resolve those selections before building the positional CLI bundle.
|
|
233
|
+
machineConfig = await resolveBblMachineProfile(activeProfilesRoot ?? path.dirname(path.dirname(path.dirname(machinePath))), printerPreset);
|
|
219
234
|
const hasSlicerProfile = !!slicerProfile && fs.existsSync(slicerProfile);
|
|
220
235
|
const filamentPaths = [];
|
|
221
236
|
let processPath;
|
|
@@ -267,7 +282,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
267
282
|
}
|
|
268
283
|
}
|
|
269
284
|
if (!bambuOptions?.loadFilaments && filamentPaths.length === 0 && Array.isArray(parsedProfile.filament_ids)) {
|
|
270
|
-
const filamentIdIndex = this.buildFilamentIdIndex(roots);
|
|
285
|
+
const filamentIdIndex = this.buildFilamentIdIndex([...roots, ...configuredBambuProfileDirs()]);
|
|
271
286
|
for (const filamentId of parsedProfile.filament_ids) {
|
|
272
287
|
const filamentPath = filamentIdIndex.get(String(filamentId));
|
|
273
288
|
if (filamentPath) {
|
|
@@ -324,7 +339,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
324
339
|
};
|
|
325
340
|
}
|
|
326
341
|
/** Resolve BBL dependencies before invoking the CLI; failures stop the slice. */
|
|
327
|
-
async maybeFlattenBundle(bundle, bambuOptions,
|
|
342
|
+
async maybeFlattenBundle(bundle, bambuOptions, activeProfilesRoot) {
|
|
328
343
|
const parts = bundle.settingsArg?.split(';').filter(Boolean) ?? [];
|
|
329
344
|
const readProfile = (filePath) => {
|
|
330
345
|
try {
|
|
@@ -341,8 +356,8 @@ export class STLManipulator extends EventEmitter {
|
|
|
341
356
|
const settings = parts.map(readProfile);
|
|
342
357
|
// Read every slot: filtering unreadable names would change positional mapping.
|
|
343
358
|
const filaments = bundle.filamentPaths.map(readProfile);
|
|
344
|
-
const profilesRoot = detectProfilesRoot(
|
|
345
|
-
const profileRoots = [path.join(profilesRoot, 'BBL')
|
|
359
|
+
const profilesRoot = activeProfilesRoot ?? detectProfilesRoot(process.env.SLICER_PATH);
|
|
360
|
+
const profileRoots = [path.join(profilesRoot, 'BBL')];
|
|
346
361
|
const hasReferences = (profile) => (typeof profile.inherits === 'string' && profile.inherits.length > 0) ||
|
|
347
362
|
(profile.include !== undefined && profile.include !== null);
|
|
348
363
|
const isBundledFile = (filePath) => profileRoots.some(root => {
|
|
@@ -380,6 +395,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
380
395
|
: leafName(processPath, processProfile),
|
|
381
396
|
filamentLeaves: filaments.map((profile, i) => leafName(bundle.filamentPaths[i], profile)),
|
|
382
397
|
profilesRoot,
|
|
398
|
+
userProfileRoots: configuredBambuProfileDirs(),
|
|
383
399
|
tempDir: this.tempDir,
|
|
384
400
|
bedType: this.resolveBambuStudioBedType(bambuOptions?.bedType),
|
|
385
401
|
filamentColours: bambuOptions?.filamentColours ?? bundle.filamentColours,
|
|
@@ -1222,11 +1238,16 @@ export class STLManipulator extends EventEmitter {
|
|
|
1222
1238
|
const outputBase = path.basename(stlFilePath, is3mf ? '.3mf' : '.stl');
|
|
1223
1239
|
const bambuOutputPath = path.join(this.tempDir, outputBase + '_sliced.3mf');
|
|
1224
1240
|
const outputDir = path.dirname(bambuOutputPath);
|
|
1225
|
-
const
|
|
1241
|
+
const profilesRoot = detectProfilesRoot(slicerPath, slicerType);
|
|
1242
|
+
const rawBundle = await this.resolveBambuLikeSettingsBundle(outputBase, slicerType, slicerProfile, printerPreset, bambuOptions, profilesRoot);
|
|
1226
1243
|
// Resolve every BBL dependency before launching the slicer.
|
|
1227
|
-
const settingsBundle =
|
|
1228
|
-
|
|
1229
|
-
|
|
1244
|
+
const settingsBundle = await this.maybeFlattenBundle(await this.expandProjectFilaments(stlFilePath, rawBundle), bambuOptions, profilesRoot);
|
|
1245
|
+
// Inherited process G-code is now present; apply Orca's existing
|
|
1246
|
+
// absolute-extrusion normalization after resolving those ancestors.
|
|
1247
|
+
if (slicerType === 'orcaslicer' && settingsBundle.settingsArg) {
|
|
1248
|
+
const [machinePath, processPath] = settingsBundle.settingsArg.split(';');
|
|
1249
|
+
settingsBundle.settingsArg = [machinePath, this.writeTempJson(outputBase, 'process_orca_resolved', this.sanitizeProcessForOrca(this.readJsonFile(processPath), printerPreset))].join(';');
|
|
1250
|
+
}
|
|
1230
1251
|
args = [
|
|
1231
1252
|
'--slice', String(bambuOptions?.slicePlate ?? 0),
|
|
1232
1253
|
'--outputdir', outputDir,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2095,7 +2095,7 @@ class BambuPrinterMCPServer {
|
|
|
2095
2095
|
description: "Type of slicer to use. Bambu-compatible choices (bambustudio, orcaslicer, orcaslicer-bambulab) export sliced 3MF; aliases such as fulu-orca and orca-studio are accepted."
|
|
2096
2096
|
},
|
|
2097
2097
|
slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env). Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1." },
|
|
2098
|
-
slicer_profile: { type: "string", description: "Path to
|
|
2098
|
+
slicer_profile: { type: "string", description: "Path to an optional process profile/config file. The exact bambu_model/nozzle machine preset is still required." },
|
|
2099
2099
|
template_3mf_path: { type: "string", description: "Optional template 3MF whose embedded Bambu slicer settings should be reused when slicing a new STL or 3MF." },
|
|
2100
2100
|
template_name: { type: "string", description: "Optional named template from the local registry. Resolves to template_3mf_path automatically." },
|
|
2101
2101
|
template_dir: { type: "string", description: "Optional template directory override when resolving template_name." },
|
|
@@ -57,6 +57,8 @@ export interface FlattenOptions {
|
|
|
57
57
|
filamentLeaves: string[];
|
|
58
58
|
/** Absolute path to `.../Resources/profiles`. */
|
|
59
59
|
profilesRoot: string;
|
|
60
|
+
/** Configured BBL directories for custom process/filament dependencies only. */
|
|
61
|
+
userProfileRoots?: string[];
|
|
60
62
|
/** Where to write flattened temp files. */
|
|
61
63
|
tempDir: string;
|
|
62
64
|
/** Vendor subdir under profilesRoot. Currently only "BBL" supported. */
|
|
@@ -110,13 +112,17 @@ type NameIndex = Map<string, IndexedProfile>;
|
|
|
110
112
|
*/
|
|
111
113
|
async function buildNameIndex(
|
|
112
114
|
profilesRoot: string,
|
|
113
|
-
vendor: string
|
|
115
|
+
vendor: string,
|
|
116
|
+
userProfileRoots: string[] = []
|
|
114
117
|
): Promise<NameIndex> {
|
|
115
118
|
const index: NameIndex = new Map();
|
|
116
119
|
const subdirs: ProfileKind[] = ["machine", "process", "filament"];
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
const directories = [
|
|
122
|
+
...subdirs.map(sub => path.join(profilesRoot, vendor, sub)),
|
|
123
|
+
...userProfileRoots.flatMap(root => ['process', 'filament'].map(sub => path.join(root, sub))),
|
|
124
|
+
];
|
|
125
|
+
for (const dir of directories) {
|
|
120
126
|
let entries: string[];
|
|
121
127
|
try {
|
|
122
128
|
entries = await fs.readdir(dir);
|
|
@@ -141,6 +147,7 @@ async function buildNameIndex(
|
|
|
141
147
|
// Malformed profile -- skip, don't poison the index.
|
|
142
148
|
continue;
|
|
143
149
|
}
|
|
150
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) continue;
|
|
144
151
|
const name = data["name"];
|
|
145
152
|
if (typeof name !== "string" || name.length === 0) continue;
|
|
146
153
|
// First-write wins. BBL doesn't have name collisions in practice;
|
|
@@ -203,6 +210,14 @@ function flattenData(
|
|
|
203
210
|
return merged;
|
|
204
211
|
}
|
|
205
212
|
|
|
213
|
+
/** Resolve bundled machine defaults before choosing process and filament leaves. */
|
|
214
|
+
export async function resolveBblMachineProfile(
|
|
215
|
+
profilesRoot: string,
|
|
216
|
+
machineLeaf: string
|
|
217
|
+
): Promise<Record<string, unknown>> {
|
|
218
|
+
return flattenByName(machineLeaf, await buildNameIndex(profilesRoot, 'BBL'));
|
|
219
|
+
}
|
|
220
|
+
|
|
206
221
|
/** Keys of an include template that describe the template, not the config. */
|
|
207
222
|
const INCLUDE_METADATA_KEYS = new Set(["name", "type", "from", "instantiation", "inherits", "include", "setting_id"]);
|
|
208
223
|
|
|
@@ -644,15 +659,19 @@ export async function flattenForCli(opts: FlattenOptions): Promise<FlattenedProf
|
|
|
644
659
|
);
|
|
645
660
|
}
|
|
646
661
|
|
|
647
|
-
|
|
662
|
+
// Machine inheritance must never resolve through user process/filament files.
|
|
663
|
+
const machineIndex = await buildNameIndex(opts.profilesRoot, vendor);
|
|
664
|
+
const index = opts.userProfileRoots?.length
|
|
665
|
+
? await buildNameIndex(opts.profilesRoot, vendor, opts.userProfileRoots)
|
|
666
|
+
: machineIndex;
|
|
648
667
|
|
|
649
668
|
// Flatten each leaf.
|
|
650
669
|
if (opts.sourceProfiles?.filaments && opts.sourceProfiles.filaments.length !== opts.filamentLeaves.length) {
|
|
651
670
|
throw new Error("Every filament slot must have a source profile.");
|
|
652
671
|
}
|
|
653
672
|
const machineFlat = opts.sourceProfiles?.machine
|
|
654
|
-
? flattenData(opts.sourceProfiles.machine,
|
|
655
|
-
: flattenByName(opts.machineLeaf,
|
|
673
|
+
? flattenData(opts.sourceProfiles.machine, machineIndex)
|
|
674
|
+
: flattenByName(opts.machineLeaf, machineIndex);
|
|
656
675
|
const processFlat = opts.sourceProfiles?.process
|
|
657
676
|
? flattenData(opts.sourceProfiles.process, index)
|
|
658
677
|
: flattenByName(opts.processLeaf, index);
|
|
@@ -663,7 +682,7 @@ export async function flattenForCli(opts: FlattenOptions): Promise<FlattenedProf
|
|
|
663
682
|
|
|
664
683
|
// CLI-specific post-processing on machine profile only.
|
|
665
684
|
deriveNozzleVolumeType(machineFlat, opts.nozzleVolumeType);
|
|
666
|
-
applyMachineModelBedMetadata(machineFlat,
|
|
685
|
+
applyMachineModelBedMetadata(machineFlat, machineIndex);
|
|
667
686
|
// A standalone custom machine can accompany process/filament profiles that
|
|
668
687
|
// need BBL resolution; do not impose bundled model config on that machine.
|
|
669
688
|
const sourceMachine = opts.sourceProfiles?.machine;
|
|
@@ -751,7 +770,7 @@ async function writeTemp(
|
|
|
751
770
|
*
|
|
752
771
|
* Override via BAMBU_PROFILES_ROOT env.
|
|
753
772
|
*/
|
|
754
|
-
export function detectProfilesRoot(slicerPath?: string): string {
|
|
773
|
+
export function detectProfilesRoot(slicerPath?: string, slicerType = 'bambustudio'): string {
|
|
755
774
|
if (process.env["BAMBU_PROFILES_ROOT"]) {
|
|
756
775
|
return process.env["BAMBU_PROFILES_ROOT"];
|
|
757
776
|
}
|
|
@@ -767,14 +786,18 @@ export function detectProfilesRoot(slicerPath?: string): string {
|
|
|
767
786
|
// Package-manager launchers may be symlinks into the installation prefix.
|
|
768
787
|
try { executable = realpathSync(executable); } catch { /* Probe the supplied path below. */ }
|
|
769
788
|
const bin = path.dirname(executable);
|
|
789
|
+
const installNames = slicerType === 'bambustudio'
|
|
790
|
+
? ['BambuStudio', 'bambu-studio']
|
|
791
|
+
: ['OrcaSlicer', 'orca-slicer', 'OrcaStudio', 'orca-studio'];
|
|
770
792
|
const candidates = path.basename(bin) === 'MacOS'
|
|
771
793
|
? [path.resolve(bin, '..', 'Resources', 'profiles')]
|
|
772
794
|
: [
|
|
773
795
|
path.join(bin, 'resources', 'profiles'),
|
|
774
796
|
path.join(bin, 'Resources', 'profiles'),
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
797
|
+
...installNames.flatMap(name => [
|
|
798
|
+
path.resolve(bin, '..', 'share', name, 'profiles'),
|
|
799
|
+
path.resolve(bin, '..', 'share', name, 'resources', 'profiles'),
|
|
800
|
+
]),
|
|
778
801
|
];
|
|
779
802
|
const found = candidates.find(root => existsSync(path.join(root, 'BBL', 'machine')));
|
|
780
803
|
// Do not select another installation if this executable has no profile tree.
|
|
@@ -783,5 +806,7 @@ export function detectProfilesRoot(slicerPath?: string): string {
|
|
|
783
806
|
}
|
|
784
807
|
|
|
785
808
|
// Default macOS install.
|
|
786
|
-
return
|
|
809
|
+
return slicerType === 'bambustudio'
|
|
810
|
+
? '/Applications/BambuStudio.app/Contents/Resources/profiles'
|
|
811
|
+
: '/Applications/OrcaSlicer.app/Contents/Resources/profiles';
|
|
787
812
|
}
|
|
@@ -9,7 +9,7 @@ import { promisify } from 'util';
|
|
|
9
9
|
import { EventEmitter } from 'events';
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import { execFile } from 'child_process';
|
|
12
|
-
import { flattenForCli, detectProfilesRoot } from '../slicer/profile-flatten.js';
|
|
12
|
+
import { flattenForCli, detectProfilesRoot, resolveBblMachineProfile } from '../slicer/profile-flatten.js';
|
|
13
13
|
|
|
14
14
|
const readFileAsync = promisify(fs.readFile);
|
|
15
15
|
const writeFileAsync = promisify(fs.writeFile);
|
|
@@ -324,24 +324,39 @@ export class STLManipulator extends EventEmitter {
|
|
|
324
324
|
return outPath;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
private resolveBambuLikeSettingsBundle(
|
|
327
|
+
private async resolveBambuLikeSettingsBundle(
|
|
328
328
|
outputBase: string,
|
|
329
329
|
slicerType: 'orcaslicer' | 'orcaslicer-bambulab' | 'bambustudio',
|
|
330
330
|
slicerProfile?: string,
|
|
331
331
|
printerPreset?: string,
|
|
332
332
|
bambuOptions?: BambuSliceOptions,
|
|
333
333
|
activeProfilesRoot?: string
|
|
334
|
-
): BambuSettingsBundle {
|
|
334
|
+
): Promise<BambuSettingsBundle> {
|
|
335
335
|
const roots = activeProfilesRoot
|
|
336
|
-
? [path.join(activeProfilesRoot, 'BBL')
|
|
336
|
+
? [path.join(activeProfilesRoot, 'BBL')]
|
|
337
337
|
: this.getAvailableProfileRoots();
|
|
338
338
|
const findProfile = (kind: 'machine' | 'process' | 'filament', name?: string) =>
|
|
339
|
-
this.findProfileFile(kind, name, roots);
|
|
339
|
+
this.findProfileFile(kind, name, kind === 'machine' ? roots : [...roots, ...configuredBambuProfileDirs()]);
|
|
340
|
+
if (!printerPreset) {
|
|
341
|
+
throw new Error('Printer preset is required for Bambu-compatible CLI slicing; select the exact bambu_model and nozzle_diameter.');
|
|
342
|
+
}
|
|
340
343
|
const machinePath = findProfile('machine', printerPreset);
|
|
341
|
-
if (
|
|
342
|
-
throw new Error(`Printer profile "${printerPreset}" was not found. Set BAMBU_PROFILES_ROOT to the matching
|
|
344
|
+
if (!machinePath) {
|
|
345
|
+
throw new Error(`Printer profile "${printerPreset}" was not found. Set BAMBU_PROFILES_ROOT to the matching ${slicerType} profile tree; refusing to use printer defaults.`);
|
|
343
346
|
}
|
|
344
|
-
|
|
347
|
+
let machineConfig: any;
|
|
348
|
+
try {
|
|
349
|
+
machineConfig = this.readJsonFile(machinePath);
|
|
350
|
+
if (!machineConfig || Array.isArray(machineConfig) || machineConfig.name !== printerPreset) {
|
|
351
|
+
throw new Error('expected a JSON machine profile with the selected preset name');
|
|
352
|
+
}
|
|
353
|
+
} catch (error: any) {
|
|
354
|
+
throw new Error(`Printer profile "${printerPreset}" is invalid: ${error?.message ?? error}`);
|
|
355
|
+
}
|
|
356
|
+
// Some FULU/Orca machine leaves inherit their default process/filaments.
|
|
357
|
+
// Resolve those selections before building the positional CLI bundle.
|
|
358
|
+
machineConfig = await resolveBblMachineProfile(
|
|
359
|
+
activeProfilesRoot ?? path.dirname(path.dirname(path.dirname(machinePath))), printerPreset);
|
|
345
360
|
const hasSlicerProfile = !!slicerProfile && fs.existsSync(slicerProfile);
|
|
346
361
|
const filamentPaths: string[] = [];
|
|
347
362
|
|
|
@@ -405,7 +420,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
405
420
|
}
|
|
406
421
|
|
|
407
422
|
if (!bambuOptions?.loadFilaments && filamentPaths.length === 0 && Array.isArray(parsedProfile.filament_ids)) {
|
|
408
|
-
const filamentIdIndex = this.buildFilamentIdIndex(roots);
|
|
423
|
+
const filamentIdIndex = this.buildFilamentIdIndex([...roots, ...configuredBambuProfileDirs()]);
|
|
409
424
|
for (const filamentId of parsedProfile.filament_ids) {
|
|
410
425
|
const filamentPath = filamentIdIndex.get(String(filamentId));
|
|
411
426
|
if (filamentPath) {
|
|
@@ -477,7 +492,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
477
492
|
private async maybeFlattenBundle(
|
|
478
493
|
bundle: BambuSettingsBundle,
|
|
479
494
|
bambuOptions?: BambuSliceOptions,
|
|
480
|
-
|
|
495
|
+
activeProfilesRoot?: string
|
|
481
496
|
): Promise<BambuSettingsBundle> {
|
|
482
497
|
const parts = bundle.settingsArg?.split(';').filter(Boolean) ?? [];
|
|
483
498
|
const readProfile = (filePath: string): Record<string, unknown> => {
|
|
@@ -494,8 +509,8 @@ export class STLManipulator extends EventEmitter {
|
|
|
494
509
|
const settings = parts.map(readProfile);
|
|
495
510
|
// Read every slot: filtering unreadable names would change positional mapping.
|
|
496
511
|
const filaments = bundle.filamentPaths.map(readProfile);
|
|
497
|
-
const profilesRoot = detectProfilesRoot(
|
|
498
|
-
const profileRoots = [path.join(profilesRoot, 'BBL')
|
|
512
|
+
const profilesRoot = activeProfilesRoot ?? detectProfilesRoot(process.env.SLICER_PATH);
|
|
513
|
+
const profileRoots = [path.join(profilesRoot, 'BBL')];
|
|
499
514
|
const hasReferences = (profile: Record<string, unknown>) =>
|
|
500
515
|
(typeof profile.inherits === 'string' && profile.inherits.length > 0) ||
|
|
501
516
|
(profile.include !== undefined && profile.include !== null);
|
|
@@ -537,6 +552,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
537
552
|
: leafName(processPath, processProfile),
|
|
538
553
|
filamentLeaves: filaments.map((profile, i) => leafName(bundle.filamentPaths[i], profile)),
|
|
539
554
|
profilesRoot,
|
|
555
|
+
userProfileRoots: configuredBambuProfileDirs(),
|
|
540
556
|
tempDir: this.tempDir,
|
|
541
557
|
bedType: this.resolveBambuStudioBedType(bambuOptions?.bedType),
|
|
542
558
|
filamentColours: bambuOptions?.filamentColours ?? bundle.filamentColours,
|
|
@@ -1591,19 +1607,27 @@ export class STLManipulator extends EventEmitter {
|
|
|
1591
1607
|
const outputBase = path.basename(stlFilePath, is3mf ? '.3mf' : '.stl');
|
|
1592
1608
|
const bambuOutputPath = path.join(this.tempDir, outputBase + '_sliced.3mf');
|
|
1593
1609
|
const outputDir = path.dirname(bambuOutputPath);
|
|
1594
|
-
const
|
|
1610
|
+
const profilesRoot = detectProfilesRoot(slicerPath, slicerType);
|
|
1611
|
+
const rawBundle = await this.resolveBambuLikeSettingsBundle(
|
|
1595
1612
|
outputBase,
|
|
1596
1613
|
slicerType,
|
|
1597
1614
|
slicerProfile,
|
|
1598
1615
|
printerPreset,
|
|
1599
1616
|
bambuOptions,
|
|
1600
|
-
|
|
1617
|
+
profilesRoot
|
|
1601
1618
|
);
|
|
1602
1619
|
// Resolve every BBL dependency before launching the slicer.
|
|
1603
|
-
const settingsBundle =
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1620
|
+
const settingsBundle = await this.maybeFlattenBundle(
|
|
1621
|
+
await this.expandProjectFilaments(stlFilePath, rawBundle), bambuOptions, profilesRoot);
|
|
1622
|
+
// Inherited process G-code is now present; apply Orca's existing
|
|
1623
|
+
// absolute-extrusion normalization after resolving those ancestors.
|
|
1624
|
+
if (slicerType === 'orcaslicer' && settingsBundle.settingsArg) {
|
|
1625
|
+
const [machinePath, processPath] = settingsBundle.settingsArg.split(';');
|
|
1626
|
+
settingsBundle.settingsArg = [machinePath, this.writeTempJson(
|
|
1627
|
+
outputBase, 'process_orca_resolved',
|
|
1628
|
+
this.sanitizeProcessForOrca(this.readJsonFile(processPath), printerPreset)
|
|
1629
|
+
)].join(';');
|
|
1630
|
+
}
|
|
1607
1631
|
args = [
|
|
1608
1632
|
'--slice', String(bambuOptions?.slicePlate ?? 0),
|
|
1609
1633
|
'--outputdir', outputDir,
|