bambu-printer-mcp 1.0.5 → 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.
- package/README.md +485 -26
- package/dist/bambu-network-bridge.d.ts +63 -0
- package/dist/bambu-network-bridge.js +393 -0
- package/dist/index.js +562 -23
- package/dist/printers/bambu.js +6 -8
- package/dist/stl/stl-manipulator.d.ts +8 -4
- package/dist/stl/stl-manipulator.js +119 -30
- package/package.json +1 -1
- package/src/bambu-network-bridge.ts +526 -0
- package/src/index.ts +671 -26
- package/src/printers/bambu.ts +6 -8
- package/src/stl/stl-manipulator.ts +143 -33
package/dist/printers/bambu.js
CHANGED
|
@@ -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: `
|
|
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
|
|
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(
|
|
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
|
|
339
|
-
await client.
|
|
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
|
|
130
|
-
* @param bambuOptions Optional
|
|
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:
|
|
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
|
|
741
|
-
* @param bambuOptions Optional
|
|
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
|
-
|
|
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
|
-
//
|
|
823
|
-
//
|
|
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
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
//
|
|
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
|
|
954
|
+
// Override outputFilePath because Bambu-compatible slicers produce 3MF, not G-code.
|
|
866
955
|
outputFilePath = bambuOutputPath;
|
|
867
956
|
}
|
|
868
957
|
break;
|