bambu-printer-mcp 1.0.4 → 1.0.8

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.
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import JSZip from "jszip";
5
5
  import { Client as FTPClient } from "basic-ftp";
6
+ import { Readable } from "node:stream";
6
7
  import { BambuPrinter } from "bambu-js";
7
8
  import { BambuClient, GCodeFileCommand, GCodeLineCommand, PushAllCommand, UpdateStateCommand, } from "bambu-node";
8
9
  class BambuClientStore {
@@ -170,7 +171,7 @@ export class BambuImplementation {
170
171
  print: {
171
172
  command: "project_file",
172
173
  param: `Metadata/${projectMetadata.plateFileName}`,
173
- url: `file:///sdcard/${remoteProjectPath}`,
174
+ url: `ftp://${remoteFileName}`,
174
175
  subtask_name: options.projectName,
175
176
  md5,
176
177
  flow_cali: options.flowCalibration ?? true,
@@ -192,7 +193,7 @@ export class BambuImplementation {
192
193
  await new Promise((resolve) => setTimeout(resolve, 300));
193
194
  return {
194
195
  status: "success",
195
- message: `Uploaded and started 3MF print: ${options.projectName}`,
196
+ message: `Uploaded 3MF and sent project_file print command: ${options.projectName}`,
196
197
  remoteProjectPath,
197
198
  plateFile: projectMetadata.plateFileName,
198
199
  platePath: projectMetadata.plateInternalPath,
@@ -323,7 +324,7 @@ export class BambuImplementation {
323
324
  * again, resulting in e.g. /cache/cache/file.3mf).
324
325
  */
325
326
  async ftpUpload(host, token, localPath, remotePath) {
326
- const client = new FTPClient(15000);
327
+ const client = new FTPClient(30000);
327
328
  try {
328
329
  await client.access({
329
330
  host,
@@ -333,12 +334,9 @@ export class BambuImplementation {
333
334
  secure: "implicit",
334
335
  secureOptions: { rejectUnauthorized: false },
335
336
  });
336
- // Use absolute path to avoid CWD side-effects
337
337
  const absoluteRemote = remotePath.startsWith("/") ? remotePath : `/${remotePath}`;
338
- const remoteDir = path.posix.dirname(absoluteRemote);
339
- await client.ensureDir(remoteDir);
340
- // uploadFrom with just the basename since we're already in the right dir
341
- await client.uploadFrom(localPath, path.posix.basename(absoluteRemote));
338
+ const fileData = await fs.readFile(localPath);
339
+ await client.uploadFrom(Readable.from(fileData), absoluteRemote);
342
340
  }
343
341
  finally {
344
342
  client.close();
@@ -22,6 +22,10 @@ export type TransformationParams = {
22
22
  relative?: boolean;
23
23
  selectionBounds?: THREE.Box3;
24
24
  };
25
+ export declare const SLICER_TYPES: readonly ["bambustudio", "orcaslicer", "orcaslicer-bambulab", "prusaslicer", "cura", "slic3r"];
26
+ export type SlicerType = typeof SLICER_TYPES[number];
27
+ export declare function normalizeSlicerType(rawSlicerType: string): SlicerType;
28
+ export declare function isBambuCompatibleSlicer(slicerType: SlicerType): boolean;
25
29
  export interface BambuSliceOptions {
26
30
  uptodate?: boolean;
27
31
  repetitions?: number;
@@ -122,15 +126,15 @@ export declare class STLManipulator extends EventEmitter {
122
126
  /**
123
127
  * Slice an STL or 3MF file using the specified slicer
124
128
  * @param stlFilePath Path to the input STL or 3MF file
125
- * @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, bambustudio)
129
+ * @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, orcaslicer-bambulab, bambustudio)
126
130
  * @param slicerPath Path to the slicer executable
127
131
  * @param slicerProfile Optional path to the slicer profile/config file
128
132
  * @param progressCallback Optional callback for progress updates
129
- * @param printerPreset Optional BambuStudio printer preset name (e.g., "Bambu Lab P1S 0.4 nozzle")
130
- * @param bambuOptions Optional BambuStudio-specific CLI flags
133
+ * @param printerPreset Optional Bambu-compatible printer preset name (e.g., "Bambu Lab P1S 0.4 nozzle")
134
+ * @param bambuOptions Optional Bambu-compatible CLI flags
131
135
  * @returns Path to the generated G-code or sliced 3MF file
132
136
  */
133
- sliceSTL(stlFilePath: string, slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer' | 'bambustudio', slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback, printerPreset?: string, bambuOptions?: BambuSliceOptions): Promise<string>;
137
+ sliceSTL(stlFilePath: string, slicerType: SlicerType, slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback, printerPreset?: string, bambuOptions?: BambuSliceOptions): Promise<string>;
134
138
  /**
135
139
  * Enhanced version of confirmTemperatures with better error handling
136
140
  * @param gcodePath Path to the G-code file
@@ -3,6 +3,7 @@ import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
3
3
  import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js';
4
4
  import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
5
5
  import fs from 'fs';
6
+ import os from 'os';
6
7
  import path from 'path';
7
8
  import { promisify } from 'util';
8
9
  import { EventEmitter } from 'events';
@@ -10,6 +11,100 @@ import * as crypto from 'crypto';
10
11
  import { execFile } from 'child_process';
11
12
  const readFileAsync = promisify(fs.readFile);
12
13
  const writeFileAsync = promisify(fs.writeFile);
14
+ export const SLICER_TYPES = [
15
+ 'bambustudio',
16
+ 'orcaslicer',
17
+ 'orcaslicer-bambulab',
18
+ 'prusaslicer',
19
+ 'cura',
20
+ 'slic3r',
21
+ ];
22
+ const SLICER_TYPE_ALIASES = {
23
+ bambustudio: 'bambustudio',
24
+ 'bambu-studio': 'bambustudio',
25
+ bblstudio: 'bambustudio',
26
+ orcaslicer: 'orcaslicer',
27
+ 'orca-slicer': 'orcaslicer',
28
+ orca: 'orcaslicer',
29
+ 'orcaslicer-bambulab': 'orcaslicer-bambulab',
30
+ 'orca-slicer-bambulab': 'orcaslicer-bambulab',
31
+ 'orca-bambulab': 'orcaslicer-bambulab',
32
+ 'fulu-orca': 'orcaslicer-bambulab',
33
+ 'fulu-orcaslicer': 'orcaslicer-bambulab',
34
+ 'fulu-orca-slicer': 'orcaslicer-bambulab',
35
+ 'orca-studio': 'orcaslicer-bambulab',
36
+ orcastudio: 'orcaslicer-bambulab',
37
+ 'orcaslicer-bmcu': 'orcaslicer-bambulab',
38
+ 'orca-bmcu': 'orcaslicer-bambulab',
39
+ prusaslicer: 'prusaslicer',
40
+ 'prusa-slicer': 'prusaslicer',
41
+ cura: 'cura',
42
+ curaengine: 'cura',
43
+ slic3r: 'slic3r',
44
+ };
45
+ export function normalizeSlicerType(rawSlicerType) {
46
+ const key = rawSlicerType.trim().toLowerCase().replace(/_/g, '-');
47
+ const normalized = SLICER_TYPE_ALIASES[key];
48
+ if (!normalized) {
49
+ throw new Error(`Unsupported slicer_type: "${rawSlicerType}". Valid values: ${SLICER_TYPES.join(', ')}`);
50
+ }
51
+ return normalized;
52
+ }
53
+ export function isBambuCompatibleSlicer(slicerType) {
54
+ return (slicerType === 'bambustudio' ||
55
+ slicerType === 'orcaslicer' ||
56
+ slicerType === 'orcaslicer-bambulab');
57
+ }
58
+ function isPathLikeExecutable(executable) {
59
+ return path.isAbsolute(executable) || /[\\/]/.test(executable);
60
+ }
61
+ function configuredBambuProfileDirs() {
62
+ const override = process.env.BAMBU_SLICER_PROFILE_DIRS;
63
+ if (override !== undefined) {
64
+ return override.split(path.delimiter).filter(Boolean);
65
+ }
66
+ const home = os.homedir();
67
+ const macAppSupport = path.join(home, 'Library', 'Application Support');
68
+ const linuxConfig = path.join(home, '.config');
69
+ const appData = process.env.APPDATA;
70
+ const localAppData = process.env.LOCALAPPDATA;
71
+ const appNames = [
72
+ 'BambuStudio',
73
+ 'Bambu Studio',
74
+ 'OrcaSlicer',
75
+ 'Orca Slicer',
76
+ 'OrcaStudio',
77
+ 'Orca Studio',
78
+ ];
79
+ const roots = [
80
+ macAppSupport,
81
+ linuxConfig,
82
+ ...(appData ? [appData] : []),
83
+ ...(localAppData ? [localAppData] : []),
84
+ ];
85
+ return roots.flatMap((root) => appNames.map((appName) => path.join(root, appName, 'system', 'BBL', 'machine')));
86
+ }
87
+ function resolveBambuPrinterProfile(printerPreset) {
88
+ if (printerPreset.includes('/') || printerPreset.includes('\\')) {
89
+ return undefined;
90
+ }
91
+ for (const profileDir of configuredBambuProfileDirs()) {
92
+ const profilePath = path.join(profileDir, `${printerPreset}.json`);
93
+ if (fs.existsSync(profilePath)) {
94
+ return profilePath;
95
+ }
96
+ }
97
+ return undefined;
98
+ }
99
+ function generatedBambuPrinterSettings(printerPreset) {
100
+ return {
101
+ type: 'machine',
102
+ from: 'system',
103
+ name: printerPreset,
104
+ inherits: printerPreset,
105
+ printer_settings_id: printerPreset,
106
+ };
107
+ }
13
108
  export class STLManipulator extends EventEmitter {
14
109
  constructor(tempDir = path.join(process.cwd(), 'temp')) {
15
110
  super();
@@ -733,18 +828,18 @@ export class STLManipulator extends EventEmitter {
733
828
  /**
734
829
  * Slice an STL or 3MF file using the specified slicer
735
830
  * @param stlFilePath Path to the input STL or 3MF file
736
- * @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, bambustudio)
831
+ * @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, orcaslicer-bambulab, bambustudio)
737
832
  * @param slicerPath Path to the slicer executable
738
833
  * @param slicerProfile Optional path to the slicer profile/config file
739
834
  * @param progressCallback Optional callback for progress updates
740
- * @param printerPreset Optional BambuStudio printer preset name (e.g., "Bambu Lab P1S 0.4 nozzle")
741
- * @param bambuOptions Optional BambuStudio-specific CLI flags
835
+ * @param printerPreset Optional Bambu-compatible printer preset name (e.g., "Bambu Lab P1S 0.4 nozzle")
836
+ * @param bambuOptions Optional Bambu-compatible CLI flags
742
837
  * @returns Path to the generated G-code or sliced 3MF file
743
838
  */
744
839
  async sliceSTL(stlFilePath, slicerType, slicerPath, slicerProfile, progressCallback, printerPreset, bambuOptions) {
745
840
  const operationId = this.generateOperationId();
746
841
  this.activeOperations.set(operationId, true);
747
- if (!fs.existsSync(slicerPath)) {
842
+ if (isPathLikeExecutable(slicerPath) && !fs.existsSync(slicerPath)) {
748
843
  throw new Error(`Slicer executable not found at: ${slicerPath}`);
749
844
  }
750
845
  if (slicerProfile && !fs.existsSync(slicerProfile)) {
@@ -769,24 +864,6 @@ export class STLManipulator extends EventEmitter {
769
864
  // Remove empty profile arg if not provided
770
865
  args = args.filter(arg => arg !== '');
771
866
  break;
772
- case 'orcaslicer': // Add OrcaSlicer case
773
- args = [
774
- // OrcaSlicer might use --load-settings like Bambu, or --load like Prusa
775
- // Assuming --load-settings based on discussion, adjust if needed.
776
- // Also assuming profile path points to the main .ini or .json config.
777
- '--load-settings', slicerProfile || '',
778
- '--output', outputFilePath, // Assuming --output works like PrusaSlicer
779
- stlFilePath
780
- ];
781
- // Alternative if --output doesn't specify filename:
782
- // args = [
783
- // '--load-settings', slicerProfile || '',
784
- // '--outputdir', this.tempDir,
785
- // stlFilePath
786
- // ];
787
- // Remove empty profile arg if not provided
788
- args = args.filter(arg => arg !== '');
789
- break;
790
867
  case 'cura':
791
868
  // CuraEngine CLI args are different, often requiring -s for settings
792
869
  // Example: curaengine slice -v -j cura_settings.json -s layer_height=0.2 -o output.gcode -l input.stl
@@ -802,7 +879,12 @@ export class STLManipulator extends EventEmitter {
802
879
  }
803
880
  break;
804
881
  case 'bambustudio':
805
- // Bambu Studio CLI: slice and export as 3MF with embedded gcode
882
+ case 'orcaslicer':
883
+ case 'orcaslicer-bambulab':
884
+ // Bambu-compatible CLI: slice and export as 3MF with embedded gcode.
885
+ // FULU's OrcaSlicer-bambulab fork keeps OrcaSlicer's Bambu-style
886
+ // --slice/--export-3mf path, so it must not use the generic Prusa
887
+ // --output G-code flow.
806
888
  // For 3MF input: slice in place and export sliced 3MF
807
889
  // For STL input: slice and export as 3MF
808
890
  {
@@ -819,14 +901,21 @@ export class STLManipulator extends EventEmitter {
819
901
  // Always allow newer-version 3MF files (the CLI rejects them by default)
820
902
  args.push('--allow-newer-file');
821
903
  // If a printer preset name is given AND no explicit profile was provided,
822
- // write a minimal settings JSON containing the printer_settings_id so
823
- // BambuStudio resolves the correct machine profile from its built-in presets.
904
+ // prefer the slicer's installed machine profile. Real BambuStudio rejects
905
+ // skeletal JSON without profile metadata, so only fall back to generated
906
+ // settings when no installed profile can be found.
824
907
  if (printerPreset && !slicerProfile) {
825
- const presetJson = path.join(this.tempDir, '_printer_preset.json');
826
- fs.writeFileSync(presetJson, JSON.stringify({ printer_settings_id: printerPreset }));
827
- args.push('--load-settings', presetJson);
908
+ const installedProfile = resolveBambuPrinterProfile(printerPreset);
909
+ if (installedProfile) {
910
+ args.push('--load-settings', installedProfile);
911
+ }
912
+ else {
913
+ const presetJson = path.join(this.tempDir, '_printer_preset.json');
914
+ fs.writeFileSync(presetJson, JSON.stringify(generatedBambuPrinterSettings(printerPreset), null, 2));
915
+ args.push('--load-settings', presetJson);
916
+ }
828
917
  }
829
- // BambuSliceOptions flags
918
+ // Bambu-compatible slicer flags
830
919
  if (bambuOptions?.uptodate)
831
920
  args.push('--uptodate');
832
921
  if (bambuOptions?.ensureOnBed)
@@ -862,7 +951,7 @@ export class STLManipulator extends EventEmitter {
862
951
  if (bambuOptions?.loadFilamentIds)
863
952
  args.push('--load-filament-ids', bambuOptions.loadFilamentIds);
864
953
  args.push(stlFilePath);
865
- // Override outputFilePath for bambustudio since it produces 3MF, not gcode
954
+ // Override outputFilePath because Bambu-compatible slicers produce 3MF, not G-code.
866
955
  outputFilePath = bambuOutputPath;
867
956
  }
868
957
  break;
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "bambu-printer-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.8",
4
4
  "description": "MCP server for Bambu Lab 3D printers with STL manipulation, slicing, and print management",
5
5
  "main": "dist/index.js",
6
- "repository": "https://github.com/DMontgomery40/bambu-printer-mcp",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/DMontgomery40/bambu-printer-mcp.git"
9
+ },
7
10
  "type": "module",
8
11
  "scripts": {
9
12
  "start": "node dist/index.js",
@@ -13,7 +16,7 @@
13
16
  "test": "npm run build && node --test tests/**/*.test.mjs"
14
17
  },
15
18
  "bin": {
16
- "bambu-printer-mcp": "./dist/index.js"
19
+ "bambu-printer-mcp": "dist/index.js"
17
20
  },
18
21
  "files": [
19
22
  "dist",