bambu-printer-mcp 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  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.
11
11
 
12
- 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, correct implementation for Bambu Lab hardware -- with two protocol-level bugs fixed that exist in the parent project.
12
+ 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.
13
13
 
14
14
  <details>
15
15
  <summary><strong>Click to expand Table of Contents</strong></summary>
@@ -25,8 +25,7 @@ This is a stripped-down, Bambu-only fork of [mcp-3D-printer-server](https://gith
25
25
  - [Install from source](#install-from-source)
26
26
  - [Configuration](#configuration)
27
27
  - [Environment variables reference](#environment-variables-reference)
28
- - [Usage with Claude Desktop](#usage-with-claude-desktop)
29
- - [Usage with Claude Code](#usage-with-claude-code)
28
+ - [Usage](#usage)
30
29
  - [Enabling Developer Mode (Required)](#enabling-developer-mode-required)
31
30
  - [Finding Your Bambu Printer's Serial Number and Access Token](#finding-your-bambu-printers-serial-number-and-access-token)
32
31
  - [AMS (Automatic Material System) Setup](#ams-automatic-material-system-setup)
@@ -56,7 +55,7 @@ This is a stripped-down, Bambu-only fork of [mcp-3D-printer-server](https://gith
56
55
 
57
56
  **What this is not.** This package intentionally supports only Bambu Lab printers. It does not include adapters for OctoPrint, Klipper (Moonraker), Duet, Repetier, Prusa Connect, or Creality Cloud. If you need multi-printer support, use the parent project [mcp-3D-printer-server](https://github.com/DMontgomery40/mcp-3D-printer-server) instead.
58
57
 
59
- **Why a separate package?** The parent project carries all printer adapters in a single binary. When working exclusively with Bambu hardware, that breadth adds noise and includes code paths with known Bambu-specific bugs. This fork strips the project to its Bambu core and applies two targeted protocol fixes described in [Bambu Communication Notes](#bambu-communication-notes-mqtt-and-ftp).
58
+ **Why a separate package?** The parent project carries all printer adapters in a single binary. When working exclusively with Bambu hardware, that breadth adds unnecessary weight. This fork strips the project to its Bambu core for a smaller, faster install. Both packages share the same protocol fixes and safety features.
60
59
 
61
60
  **Note on resource usage.** STL manipulation loads entire mesh geometry into memory. For large or complex STL files (greater than 10 MB), these operations can be memory-intensive. See [General Limitations and Considerations](#general-limitations-and-considerations) for details.
62
61
 
@@ -182,48 +181,9 @@ BLENDER_MCP_BRIDGE_COMMAND= # Shell command to invoke your Blender MCP bri
182
181
 
183
182
  ---
184
183
 
185
- ## Usage with Claude Desktop
184
+ ## Usage
186
185
 
187
- Edit your Claude Desktop configuration file. On macOS this is at `~/Library/Application Support/Claude/claude_desktop_config.json`. On Windows it is at `%APPDATA%\Claude\claude_desktop_config.json`.
188
-
189
- **Using npx (recommended -- always runs the latest version):**
190
-
191
- ```json
192
- {
193
- "mcpServers": {
194
- "bambu-printer": {
195
- "command": "npx",
196
- "args": ["-y", "bambu-printer-mcp"],
197
- "env": {
198
- "PRINTER_HOST": "192.168.1.100",
199
- "BAMBU_SERIAL": "01P00A123456789",
200
- "BAMBU_TOKEN": "your_access_token",
201
- "BAMBU_MODEL": "p1s"
202
- }
203
- }
204
- }
205
- }
206
- ```
207
-
208
- **Using a globally installed binary:**
209
-
210
- ```json
211
- {
212
- "mcpServers": {
213
- "bambu-printer": {
214
- "command": "bambu-printer-mcp",
215
- "env": {
216
- "PRINTER_HOST": "192.168.1.100",
217
- "BAMBU_SERIAL": "01P00A123456789",
218
- "BAMBU_TOKEN": "your_access_token",
219
- "BAMBU_MODEL": "p1s"
220
- }
221
- }
222
- }
223
- }
224
- ```
225
-
226
- **With slicer configured (for auto-slice and print_3mf workflows):**
186
+ Add this server to your MCP client's config (Claude Desktop, Claude Code, Cursor, Codex CLI, or any MCP-compatible client). The config format is the same everywhere -- an `mcpServers` entry with the command and env vars:
227
187
 
228
188
  ```json
229
189
  {
@@ -244,53 +204,29 @@ Edit your Claude Desktop configuration file. On macOS this is at `~/Library/Appl
244
204
  }
245
205
  ```
246
206
 
247
- After editing the file, restart Claude Desktop. You should see the bambu-printer tools appear in the tool list when you open a new conversation.
207
+ Where this config lives depends on your client:
248
208
 
249
- ---
209
+ | Client | Config location |
210
+ |--------|----------------|
211
+ | Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
212
+ | Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
213
+ | Claude Code (project) | `.mcp.json` in project root |
214
+ | Claude Code (global) | `~/.claude/settings.json` |
215
+ | Cursor | MCP settings in Cursor preferences |
216
+ | Codex CLI | MCP config per Codex docs |
250
217
 
251
- ## Usage with Claude Code
218
+ Restart your client after editing the config.
252
219
 
253
- In Claude Code, MCP servers are configured in your project's `.mcp.json` file or in your global Claude Code settings at `~/.claude/settings.json`.
220
+ ### Recommended: use with codemode-mcp
254
221
 
255
- **Project-level configuration (`.mcp.json` in your project root):**
222
+ For any MCP server with a large tool surface, wrapping it behind [codemode-mcp](https://github.com/jx-codes/codemode-mcp) dramatically reduces token usage. Instead of exposing every tool definition to the model (which can consume tens of thousands of tokens per turn), codemode lets the agent write code against a two-tool interface (`search()` and `execute()`), loading only the tools it needs on demand.
256
223
 
257
- ```json
258
- {
259
- "mcpServers": {
260
- "bambu-printer": {
261
- "command": "npx",
262
- "args": ["-y", "bambu-printer-mcp"],
263
- "env": {
264
- "PRINTER_HOST": "192.168.1.100",
265
- "BAMBU_SERIAL": "01P00A123456789",
266
- "BAMBU_TOKEN": "your_access_token",
267
- "BAMBU_MODEL": "p1s",
268
- "SLICER_TYPE": "bambustudio",
269
- "SLICER_PATH": "/Applications/BambuStudio.app/Contents/MacOS/BambuStudio"
270
- }
271
- }
272
- }
273
- }
274
- ```
224
+ Anthropic and Cloudflare independently demonstrated this pattern reduces MCP token costs by up to 98%:
275
225
 
276
- **Global configuration (`~/.claude/settings.json`):**
226
+ - [Code execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp) (Anthropic)
227
+ - [Code Mode: give agents an entire API in 1,000 tokens](https://blog.cloudflare.com/code-mode-mcp/) (Cloudflare)
277
228
 
278
- ```json
279
- {
280
- "mcpServers": {
281
- "bambu-printer": {
282
- "command": "npx",
283
- "args": ["-y", "bambu-printer-mcp"],
284
- "env": {
285
- "PRINTER_HOST": "192.168.1.100",
286
- "BAMBU_SERIAL": "01P00A123456789",
287
- "BAMBU_TOKEN": "your_access_token",
288
- "BAMBU_MODEL": "p1s"
289
- }
290
- }
291
- }
292
- }
293
- ```
229
+ This applies to all MCP servers, not just this one.
294
230
 
295
231
  ---
296
232
 
@@ -464,7 +400,7 @@ Bambu Lab printers do not use a conventional REST API. Instead, they expose two
464
400
 
465
401
  ### What this fork fixes
466
402
 
467
- The parent project (`mcp-3D-printer-server`) contains two Bambu-specific protocol bugs that this fork corrects.
403
+ Both this package and the parent project (`mcp-3D-printer-server`) include fixes for two protocol-level issues in the underlying `bambu-js` library.
468
404
 
469
405
  **Bug 1: FTP double-path error in bambu-js.**
470
406
 
@@ -489,7 +425,7 @@ private async ftpUpload(host, token, localPath, remotePath): Promise<void> {
489
425
 
490
426
  **Bug 2: AMS mapping format in the project_file MQTT command.**
491
427
 
492
- The `bambu-js` library's project file command hardcodes `use_ams: true` and does not support the `ams_mapping` field at all. Separately, the parent project constructs the mapping as a simple array of slot indices (e.g., `[0, 2]`), which does not match the OpenBambuAPI specification.
428
+ The `bambu-js` library's project file command hardcodes `use_ams: true` and does not support the `ams_mapping` field at all. Without the fix, the mapping is a simple array of slot indices (e.g., `[0, 2]`), which does not match the OpenBambuAPI specification.
493
429
 
494
430
  According to the OpenBambuAPI spec, `ams_mapping` must be a 5-element array where each position corresponds to a filament color slot in the print file. Unused positions must be padded with `-1`. For example, a print using only AMS slot 0 sends `[-1, -1, -1, -1, 0]`.
495
431
 
@@ -48,7 +48,17 @@ export declare class STLManipulator extends EventEmitter {
48
48
  boundingBox: BoundingBox;
49
49
  vertexCount: number;
50
50
  faceCount: number;
51
+ objects?: Array<{
52
+ name: string;
53
+ vertexCount: number;
54
+ faceCount: number;
55
+ boundingBox: BoundingBox;
56
+ }>;
51
57
  }>;
58
+ /**
59
+ * Extract mesh info from a 3MF file by parsing the XML object models.
60
+ */
61
+ private get3MFInfo;
52
62
  /**
53
63
  * Scale an STL model uniformly or along specific axes
54
64
  */
@@ -85,8 +85,12 @@ export class STLManipulator extends EventEmitter {
85
85
  */
86
86
  async getSTLInfo(stlFilePath) {
87
87
  try {
88
- const { geometry, boundingBox } = await this.loadSTL(stlFilePath);
89
88
  const fileStats = fs.statSync(stlFilePath);
89
+ // Handle 3MF files: extract mesh data from embedded object models
90
+ if (stlFilePath.toLowerCase().endsWith('.3mf')) {
91
+ return await this.get3MFInfo(stlFilePath, fileStats.size);
92
+ }
93
+ const { geometry, boundingBox } = await this.loadSTL(stlFilePath);
90
94
  // Count faces (each face is a triangle in STL)
91
95
  const positionAttribute = geometry.getAttribute('position');
92
96
  const vertexCount = positionAttribute.count;
@@ -115,6 +119,89 @@ export class STLManipulator extends EventEmitter {
115
119
  throw new Error(`Failed to get STL info: ${error instanceof Error ? error.message : String(error)}`);
116
120
  }
117
121
  }
122
+ /**
123
+ * Extract mesh info from a 3MF file by parsing the XML object models.
124
+ */
125
+ async get3MFInfo(filePath, fileSize) {
126
+ const JSZip = (await import('jszip')).default;
127
+ const data = fs.readFileSync(filePath);
128
+ const zip = await JSZip.loadAsync(data);
129
+ // Find all object model files
130
+ const objectFiles = Object.keys(zip.files).filter((name) => /^3D\/Objects\/.*\.model$/i.test(name));
131
+ const overallBox = new THREE.Box3(new THREE.Vector3(Infinity, Infinity, Infinity), new THREE.Vector3(-Infinity, -Infinity, -Infinity));
132
+ let totalVerts = 0;
133
+ let totalFaces = 0;
134
+ const objects = [];
135
+ for (const objFile of objectFiles) {
136
+ const content = await zip.file(objFile).async('string');
137
+ // Parse vertices from XML: <vertex x="..." y="..." z="..."/>
138
+ const vertexRegex = /<(?:\w+:)?vertex\s+x="([^"]+)"\s+y="([^"]+)"\s+z="([^"]+)"/g;
139
+ const triangleRegex = /<(?:\w+:)?triangle\s/g;
140
+ const objBox = new THREE.Box3(new THREE.Vector3(Infinity, Infinity, Infinity), new THREE.Vector3(-Infinity, -Infinity, -Infinity));
141
+ let vCount = 0;
142
+ let match;
143
+ while ((match = vertexRegex.exec(content)) !== null) {
144
+ const x = parseFloat(match[1]);
145
+ const y = parseFloat(match[2]);
146
+ const z = parseFloat(match[3]);
147
+ objBox.expandByPoint(new THREE.Vector3(x, y, z));
148
+ overallBox.expandByPoint(new THREE.Vector3(x, y, z));
149
+ vCount++;
150
+ }
151
+ let fCount = 0;
152
+ while (triangleRegex.exec(content) !== null)
153
+ fCount++;
154
+ totalVerts += vCount;
155
+ totalFaces += fCount;
156
+ if (vCount > 0) {
157
+ const center = new THREE.Vector3();
158
+ objBox.getCenter(center);
159
+ const dims = new THREE.Vector3();
160
+ objBox.getSize(dims);
161
+ objects.push({
162
+ name: path.basename(objFile),
163
+ vertexCount: vCount,
164
+ faceCount: fCount,
165
+ boundingBox: { min: objBox.min, max: objBox.max, center, dimensions: dims },
166
+ });
167
+ }
168
+ }
169
+ // If no object files found, try the main 3dmodel.model
170
+ if (objects.length === 0) {
171
+ const mainModel = zip.file('3D/3dmodel.model');
172
+ if (mainModel) {
173
+ const content = await mainModel.async('string');
174
+ const vertexRegex = /<(?:\w+:)?vertex\s+x="([^"]+)"\s+y="([^"]+)"\s+z="([^"]+)"/g;
175
+ const triangleRegex = /<(?:\w+:)?triangle\s/g;
176
+ let match;
177
+ while ((match = vertexRegex.exec(content)) !== null) {
178
+ const x = parseFloat(match[1]);
179
+ const y = parseFloat(match[2]);
180
+ const z = parseFloat(match[3]);
181
+ overallBox.expandByPoint(new THREE.Vector3(x, y, z));
182
+ totalVerts++;
183
+ }
184
+ while (triangleRegex.exec(content) !== null)
185
+ totalFaces++;
186
+ }
187
+ }
188
+ if (totalVerts === 0) {
189
+ throw new Error('No mesh data found in 3MF file. The archive may not contain any 3D objects.');
190
+ }
191
+ const center = new THREE.Vector3();
192
+ overallBox.getCenter(center);
193
+ const dimensions = new THREE.Vector3();
194
+ overallBox.getSize(dimensions);
195
+ return {
196
+ filePath,
197
+ fileName: path.basename(filePath),
198
+ fileSize,
199
+ boundingBox: { min: overallBox.min, max: overallBox.max, center, dimensions },
200
+ vertexCount: totalVerts,
201
+ faceCount: totalFaces,
202
+ objects,
203
+ };
204
+ }
118
205
  /**
119
206
  * Scale an STL model uniformly or along specific axes
120
207
  */
@@ -728,12 +815,15 @@ export class STLManipulator extends EventEmitter {
728
815
  if (slicerProfile) {
729
816
  args.push('--load-settings', slicerProfile);
730
817
  }
731
- // CRITICAL: Pass printer preset so slicer generates correct G-code
732
- // for the target printer model. Without this, the slicer uses its
733
- // default which may be a completely different printer, causing
734
- // dangerous bed crashes or axis limit violations.
818
+ // Always allow newer-version 3MF files (the CLI rejects them by default)
819
+ args.push('--allow-newer-file');
820
+ // If a printer preset name is given AND no explicit profile was provided,
821
+ // write a minimal settings JSON containing the printer_settings_id so
822
+ // BambuStudio resolves the correct machine profile from its built-in presets.
735
823
  if (printerPreset && !slicerProfile) {
736
- args.push('--load-machine', printerPreset);
824
+ const presetJson = path.join(this.tempDir, '_printer_preset.json');
825
+ fs.writeFileSync(presetJson, JSON.stringify({ printer_settings_id: printerPreset }));
826
+ args.push('--load-settings', presetJson);
737
827
  }
738
828
  args.push(stlFilePath);
739
829
  // Override outputFilePath for bambustudio since it produces 3MF, not gcode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bambu-printer-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "MCP server for Bambu Lab 3D printers with STL manipulation, slicing, and print management",
5
5
  "main": "dist/index.js",
6
6
  "repository": "https://github.com/DMontgomery40/bambu-printer-mcp",
@@ -141,23 +141,30 @@ export class STLManipulator extends EventEmitter {
141
141
  boundingBox: BoundingBox;
142
142
  vertexCount: number;
143
143
  faceCount: number;
144
+ objects?: Array<{ name: string; vertexCount: number; faceCount: number; boundingBox: BoundingBox }>;
144
145
  }> {
145
146
  try {
146
- const { geometry, boundingBox } = await this.loadSTL(stlFilePath);
147
147
  const fileStats = fs.statSync(stlFilePath);
148
-
148
+
149
+ // Handle 3MF files: extract mesh data from embedded object models
150
+ if (stlFilePath.toLowerCase().endsWith('.3mf')) {
151
+ return await this.get3MFInfo(stlFilePath, fileStats.size);
152
+ }
153
+
154
+ const { geometry, boundingBox } = await this.loadSTL(stlFilePath);
155
+
149
156
  // Count faces (each face is a triangle in STL)
150
157
  const positionAttribute = geometry.getAttribute('position');
151
158
  const vertexCount = positionAttribute.count;
152
159
  const faceCount = vertexCount / 3;
153
-
160
+
154
161
  // Calculate center and dimensions
155
162
  const center = new THREE.Vector3();
156
163
  boundingBox.getCenter(center);
157
-
164
+
158
165
  const dimensions = new THREE.Vector3();
159
166
  boundingBox.getSize(dimensions);
160
-
167
+
161
168
  return {
162
169
  filePath: stlFilePath,
163
170
  fileName: path.basename(stlFilePath),
@@ -177,6 +184,115 @@ export class STLManipulator extends EventEmitter {
177
184
  }
178
185
  }
179
186
 
187
+ /**
188
+ * Extract mesh info from a 3MF file by parsing the XML object models.
189
+ */
190
+ private async get3MFInfo(filePath: string, fileSize: number): Promise<{
191
+ filePath: string;
192
+ fileName: string;
193
+ fileSize: number;
194
+ boundingBox: BoundingBox;
195
+ vertexCount: number;
196
+ faceCount: number;
197
+ objects: Array<{ name: string; vertexCount: number; faceCount: number; boundingBox: BoundingBox }>;
198
+ }> {
199
+ const JSZip = (await import('jszip')).default;
200
+ const data = fs.readFileSync(filePath);
201
+ const zip = await JSZip.loadAsync(data);
202
+
203
+ // Find all object model files
204
+ const objectFiles = Object.keys(zip.files).filter(
205
+ (name) => /^3D\/Objects\/.*\.model$/i.test(name)
206
+ );
207
+
208
+ const overallBox = new THREE.Box3(
209
+ new THREE.Vector3(Infinity, Infinity, Infinity),
210
+ new THREE.Vector3(-Infinity, -Infinity, -Infinity)
211
+ );
212
+ let totalVerts = 0;
213
+ let totalFaces = 0;
214
+ const objects: Array<{ name: string; vertexCount: number; faceCount: number; boundingBox: BoundingBox }> = [];
215
+
216
+ for (const objFile of objectFiles) {
217
+ const content = await zip.file(objFile)!.async('string');
218
+ // Parse vertices from XML: <vertex x="..." y="..." z="..."/>
219
+ const vertexRegex = /<(?:\w+:)?vertex\s+x="([^"]+)"\s+y="([^"]+)"\s+z="([^"]+)"/g;
220
+ const triangleRegex = /<(?:\w+:)?triangle\s/g;
221
+
222
+ const objBox = new THREE.Box3(
223
+ new THREE.Vector3(Infinity, Infinity, Infinity),
224
+ new THREE.Vector3(-Infinity, -Infinity, -Infinity)
225
+ );
226
+ let vCount = 0;
227
+ let match: RegExpExecArray | null;
228
+ while ((match = vertexRegex.exec(content)) !== null) {
229
+ const x = parseFloat(match[1]);
230
+ const y = parseFloat(match[2]);
231
+ const z = parseFloat(match[3]);
232
+ objBox.expandByPoint(new THREE.Vector3(x, y, z));
233
+ overallBox.expandByPoint(new THREE.Vector3(x, y, z));
234
+ vCount++;
235
+ }
236
+
237
+ let fCount = 0;
238
+ while (triangleRegex.exec(content) !== null) fCount++;
239
+
240
+ totalVerts += vCount;
241
+ totalFaces += fCount;
242
+
243
+ if (vCount > 0) {
244
+ const center = new THREE.Vector3();
245
+ objBox.getCenter(center);
246
+ const dims = new THREE.Vector3();
247
+ objBox.getSize(dims);
248
+ objects.push({
249
+ name: path.basename(objFile),
250
+ vertexCount: vCount,
251
+ faceCount: fCount,
252
+ boundingBox: { min: objBox.min, max: objBox.max, center, dimensions: dims },
253
+ });
254
+ }
255
+ }
256
+
257
+ // If no object files found, try the main 3dmodel.model
258
+ if (objects.length === 0) {
259
+ const mainModel = zip.file('3D/3dmodel.model');
260
+ if (mainModel) {
261
+ const content = await mainModel.async('string');
262
+ const vertexRegex = /<(?:\w+:)?vertex\s+x="([^"]+)"\s+y="([^"]+)"\s+z="([^"]+)"/g;
263
+ const triangleRegex = /<(?:\w+:)?triangle\s/g;
264
+ let match: RegExpExecArray | null;
265
+ while ((match = vertexRegex.exec(content)) !== null) {
266
+ const x = parseFloat(match[1]);
267
+ const y = parseFloat(match[2]);
268
+ const z = parseFloat(match[3]);
269
+ overallBox.expandByPoint(new THREE.Vector3(x, y, z));
270
+ totalVerts++;
271
+ }
272
+ while (triangleRegex.exec(content) !== null) totalFaces++;
273
+ }
274
+ }
275
+
276
+ if (totalVerts === 0) {
277
+ throw new Error('No mesh data found in 3MF file. The archive may not contain any 3D objects.');
278
+ }
279
+
280
+ const center = new THREE.Vector3();
281
+ overallBox.getCenter(center);
282
+ const dimensions = new THREE.Vector3();
283
+ overallBox.getSize(dimensions);
284
+
285
+ return {
286
+ filePath,
287
+ fileName: path.basename(filePath),
288
+ fileSize,
289
+ boundingBox: { min: overallBox.min, max: overallBox.max, center, dimensions },
290
+ vertexCount: totalVerts,
291
+ faceCount: totalFaces,
292
+ objects,
293
+ };
294
+ }
295
+
180
296
  /**
181
297
  * Scale an STL model uniformly or along specific axes
182
298
  */
@@ -941,12 +1057,16 @@ export class STLManipulator extends EventEmitter {
941
1057
  if (slicerProfile) {
942
1058
  args.push('--load-settings', slicerProfile);
943
1059
  }
944
- // CRITICAL: Pass printer preset so slicer generates correct G-code
945
- // for the target printer model. Without this, the slicer uses its
946
- // default which may be a completely different printer, causing
947
- // dangerous bed crashes or axis limit violations.
1060
+ // Always allow newer-version 3MF files (the CLI rejects them by default)
1061
+ args.push('--allow-newer-file');
1062
+
1063
+ // If a printer preset name is given AND no explicit profile was provided,
1064
+ // write a minimal settings JSON containing the printer_settings_id so
1065
+ // BambuStudio resolves the correct machine profile from its built-in presets.
948
1066
  if (printerPreset && !slicerProfile) {
949
- args.push('--load-machine', printerPreset);
1067
+ const presetJson = path.join(this.tempDir, '_printer_preset.json');
1068
+ fs.writeFileSync(presetJson, JSON.stringify({ printer_settings_id: printerPreset }));
1069
+ args.push('--load-settings', presetJson);
950
1070
  }
951
1071
  args.push(stlFilePath);
952
1072
  // Override outputFilePath for bambustudio since it produces 3MF, not gcode