bambu-printer-mcp 1.0.5 → 1.1.2

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,13 +3,103 @@ 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';
9
10
  import * as crypto from 'crypto';
10
11
  import { execFile } from 'child_process';
12
+ import { flattenForCli, detectProfilesRoot } from '../slicer/profile-flatten.js';
11
13
  const readFileAsync = promisify(fs.readFile);
12
14
  const writeFileAsync = promisify(fs.writeFile);
15
+ const BAMBU_PROFILE_ROOTS = [
16
+ '/Applications/BambuStudio.app/Contents/Resources/profiles/BBL',
17
+ '/Applications/OrcaSlicer.app/Contents/Resources/profiles/BBL',
18
+ ];
19
+ const BAMBU_CLI_BED_TYPES = {
20
+ textured_plate: 'Textured PEI Plate',
21
+ cool_plate: 'Cool Plate',
22
+ engineering_plate: 'Engineering Plate',
23
+ hot_plate: 'High Temp Plate',
24
+ };
25
+ export const SLICER_TYPES = [
26
+ 'bambustudio',
27
+ 'orcaslicer',
28
+ 'orcaslicer-bambulab',
29
+ 'prusaslicer',
30
+ 'cura',
31
+ 'slic3r',
32
+ ];
33
+ const SLICER_TYPE_ALIASES = {
34
+ bambustudio: 'bambustudio',
35
+ 'bambu-studio': 'bambustudio',
36
+ bblstudio: 'bambustudio',
37
+ orcaslicer: 'orcaslicer',
38
+ 'orca-slicer': 'orcaslicer',
39
+ orca: 'orcaslicer',
40
+ 'orcaslicer-bambulab': 'orcaslicer-bambulab',
41
+ 'orca-slicer-bambulab': 'orcaslicer-bambulab',
42
+ 'orca-bambulab': 'orcaslicer-bambulab',
43
+ 'fulu-orca': 'orcaslicer-bambulab',
44
+ 'fulu-orcaslicer': 'orcaslicer-bambulab',
45
+ 'fulu-orca-slicer': 'orcaslicer-bambulab',
46
+ 'orca-studio': 'orcaslicer-bambulab',
47
+ orcastudio: 'orcaslicer-bambulab',
48
+ 'orcaslicer-bmcu': 'orcaslicer-bambulab',
49
+ 'orca-bmcu': 'orcaslicer-bambulab',
50
+ prusaslicer: 'prusaslicer',
51
+ 'prusa-slicer': 'prusaslicer',
52
+ cura: 'cura',
53
+ curaengine: 'cura',
54
+ slic3r: 'slic3r',
55
+ };
56
+ export function normalizeSlicerType(rawSlicerType) {
57
+ const key = rawSlicerType.trim().toLowerCase().replace(/_/g, '-');
58
+ const normalized = SLICER_TYPE_ALIASES[key];
59
+ if (!normalized) {
60
+ throw new Error(`Unsupported slicer_type: "${rawSlicerType}". Valid values: ${SLICER_TYPES.join(', ')}`);
61
+ }
62
+ return normalized;
63
+ }
64
+ export function isBambuCompatibleSlicer(slicerType) {
65
+ return (slicerType === 'bambustudio' ||
66
+ slicerType === 'orcaslicer' ||
67
+ slicerType === 'orcaslicer-bambulab');
68
+ }
69
+ function isPathLikeExecutable(slicerPath) {
70
+ return (path.isAbsolute(slicerPath) ||
71
+ slicerPath.startsWith("./") ||
72
+ slicerPath.startsWith("../") ||
73
+ slicerPath.includes("/") ||
74
+ slicerPath.includes("\\") ||
75
+ /^[A-Za-z]:/.test(slicerPath));
76
+ }
77
+ function configuredBambuProfileDirs() {
78
+ const override = process.env.BAMBU_SLICER_PROFILE_DIRS;
79
+ if (override !== undefined) {
80
+ return override.split(path.delimiter).filter(Boolean);
81
+ }
82
+ const home = os.homedir();
83
+ const macAppSupport = path.join(home, 'Library', 'Application Support');
84
+ const linuxConfig = path.join(home, '.config');
85
+ const appData = process.env.APPDATA;
86
+ const localAppData = process.env.LOCALAPPDATA;
87
+ const appNames = [
88
+ 'BambuStudio',
89
+ 'Bambu Studio',
90
+ 'OrcaSlicer',
91
+ 'Orca Slicer',
92
+ 'OrcaStudio',
93
+ 'Orca Studio',
94
+ ];
95
+ const roots = [
96
+ macAppSupport,
97
+ linuxConfig,
98
+ ...(appData ? [appData] : []),
99
+ ...(localAppData ? [localAppData] : []),
100
+ ];
101
+ return roots.flatMap((root) => appNames.map((appName) => path.join(root, appName, 'system', 'BBL')));
102
+ }
13
103
  export class STLManipulator extends EventEmitter {
14
104
  constructor(tempDir = path.join(process.cwd(), 'temp')) {
15
105
  super();
@@ -26,6 +116,266 @@ export class STLManipulator extends EventEmitter {
26
116
  generateOperationId() {
27
117
  return crypto.randomUUID();
28
118
  }
119
+ getAvailableProfileRoots() {
120
+ return [...BAMBU_PROFILE_ROOTS, ...configuredBambuProfileDirs()].filter((root) => fs.existsSync(root));
121
+ }
122
+ findProfileFile(category, profileName) {
123
+ if (!profileName) {
124
+ return undefined;
125
+ }
126
+ for (const root of this.getAvailableProfileRoots()) {
127
+ const candidate = path.join(root, category, `${profileName}.json`);
128
+ if (fs.existsSync(candidate)) {
129
+ return candidate;
130
+ }
131
+ }
132
+ return undefined;
133
+ }
134
+ buildFilamentIdIndex() {
135
+ const index = new Map();
136
+ for (const root of this.getAvailableProfileRoots()) {
137
+ const filamentDir = path.join(root, 'filament');
138
+ if (!fs.existsSync(filamentDir)) {
139
+ continue;
140
+ }
141
+ for (const entry of fs.readdirSync(filamentDir)) {
142
+ if (!entry.endsWith('.json')) {
143
+ continue;
144
+ }
145
+ const filePath = path.join(filamentDir, entry);
146
+ try {
147
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
148
+ const filamentId = typeof parsed?.filament_id === 'string' ? parsed.filament_id.trim() : '';
149
+ if (filamentId && !index.has(filamentId)) {
150
+ index.set(filamentId, filePath);
151
+ }
152
+ }
153
+ catch {
154
+ // Ignore malformed JSON.
155
+ }
156
+ }
157
+ }
158
+ return index;
159
+ }
160
+ readJsonFile(filePath) {
161
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
162
+ }
163
+ stripAbsoluteExtruderResets(value) {
164
+ if (typeof value === 'string') {
165
+ return value
166
+ .split(/\r?\n/)
167
+ .filter((line) => line.trim().toUpperCase() !== 'G92 E0')
168
+ .join('\n');
169
+ }
170
+ if (Array.isArray(value)) {
171
+ return value.filter((line) => String(line).trim().toUpperCase() !== 'G92 E0');
172
+ }
173
+ return value;
174
+ }
175
+ sanitizeProcessForOrca(processConfig, printerPreset) {
176
+ const sanitized = { ...processConfig };
177
+ sanitized.type = 'process';
178
+ if (!sanitized.from || sanitized.from === 'project') {
179
+ sanitized.from = 'User';
180
+ }
181
+ if (printerPreset) {
182
+ sanitized.compatible_printers = [printerPreset];
183
+ }
184
+ if (sanitized.prime_tower_brim_width !== undefined &&
185
+ Number(sanitized.prime_tower_brim_width) < 0) {
186
+ sanitized.prime_tower_brim_width = '0';
187
+ }
188
+ else if (sanitized.prime_tower_brim_width !== undefined) {
189
+ sanitized.prime_tower_brim_width = String(sanitized.prime_tower_brim_width);
190
+ }
191
+ sanitized.use_relative_e_distances = '0';
192
+ sanitized.before_layer_change_gcode = this.stripAbsoluteExtruderResets(sanitized.before_layer_change_gcode);
193
+ sanitized.layer_gcode = this.stripAbsoluteExtruderResets(sanitized.layer_gcode);
194
+ sanitized.layer_change_gcode = this.stripAbsoluteExtruderResets(sanitized.layer_change_gcode);
195
+ return sanitized;
196
+ }
197
+ writeTempJson(outputBase, suffix, value) {
198
+ const outPath = path.join(this.tempDir, `${outputBase}_${suffix}.json`);
199
+ fs.writeFileSync(outPath, JSON.stringify(value, null, 2));
200
+ return outPath;
201
+ }
202
+ resolveBambuLikeSettingsBundle(outputBase, slicerType, slicerProfile, printerPreset, bambuOptions) {
203
+ const machinePath = this.findProfileFile('machine', printerPreset);
204
+ const machineConfig = machinePath ? this.readJsonFile(machinePath) : null;
205
+ const hasSlicerProfile = !!slicerProfile && fs.existsSync(slicerProfile);
206
+ const filamentPaths = [];
207
+ let processPath;
208
+ let parsedProfile = null;
209
+ if (hasSlicerProfile) {
210
+ try {
211
+ parsedProfile = this.readJsonFile(slicerProfile);
212
+ }
213
+ catch {
214
+ parsedProfile = null;
215
+ }
216
+ }
217
+ if (parsedProfile && typeof parsedProfile === 'object') {
218
+ const inheritedProcessName = (typeof parsedProfile.inherits === 'string' && parsedProfile.inherits) ||
219
+ (typeof parsedProfile.print_settings_id === 'string' &&
220
+ parsedProfile.print_settings_id !== parsedProfile.name
221
+ ? parsedProfile.print_settings_id
222
+ : undefined) ||
223
+ (typeof parsedProfile.default_print_profile === 'string'
224
+ ? parsedProfile.default_print_profile
225
+ : undefined) ||
226
+ (typeof machineConfig?.default_print_profile === 'string'
227
+ ? machineConfig.default_print_profile
228
+ : undefined);
229
+ const inheritedProcessPath = this.findProfileFile('process', inheritedProcessName);
230
+ const inheritedProcess = inheritedProcessPath && fs.existsSync(inheritedProcessPath)
231
+ ? this.readJsonFile(inheritedProcessPath)
232
+ : {};
233
+ const mergedProcess = {
234
+ ...inheritedProcess,
235
+ ...parsedProfile,
236
+ type: 'process',
237
+ };
238
+ processPath = this.writeTempJson(outputBase, slicerType === 'orcaslicer' ? 'process_orca' : 'process', slicerType === 'orcaslicer'
239
+ ? this.sanitizeProcessForOrca(mergedProcess, printerPreset)
240
+ : mergedProcess);
241
+ const defaultFilamentProfiles = Array.isArray(parsedProfile.default_filament_profile)
242
+ ? parsedProfile.default_filament_profile
243
+ : [];
244
+ for (const profileName of defaultFilamentProfiles) {
245
+ const filamentPath = this.findProfileFile('filament', String(profileName));
246
+ if (filamentPath) {
247
+ filamentPaths.push(filamentPath);
248
+ }
249
+ }
250
+ if (filamentPaths.length === 0 && Array.isArray(parsedProfile.filament_ids)) {
251
+ const filamentIdIndex = this.buildFilamentIdIndex();
252
+ for (const filamentId of parsedProfile.filament_ids) {
253
+ const filamentPath = filamentIdIndex.get(String(filamentId));
254
+ if (filamentPath) {
255
+ filamentPaths.push(filamentPath);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ else if (hasSlicerProfile) {
261
+ processPath = slicerProfile;
262
+ }
263
+ if (!processPath) {
264
+ const defaultProcessName = typeof machineConfig?.default_print_profile === 'string'
265
+ ? machineConfig.default_print_profile
266
+ : undefined;
267
+ const defaultProcessPath = this.findProfileFile('process', defaultProcessName);
268
+ if (defaultProcessPath) {
269
+ processPath =
270
+ slicerType === 'orcaslicer'
271
+ ? this.writeTempJson(outputBase, 'process_default_orca', this.sanitizeProcessForOrca(this.readJsonFile(defaultProcessPath), printerPreset))
272
+ : defaultProcessPath;
273
+ }
274
+ }
275
+ if (!bambuOptions?.loadFilaments &&
276
+ filamentPaths.length === 0 &&
277
+ Array.isArray(machineConfig?.default_filament_profile)) {
278
+ for (const profileName of machineConfig.default_filament_profile) {
279
+ const filamentPath = this.findProfileFile('filament', String(profileName));
280
+ if (filamentPath) {
281
+ filamentPaths.push(filamentPath);
282
+ }
283
+ }
284
+ }
285
+ if (bambuOptions?.loadFilaments) {
286
+ filamentPaths.length = 0;
287
+ for (const filamentPath of bambuOptions.loadFilaments.split(';')) {
288
+ const trimmed = filamentPath.trim();
289
+ if (trimmed) {
290
+ filamentPaths.push(trimmed);
291
+ }
292
+ }
293
+ }
294
+ const settingsParts = [machinePath, processPath].filter((entry) => Boolean(entry));
295
+ return {
296
+ settingsArg: settingsParts.length > 0 ? settingsParts.join(';') : undefined,
297
+ filamentPaths: Array.from(new Set(filamentPaths)),
298
+ };
299
+ }
300
+ /**
301
+ * Optionally rewrite a Bambu-like settings bundle so the paths point at
302
+ * fully-flattened temp configs instead of the BBL-shipped leaf JSONs.
303
+ *
304
+ * BambuStudio's CLI does not resolve the `inherits` chain when loading
305
+ * profiles via --load-settings / --load-filaments, which causes a
306
+ * cluster of upstream bugs (see https://github.com/bambulab/BambuStudio/issues/9636
307
+ * and #9968). Our flattener (src/slicer/profile-flatten.ts) reproduces
308
+ * what the GUI does at slice time so the CLI accepts the configs.
309
+ *
310
+ * Opt-in via `BAMBU_CLI_FLATTEN=true`. When the env var is unset or
311
+ * not "true"/"1", returns the bundle unchanged so behavior is
312
+ * backward-compatible. When enabled, only BBL-shipped leaves get
313
+ * flattened; user-provided custom configs pass through untouched.
314
+ */
315
+ async maybeFlattenBundle(bundle, bambuOptions, activeSlicerPath) {
316
+ const flag = process.env.BAMBU_CLI_FLATTEN;
317
+ if (flag !== 'true' && flag !== '1')
318
+ return bundle;
319
+ if (!bundle.settingsArg)
320
+ return bundle;
321
+ // settingsArg is "machine.json;process.json".
322
+ const parts = bundle.settingsArg.split(';').filter(Boolean);
323
+ if (parts.length < 2)
324
+ return bundle;
325
+ const [machinePath, processPath] = parts;
326
+ // Pull leaf names from each JSON's `name` field. If any is missing or
327
+ // looks non-BBL (e.g. a user-saved custom config), bail out and
328
+ // return the original bundle untouched.
329
+ const machineLeaf = this.readLeafName(machinePath);
330
+ const processLeaf = this.readLeafName(processPath);
331
+ const filamentLeaves = bundle.filamentPaths
332
+ .map((p) => this.readLeafName(p))
333
+ .filter((n) => Boolean(n));
334
+ if (!machineLeaf || !processLeaf || filamentLeaves.length === 0) {
335
+ console.log('[cli-flatten] skipping: could not derive BBL leaf names from bundle paths');
336
+ return bundle;
337
+ }
338
+ const bedType = this.resolveBambuStudioBedType(bambuOptions?.bedType);
339
+ try {
340
+ const profilesRoot = detectProfilesRoot(activeSlicerPath || process.env.SLICER_PATH);
341
+ const flat = await flattenForCli({
342
+ machineLeaf,
343
+ processLeaf,
344
+ filamentLeaves,
345
+ profilesRoot,
346
+ tempDir: this.tempDir,
347
+ bedType,
348
+ });
349
+ console.log(`[cli-flatten] applied for ${machineLeaf} (cliOverlay=${flat.meta.cliOverlayApplied})`);
350
+ return {
351
+ settingsArg: `${flat.machinePath};${flat.processPath}`,
352
+ filamentPaths: flat.filamentPaths,
353
+ };
354
+ }
355
+ catch (err) {
356
+ console.error(`[cli-flatten] failed, falling back to unflattened bundle: ${err?.message ?? err}`);
357
+ return bundle;
358
+ }
359
+ }
360
+ resolveBambuStudioBedType(bedType) {
361
+ if (!bedType)
362
+ return undefined;
363
+ const normalized = bedType.trim().toLowerCase();
364
+ if (normalized === 'supertack_plate') {
365
+ throw new Error('BambuStudio CLI SuperTack bed type is not verified; use a pre-sliced 3MF or choose textured_plate, cool_plate, engineering_plate, or hot_plate.');
366
+ }
367
+ return BAMBU_CLI_BED_TYPES[normalized] || bedType;
368
+ }
369
+ /** Read a profile JSON's top-level `name` field, or null if unreadable. */
370
+ readLeafName(filePath) {
371
+ try {
372
+ const data = this.readJsonFile(filePath);
373
+ return typeof data?.name === 'string' && data.name.length > 0 ? data.name : null;
374
+ }
375
+ catch {
376
+ return null;
377
+ }
378
+ }
29
379
  /**
30
380
  * Load STL file and return geometry and bounding box
31
381
  */
@@ -744,7 +1094,7 @@ export class STLManipulator extends EventEmitter {
744
1094
  async sliceSTL(stlFilePath, slicerType, slicerPath, slicerProfile, progressCallback, printerPreset, bambuOptions) {
745
1095
  const operationId = this.generateOperationId();
746
1096
  this.activeOperations.set(operationId, true);
747
- if (!fs.existsSync(slicerPath)) {
1097
+ if (isPathLikeExecutable(slicerPath) && !fs.existsSync(slicerPath)) {
748
1098
  throw new Error(`Slicer executable not found at: ${slicerPath}`);
749
1099
  }
750
1100
  if (slicerProfile && !fs.existsSync(slicerProfile)) {
@@ -769,24 +1119,6 @@ export class STLManipulator extends EventEmitter {
769
1119
  // Remove empty profile arg if not provided
770
1120
  args = args.filter(arg => arg !== '');
771
1121
  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
1122
  case 'cura':
791
1123
  // CuraEngine CLI args are different, often requiring -s for settings
792
1124
  // Example: curaengine slice -v -j cura_settings.json -s layer_height=0.2 -o output.gcode -l input.stl
@@ -802,30 +1134,33 @@ export class STLManipulator extends EventEmitter {
802
1134
  }
803
1135
  break;
804
1136
  case 'bambustudio':
805
- // Bambu Studio CLI: slice and export as 3MF with embedded gcode
1137
+ case 'orcaslicer':
1138
+ case 'orcaslicer-bambulab':
1139
+ // Bambu-compatible CLI: slice and export as 3MF with embedded gcode.
1140
+ // FULU's OrcaSlicer-bambulab fork keeps this Bambu-style path.
806
1141
  // For 3MF input: slice in place and export sliced 3MF
807
1142
  // For STL input: slice and export as 3MF
808
1143
  {
809
1144
  const is3mf = stlFilePath.toLowerCase().endsWith('.3mf');
810
1145
  const outputBase = path.basename(stlFilePath, is3mf ? '.3mf' : '.stl');
811
1146
  const bambuOutputPath = path.join(this.tempDir, outputBase + '_sliced.3mf');
1147
+ const outputDir = path.dirname(bambuOutputPath);
1148
+ const rawBundle = this.resolveBambuLikeSettingsBundle(outputBase, slicerType, slicerProfile, printerPreset, bambuOptions);
1149
+ // Opt-in flatten step: walks the BBL `inherits` chain so the
1150
+ // CLI accepts what we hand it. See maybeFlattenBundle docstring.
1151
+ const settingsBundle = slicerType === 'bambustudio'
1152
+ ? await this.maybeFlattenBundle(rawBundle, bambuOptions, slicerPath)
1153
+ : rawBundle;
812
1154
  args = [
813
1155
  '--slice', String(bambuOptions?.slicePlate ?? 0),
814
- '--export-3mf', bambuOutputPath,
1156
+ '--outputdir', outputDir,
1157
+ '--export-3mf', path.basename(bambuOutputPath),
815
1158
  ];
816
- if (slicerProfile) {
817
- args.push('--load-settings', slicerProfile);
1159
+ if (settingsBundle.settingsArg) {
1160
+ args.push('--load-settings', settingsBundle.settingsArg);
818
1161
  }
819
1162
  // Always allow newer-version 3MF files (the CLI rejects them by default)
820
1163
  args.push('--allow-newer-file');
821
- // 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.
824
- 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);
828
- }
829
1164
  // BambuSliceOptions flags
830
1165
  if (bambuOptions?.uptodate)
831
1166
  args.push('--uptodate');
@@ -857,8 +1192,10 @@ export class STLManipulator extends EventEmitter {
857
1192
  args.push('--clone-objects', bambuOptions.cloneObjects);
858
1193
  if (bambuOptions?.skipObjects)
859
1194
  args.push('--skip-objects', bambuOptions.skipObjects);
860
- if (bambuOptions?.loadFilaments)
861
- args.push('--load-filaments', bambuOptions.loadFilaments);
1195
+ if (settingsBundle.filamentPaths.length > 0) {
1196
+ args.push('--load-filaments', settingsBundle.filamentPaths.join(';'));
1197
+ args.push('--load-defaultfila');
1198
+ }
862
1199
  if (bambuOptions?.loadFilamentIds)
863
1200
  args.push('--load-filament-ids', bambuOptions.loadFilamentIds);
864
1201
  args.push(stlFilePath);
package/dist/types.d.ts CHANGED
@@ -55,3 +55,38 @@ export interface ThreeMFData {
55
55
  };
56
56
  slicerConfig?: Partial<BambuSlicerConfig>;
57
57
  }
58
+ export interface CollarCharmRole {
59
+ role: 'inner' | 'outer';
60
+ objectIndex: number;
61
+ name: string;
62
+ area: number;
63
+ filamentPosition: number;
64
+ }
65
+ export interface CollarCharmAnalysis {
66
+ plateIndex: number;
67
+ usedFilamentPositions: number[];
68
+ amsSlots: number[];
69
+ roles: CollarCharmRole[];
70
+ }
71
+ export interface ThreeMFFilamentRequirement {
72
+ filamentPosition: number;
73
+ filamentId: number;
74
+ tray_info_idx: string | null;
75
+ type: string | null;
76
+ color: string | null;
77
+ }
78
+ export interface ThreeMFAmsRequirements {
79
+ plateIndex: number;
80
+ usedFilamentPositions: number[];
81
+ filaments: ThreeMFFilamentRequirement[];
82
+ }
83
+ export interface ThreeMFPlateObject {
84
+ id: number;
85
+ name: string;
86
+ area: number | null;
87
+ bbox: unknown;
88
+ }
89
+ export interface ThreeMFPlateObjects {
90
+ plateIndex: number;
91
+ objects: ThreeMFPlateObject[];
92
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bambu-printer-mcp",
3
- "version": "1.0.5",
3
+ "version": "1.1.2",
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": {
@@ -13,7 +13,8 @@
13
13
  "start:stdio": "MCP_TRANSPORT=stdio node dist/index.js",
14
14
  "start:http": "MCP_TRANSPORT=streamable-http node dist/index.js",
15
15
  "build": "tsc",
16
- "test": "npm run build && node --test tests/**/*.test.mjs"
16
+ "test": "npm run build && node --test tests/*.test.mjs tests/**/*.test.mjs",
17
+ "postinstall": "patch-package"
17
18
  },
18
19
  "bin": {
19
20
  "bambu-printer-mcp": "dist/index.js"
@@ -21,6 +22,7 @@
21
22
  "files": [
22
23
  "dist",
23
24
  "src",
25
+ "patches",
24
26
  "README.md",
25
27
  "LICENSE"
26
28
  ],
@@ -42,12 +44,15 @@
42
44
  "basic-ftp": "^5.0.5",
43
45
  "dotenv": "^16.3.1",
44
46
  "jszip": "^3.10.1",
47
+ "mqtt": "^5.15.1",
48
+ "patch-package": "^8.0.1",
45
49
  "three": "^0.173.0",
46
50
  "xml2js": "^0.6.2"
47
51
  },
48
52
  "devDependencies": {
49
53
  "@types/node": "^18.0.0",
50
54
  "@types/three": "^0.173.0",
51
- "@types/xml2js": "^0.4.14"
55
+ "@types/xml2js": "^0.4.14",
56
+ "typescript": "^6.0.3"
52
57
  }
53
58
  }