bambu-printer-mcp 1.1.27 → 1.1.29
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 +7 -1
- package/README.md +29 -10
- package/dist/ams-mapping.d.ts +18 -2
- package/dist/ams-mapping.js +36 -5
- package/dist/bambu-native.d.ts +55 -0
- package/dist/bambu-native.js +212 -13
- package/dist/index.js +154 -71
- package/dist/printers/bambu.d.ts +29 -2
- package/dist/printers/bambu.js +59 -28
- package/dist/safety/hidden-hms.d.ts +32 -0
- package/dist/safety/hidden-hms.js +405 -0
- package/dist/safety/limits.d.ts +6 -0
- package/dist/safety/limits.js +9 -0
- package/dist/safety/print-file.d.ts +2 -0
- package/dist/safety/print-file.js +83 -7
- package/dist/safety/printer-state.d.ts +11 -0
- package/dist/safety/printer-state.js +115 -22
- package/dist/slicer/profile-flatten.js +25 -0
- package/dist/slicer/remote-slicer.d.ts +12 -0
- package/dist/slicer/remote-slicer.js +240 -0
- package/dist/stl/stl-manipulator.d.ts +2 -1
- package/dist/stl/stl-manipulator.js +71 -42
- package/native/bambu-native-print.cpp +100 -26
- package/package.json +7 -5
- package/scripts/build-bambu-native.sh +53 -0
- package/scripts/setup-bambu-native.mjs +372 -0
- package/src/ams-mapping.ts +38 -5
- package/src/bambu-native.ts +229 -14
- package/src/index.ts +156 -67
- package/src/printers/bambu.ts +65 -25
- package/src/safety/hidden-hms.ts +406 -0
- package/src/safety/limits.ts +9 -0
- package/src/safety/print-file.ts +71 -7
- package/src/safety/printer-state.ts +117 -22
- package/src/slicer/profile-flatten.ts +32 -0
- package/src/slicer/remote-slicer.ts +216 -0
- package/src/stl/stl-manipulator.ts +70 -43
- package/scripts/build-bambu-native.zsh +0 -33
package/src/index.ts
CHANGED
|
@@ -27,6 +27,9 @@ import {
|
|
|
27
27
|
import { BambuNetworkBridge, type BambuNetworkBridgeOptions } from "./bambu-network-bridge.js";
|
|
28
28
|
import {
|
|
29
29
|
assertBambuNativeAvailable,
|
|
30
|
+
bambuNativeControlsAvailable,
|
|
31
|
+
bambuNativeUnavailableReason,
|
|
32
|
+
interruptNativeOperations,
|
|
30
33
|
validateBambuNativeMetadata,
|
|
31
34
|
buildBambuNativeFanCommand,
|
|
32
35
|
buildBambuNativeTemperatureCommand,
|
|
@@ -37,12 +40,13 @@ import {
|
|
|
37
40
|
} from "./bambu-native.js";
|
|
38
41
|
import { importFileViaBambuConnect } from "./bambu-connect.js";
|
|
39
42
|
import { BlenderMcpBridge } from "./blender-mcp-bridge.js";
|
|
40
|
-
import { hasAmsMappingInput, normalizeAmsMappingObject, normalizeBridgeAmsTrayValue } from "./ams-mapping.js";
|
|
43
|
+
import { bridgeAmsMapping2Entry, bridgeAmsMappingValue, externalTrayForNozzle, hasAmsMappingInput, normalizeAmsMappingObject, normalizeBridgeAmsTrayValue } from "./ams-mapping.js";
|
|
41
44
|
import { analyze3MFAmsRequirements, analyze3MFPlateObjects, analyzeCollarCharm3MF, extractBambuTemplateSettings, getCollarCharmRolePolicy, parse3MF } from './3mf_parser.js';
|
|
42
45
|
import type { ThreeMFAmsRequirements } from "./types.js";
|
|
43
46
|
import { BambuImplementation, assertDirectPrintSupported } from "./printers/bambu.js";
|
|
44
47
|
import { inspectPrintFile } from "./safety/print-file.js";
|
|
45
48
|
import { validatePrinterState } from "./safety/printer-state.js";
|
|
49
|
+
import { isDualNozzleModel } from "./safety/limits.js";
|
|
46
50
|
import { withPrinterOperation, withPrintSnapshot, uniquePrintName } from "./safety/artifact.js";
|
|
47
51
|
import { detectProfilesRoot } from "./slicer/profile-flatten.js";
|
|
48
52
|
|
|
@@ -124,6 +128,21 @@ function resolveNozzleType(value: unknown): string | undefined {
|
|
|
124
128
|
}
|
|
125
129
|
return normalized;
|
|
126
130
|
}
|
|
131
|
+
|
|
132
|
+
/** BambuStudio's nozzle_volume_type value for each tool argument value. */
|
|
133
|
+
const NOZZLE_VOLUME_TYPES = { standard: "Standard", high_flow: "High Flow" } as const;
|
|
134
|
+
const VALID_NOZZLE_VOLUME_TYPES = Object.keys(NOZZLE_VOLUME_TYPES) as (keyof typeof NOZZLE_VOLUME_TYPES)[];
|
|
135
|
+
|
|
136
|
+
/** The installed nozzle's flow type. Bambu machine presets assume Standard; the print gate compares this with the printer. */
|
|
137
|
+
function resolveNozzleVolumeType(value: unknown): "Standard" | "High Flow" | undefined {
|
|
138
|
+
const raw = value === undefined || value === null || value === "" ? process.env.BAMBU_NOZZLE_VOLUME_TYPE?.trim() : value;
|
|
139
|
+
if (raw === undefined || raw === "") return undefined;
|
|
140
|
+
const normalized = String(raw).trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
141
|
+
if (!(VALID_NOZZLE_VOLUME_TYPES as readonly string[]).includes(normalized)) {
|
|
142
|
+
throw new Error(`Invalid nozzle volume type "${raw}". Valid types: ${VALID_NOZZLE_VOLUME_TYPES.join(", ")}`);
|
|
143
|
+
}
|
|
144
|
+
return NOZZLE_VOLUME_TYPES[normalized as keyof typeof NOZZLE_VOLUME_TYPES];
|
|
145
|
+
}
|
|
127
146
|
const VALID_BAMBUSTUDIO_CLI_BED_TYPES = ["textured_plate", "cool_plate", "engineering_plate", "hot_plate"] as const;
|
|
128
147
|
|
|
129
148
|
// Map model IDs to BambuStudio --load-machine preset names
|
|
@@ -685,7 +704,7 @@ function resolveSlicerConfig(args: Record<string, unknown> | undefined): {
|
|
|
685
704
|
slicerProfile: string;
|
|
686
705
|
} {
|
|
687
706
|
const slicerType = normalizeSlicerType(String(args?.slicer_type || DEFAULT_SLICER_TYPE));
|
|
688
|
-
const slicerPath = String(args?.slicer_path || process.env.SLICER_PATH || defaultSlicerPathFor(slicerType));
|
|
707
|
+
const slicerPath = slicerType === "orca-slicer-api" ? "" : String(args?.slicer_path || process.env.SLICER_PATH || defaultSlicerPathFor(slicerType));
|
|
689
708
|
const slicerProfile = String(args?.slicer_profile || DEFAULT_SLICER_PROFILE);
|
|
690
709
|
return { slicerType, slicerPath, slicerProfile };
|
|
691
710
|
}
|
|
@@ -976,14 +995,9 @@ function stringifyBridgeJson(value: unknown): string | undefined {
|
|
|
976
995
|
return JSON.stringify(value);
|
|
977
996
|
}
|
|
978
997
|
|
|
979
|
-
function nativeAmsMapping2(mapping: number[] | undefined): string | undefined {
|
|
998
|
+
function nativeAmsMapping2(mapping: number[] | undefined, dualNozzle = false): string | undefined {
|
|
980
999
|
if (!mapping || mapping.length === 0) return undefined;
|
|
981
|
-
return JSON.stringify(mapping.map((value) =>
|
|
982
|
-
if (value < 0 || value === 255) return { ams_id: 255, slot_id: 255 };
|
|
983
|
-
if (value === 254) return { ams_id: 254, slot_id: 254 };
|
|
984
|
-
if (value >= 128) return { ams_id: 128, slot_id: value - 128 };
|
|
985
|
-
return { ams_id: Math.floor(value / 4), slot_id: value % 4 };
|
|
986
|
-
}));
|
|
1000
|
+
return JSON.stringify(mapping.map((value) => bridgeAmsMapping2Entry(value, dualNozzle)));
|
|
987
1001
|
}
|
|
988
1002
|
|
|
989
1003
|
function redactPrintParams(params: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -1339,6 +1353,9 @@ class BambuPrinterMCPServer {
|
|
|
1339
1353
|
): ReturnType<typeof resolveSlicerConfig> {
|
|
1340
1354
|
const config = resolveSlicerConfig(args);
|
|
1341
1355
|
if (args?.slicer_path !== undefined) {
|
|
1356
|
+
if (config.slicerType === "orca-slicer-api") {
|
|
1357
|
+
throw new Error(`${toolName}: remote slicing uses trusted SLICER_API_URL and BAMBU_PROFILES_ROOT configuration; slicer_path is unsupported.`);
|
|
1358
|
+
}
|
|
1342
1359
|
const resolvedSlicerPath = this.resolveExecutableSelectorArg(
|
|
1343
1360
|
args.slicer_path,
|
|
1344
1361
|
toolName,
|
|
@@ -1379,12 +1396,13 @@ class BambuPrinterMCPServer {
|
|
|
1379
1396
|
|
|
1380
1397
|
console.error(`3MF has no gcode - auto-slicing with ${slicerType} for ${printModel}`);
|
|
1381
1398
|
const autoSliceOptions: BambuSliceOptions = {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1399
|
+
...(slicerType === "orca-slicer-api" ? {} : {
|
|
1400
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
1401
|
+
}),
|
|
1402
|
+
...(slicerType === "orca-slicer-api" ? { slicePlate: Number(args.plate_index ?? 0) + 1 } : {}),
|
|
1386
1403
|
bedType: bedType ? resolveBambuStudioCliBedType(bedType) : undefined,
|
|
1387
1404
|
nozzleType: resolveNozzleType(args?.nozzle_type),
|
|
1405
|
+
nozzleVolumeType: resolveNozzleVolumeType(args?.nozzle_volume_type),
|
|
1388
1406
|
};
|
|
1389
1407
|
threeMFPath = await this.stlManipulator.sliceSTL(
|
|
1390
1408
|
threeMFPath, slicerType, slicerPath, slicerProfile || undefined,
|
|
@@ -1407,9 +1425,10 @@ class BambuPrinterMCPServer {
|
|
|
1407
1425
|
): Promise<{ useAMS: boolean; finalAmsMapping?: number[]; finalAmsSlots?: number[] }> {
|
|
1408
1426
|
const parsed3MFData = await parse3MF(threeMFPath);
|
|
1409
1427
|
let parsedAmsMapping: number[] | undefined;
|
|
1428
|
+
const trayOptions = { dualNozzle: isDualNozzleModel(printModel) };
|
|
1410
1429
|
|
|
1411
1430
|
if (parsed3MFData.slicerConfig?.ams_mapping) {
|
|
1412
|
-
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping as Record<string, unknown
|
|
1431
|
+
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping as Record<string, unknown>, "ams_mapping", trayOptions);
|
|
1413
1432
|
if (slots.length > 0) {
|
|
1414
1433
|
parsedAmsMapping = slots;
|
|
1415
1434
|
}
|
|
@@ -1425,10 +1444,10 @@ class BambuPrinterMCPServer {
|
|
|
1425
1444
|
let userMappingOverride: number[] | undefined;
|
|
1426
1445
|
if (Array.isArray(args.ams_mapping)) {
|
|
1427
1446
|
userMappingOverride = args.ams_mapping.map((v: unknown, i: number) =>
|
|
1428
|
-
normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]
|
|
1447
|
+
normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]`, trayOptions)
|
|
1429
1448
|
);
|
|
1430
1449
|
} else if (args.ams_mapping && typeof args.ams_mapping === 'object') {
|
|
1431
|
-
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping as Record<string, unknown
|
|
1450
|
+
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping as Record<string, unknown>, "ams_mapping", trayOptions);
|
|
1432
1451
|
}
|
|
1433
1452
|
if (userMappingOverride && userMappingOverride.length > 0) {
|
|
1434
1453
|
finalAmsMapping = userMappingOverride;
|
|
@@ -1439,7 +1458,7 @@ class BambuPrinterMCPServer {
|
|
|
1439
1458
|
|
|
1440
1459
|
if (!hasUserAmsMapping && hasUserAmsSlots) {
|
|
1441
1460
|
const userSlots = args.ams_slots.map((slot: unknown, i: number) =>
|
|
1442
|
-
normalizeBridgeAmsTrayValue(slot, `ams_slots[${i}]
|
|
1461
|
+
normalizeBridgeAmsTrayValue(slot, `ams_slots[${i}]`, trayOptions)
|
|
1443
1462
|
);
|
|
1444
1463
|
finalAmsSlots = userSlots;
|
|
1445
1464
|
finalAmsMapping = undefined;
|
|
@@ -1468,8 +1487,8 @@ class BambuPrinterMCPServer {
|
|
|
1468
1487
|
}
|
|
1469
1488
|
|
|
1470
1489
|
if (args?.use_ams === false) {
|
|
1471
|
-
finalAmsMapping = undefined;
|
|
1472
|
-
finalAmsSlots = undefined;
|
|
1490
|
+
if (!hasUserAmsMapping) finalAmsMapping = undefined;
|
|
1491
|
+
if (!hasUserAmsSlots) finalAmsSlots = undefined;
|
|
1473
1492
|
useAMS = false;
|
|
1474
1493
|
}
|
|
1475
1494
|
if ((!finalAmsMapping || finalAmsMapping.length === 0) && (!finalAmsSlots || finalAmsSlots.length === 0)) {
|
|
@@ -1570,8 +1589,16 @@ class BambuPrinterMCPServer {
|
|
|
1570
1589
|
inspection.usedFilamentPositions.forEach((position, index) => { mapping![position] = finalAmsSlots[index]; });
|
|
1571
1590
|
}
|
|
1572
1591
|
if (!useAMS) {
|
|
1573
|
-
mapping
|
|
1574
|
-
|
|
1592
|
+
mapping ??= Array(inspection.materials.length).fill(-1);
|
|
1593
|
+
const dualNozzle = isDualNozzleModel(printModel);
|
|
1594
|
+
for (const position of inspection.usedFilamentPositions) {
|
|
1595
|
+
if (mapping[position] === undefined || mapping[position] < 0) {
|
|
1596
|
+
mapping[position] = externalTrayForNozzle(inspection.filamentNozzleIndices?.[position], dualNozzle);
|
|
1597
|
+
}
|
|
1598
|
+
if (mapping[position] !== 254 && !(dualNozzle && mapping[position] === 255)) {
|
|
1599
|
+
throw new Error("use_ams:false requires an external-spool mapping for every used filament.");
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1575
1602
|
}
|
|
1576
1603
|
const requirements = { ...inspection, amsMapping: mapping, useAMS };
|
|
1577
1604
|
const initialStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
@@ -1580,7 +1607,7 @@ class BambuPrinterMCPServer {
|
|
|
1580
1607
|
const confirmedStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1581
1608
|
validatePrinterState(confirmedStatus, requirements);
|
|
1582
1609
|
this.bambu.assertBedClearance(confirmedStatus, bedClearance);
|
|
1583
|
-
const amsMapping = stringifyBridgeJson(mapping);
|
|
1610
|
+
const amsMapping = stringifyBridgeJson(mapping?.map(value => bridgeAmsMappingValue(value, isDualNozzleModel(printModel))));
|
|
1584
1611
|
const remoteFileName = uniquePrintName(threeMFPath);
|
|
1585
1612
|
const params: Record<string, unknown> = {
|
|
1586
1613
|
dev_id: devId,
|
|
@@ -1597,7 +1624,7 @@ class BambuPrinterMCPServer {
|
|
|
1597
1624
|
ftp_file_md5: String(args?.ftp_file_md5 || ""),
|
|
1598
1625
|
nozzle_mapping: stringifyBridgeJson(args?.nozzle_mapping) || "",
|
|
1599
1626
|
ams_mapping: amsMapping || "",
|
|
1600
|
-
ams_mapping2:
|
|
1627
|
+
ams_mapping2: nativeAmsMapping2(mapping, isDualNozzleModel(printModel)) || "",
|
|
1601
1628
|
ams_mapping_info: stringifyBridgeJson(args?.ams_mapping_info) || "",
|
|
1602
1629
|
nozzles_info: stringifyBridgeJson(args?.nozzles_info) || "",
|
|
1603
1630
|
connection_type: connectionType,
|
|
@@ -1638,7 +1665,7 @@ class BambuPrinterMCPServer {
|
|
|
1638
1665
|
host: devIp, serial: devId, token: password || bambuToken, filePath: snapshot,
|
|
1639
1666
|
projectName: String(params.task_name), presetName, remoteName: remoteFileName,
|
|
1640
1667
|
plateIndex, bedType: printBedType, useAMS,
|
|
1641
|
-
amsMapping, amsMapping2: nativeAmsMapping2(mapping),
|
|
1668
|
+
amsMapping, amsMapping2: nativeAmsMapping2(mapping, isDualNozzleModel(printModel)),
|
|
1642
1669
|
bedLeveling: params.task_bed_leveling as boolean,
|
|
1643
1670
|
flowCalibration: params.task_flow_cali as boolean,
|
|
1644
1671
|
vibrationCalibration: params.task_vibration_cali as boolean,
|
|
@@ -1765,10 +1792,12 @@ class BambuPrinterMCPServer {
|
|
|
1765
1792
|
args: Record<string, any>, host: string, bambuSerial: string, bambuToken: string,
|
|
1766
1793
|
onUpdate?: BambuNativeUpdateCallback, signal?: AbortSignal
|
|
1767
1794
|
): Promise<Record<string, unknown>> {
|
|
1768
|
-
assertBambuNativeAvailable();
|
|
1769
1795
|
if (args.print) throw new Error("Native upload never starts printing; use print_3mf.");
|
|
1796
|
+
// Resolve and enforce the model first, as the print path does, so a
|
|
1797
|
+
// non-X2D request is never told to install the native helper.
|
|
1770
1798
|
const model = await this.resolveBambuModel(args.bambu_model);
|
|
1771
1799
|
if (model !== "x2d") throw new Error("connection_mode=bambu_native is restricted to X2D.");
|
|
1800
|
+
assertBambuNativeAvailable();
|
|
1772
1801
|
const unsupported = ["use_ams", "ams_mapping", "ams_mapping2", "ams_mapping_info", "nozzle_mapping", "nozzles_info",
|
|
1773
1802
|
"bed_leveling", "flow_calibration", "vibration_calibration", "layer_inspect", "timelapse"];
|
|
1774
1803
|
for (const key of unsupported) {
|
|
@@ -1895,10 +1924,9 @@ class BambuPrinterMCPServer {
|
|
|
1895
1924
|
);
|
|
1896
1925
|
}
|
|
1897
1926
|
const autoSliceOptions: BambuSliceOptions = {
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
skipModifiedGcodes: true,
|
|
1927
|
+
...(slicerType === "orca-slicer-api" ? {} : {
|
|
1928
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
1929
|
+
}),
|
|
1902
1930
|
bedType,
|
|
1903
1931
|
};
|
|
1904
1932
|
|
|
@@ -2364,6 +2392,11 @@ class BambuPrinterMCPServer {
|
|
|
2364
2392
|
enum: [...VALID_NOZZLE_TYPES],
|
|
2365
2393
|
description: "Bambu-compatible slicing: the hotend nozzle material installed on the printer (default: BAMBU_NOZZLE_TYPE, else the model preset's stock nozzle, usually stainless_steel). Printing compares it with the printer's reported nozzle."
|
|
2366
2394
|
},
|
|
2395
|
+
nozzle_volume_type: {
|
|
2396
|
+
type: "string",
|
|
2397
|
+
enum: [...VALID_NOZZLE_VOLUME_TYPES],
|
|
2398
|
+
description: "Bambu-compatible slicing: the flow type of the nozzle installed on the printer (default: BAMBU_NOZZLE_VOLUME_TYPE, else standard). high_flow uses the presets' High Flow values, such as the filament's High Flow max volumetric speed, and is refused if the machine preset has no High Flow variant. Printing compares it with the printer's reported nozzle flow."
|
|
2399
|
+
},
|
|
2367
2400
|
use_printer_filaments: { type: "boolean", description: "When true, and no explicit slicer profile or load_filaments override is provided, use the printer's current or first loaded AMS filament as the slicer filament profile." },
|
|
2368
2401
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
2369
2402
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
@@ -2419,6 +2452,11 @@ class BambuPrinterMCPServer {
|
|
|
2419
2452
|
enum: [...VALID_NOZZLE_TYPES],
|
|
2420
2453
|
description: "Bambu-compatible slicing: the hotend nozzle material installed on the printer (default: BAMBU_NOZZLE_TYPE, else the model preset's stock nozzle, usually stainless_steel). Printing compares it with the printer's reported nozzle."
|
|
2421
2454
|
},
|
|
2455
|
+
nozzle_volume_type: {
|
|
2456
|
+
type: "string",
|
|
2457
|
+
enum: [...VALID_NOZZLE_VOLUME_TYPES],
|
|
2458
|
+
description: "Bambu-compatible slicing: the flow type of the nozzle installed on the printer (default: BAMBU_NOZZLE_VOLUME_TYPE, else standard). high_flow uses the presets' High Flow values, such as the filament's High Flow max volumetric speed, and is refused if the machine preset has no High Flow variant. Printing compares it with the printer's reported nozzle flow."
|
|
2459
|
+
},
|
|
2422
2460
|
use_printer_filaments: { type: "boolean", description: "When true, and no explicit slicer profile or load_filaments override is provided, use the printer's current or first loaded AMS filament as the slicer filament profile. Template 3MF process settings can still be used at the same time." },
|
|
2423
2461
|
uptodate: { type: "boolean", description: "Refresh 3MF preset configs to match the latest BambuStudio version. Use when slicing downloaded or older 3MF files to prevent stale-config failures." },
|
|
2424
2462
|
repetitions: { type: "number", description: "Print N identical copies of the model. Each copy gets its own plate placement. Example: 3 prints three copies." },
|
|
@@ -2439,7 +2477,7 @@ class BambuPrinterMCPServer {
|
|
|
2439
2477
|
rotate_y: { type: "number", description: "Rotate the model around the Y-axis by this many degrees before slicing. Useful for reorienting prints for better layer adhesion." },
|
|
2440
2478
|
min_save: { type: "boolean", description: "Write a smaller output 3MF by omitting non-essential metadata. Reduces file size for faster FTP upload to the printer." },
|
|
2441
2479
|
skip_modified_gcodes: { type: "boolean", description: "Strip custom start/end gcodes embedded in the 3MF. Recommended for downloaded 3MFs since custom gcodes from other users' profiles may be unsafe for your printer." },
|
|
2442
|
-
slice_plate: { type: "number", description: "Which plate index to slice. 0 = all plates
|
|
2480
|
+
slice_plate: { type: "number", description: "Which plate index to slice. 0 = all plates for local slicers; remote slicing defaults to plate 1 and rejects 0. Use 1, 2, etc. to slice only a specific plate in multi-plate 3MF projects." }
|
|
2443
2481
|
},
|
|
2444
2482
|
required: ["stl_path", "bambu_model"]
|
|
2445
2483
|
}
|
|
@@ -2525,6 +2563,7 @@ class BambuPrinterMCPServer {
|
|
|
2525
2563
|
password: { type: "string", description: "Printer password/access code override for LAN/local bridge methods." },
|
|
2526
2564
|
bed_type: { type: "string", enum: ["textured_plate", "cool_plate", "engineering_plate", "hot_plate", "supertack_plate"], description: "Bed plate type currently installed (default: textured_plate)." },
|
|
2527
2565
|
nozzle_type: { type: "string", enum: [...VALID_NOZZLE_TYPES], description: "Installed nozzle material, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_TYPE, else the preset's stock nozzle)." },
|
|
2566
|
+
nozzle_volume_type: { type: "string", enum: [...VALID_NOZZLE_VOLUME_TYPES], description: "Installed nozzle flow type, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_VOLUME_TYPE, else standard)." },
|
|
2528
2567
|
plate_index: { type: "number", description: "Zero-based plate index to print from the sliced 3MF; converted to FULU's one-based PrintParams plate_index." },
|
|
2529
2568
|
project_name: { type: "string", description: "Optional project name sent in FULU PrintParams; defaults to the 3MF filename without extension." },
|
|
2530
2569
|
preset_name: { type: "string", description: "Optional preset name sent in FULU PrintParams; defaults to project plus one-based plate index." },
|
|
@@ -2541,8 +2580,8 @@ class BambuPrinterMCPServer {
|
|
|
2541
2580
|
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)." },
|
|
2542
2581
|
nozzle_diameters: { type: "array", minItems: 1, maxItems: 2, items: { type: "number", enum: [0.2, 0.4, 0.6, 0.8] }, description: "Complete per-nozzle diameters for pre-sliced jobs, e.g. [0.4, 0.6]. Omit to verify file metadata against every reported nozzle; cannot combine with nozzle_diameter." },
|
|
2543
2582
|
use_ams: { type: "boolean", description: "Whether to use the AMS; defaults to auto-detect from the 3MF mapping." },
|
|
2544
|
-
ams_mapping: { type: "array", description: "AMS slot mapping array used by both local MCP printing and FULU PrintParams.", items: { type: "number" } },
|
|
2545
|
-
ams_slots: { type: "array", description: "Per-used-filament AMS slot list, matching the local LAN print path.", items: { type: "number" } },
|
|
2583
|
+
ams_mapping: { type: "array", description: "AMS slot mapping array used by both local MCP printing and FULU PrintParams. Same tray values as print_3mf: 254 = external spool; on dual-nozzle printers 254 = left/deputy nozzle external and 255 = right/main nozzle external.", items: { type: "number" } },
|
|
2584
|
+
ams_slots: { type: "array", description: "Per-used-filament AMS slot list, matching the local LAN print path (254/255 external spool rules as in print_3mf).", items: { type: "number" } },
|
|
2546
2585
|
ams_mapping_bridge: { type: "string", description: "Raw JSON string override for FULU PrintParams ams_mapping when the automatic array is not enough." },
|
|
2547
2586
|
ams_mapping2: { type: "string", description: "Raw JSON string for FULU PrintParams ams_mapping2, matching OrcaSlicer-bambulab's v1 AMS mapping field." },
|
|
2548
2587
|
ams_mapping_info: { type: "string", description: "Raw JSON string for FULU PrintParams ams_mapping_info, matching OrcaSlicer-bambulab's detailed AMS mapping field." },
|
|
@@ -2788,7 +2827,7 @@ class BambuPrinterMCPServer {
|
|
|
2788
2827
|
},
|
|
2789
2828
|
{
|
|
2790
2829
|
name: "reread_ams_rfid",
|
|
2791
|
-
description: "Trigger a Bambu AMS RFID re-read for one AMS slot. X2D on macOS uses the native official plug-in route. This can move AMS filament; use only when the printer is idle and unloaded.",
|
|
2830
|
+
description: "Trigger a Bambu AMS RFID re-read for one AMS slot. X2D on macOS, or Linux with the native helper installed, uses the native official plug-in route. This can move AMS filament; use only when the printer is idle and unloaded.",
|
|
2792
2831
|
inputSchema: {
|
|
2793
2832
|
type: "object",
|
|
2794
2833
|
properties: {
|
|
@@ -2821,7 +2860,7 @@ class BambuPrinterMCPServer {
|
|
|
2821
2860
|
},
|
|
2822
2861
|
{
|
|
2823
2862
|
name: "set_fan_speed",
|
|
2824
|
-
description: "Set a Bambu printer fan speed percentage. X2D on macOS
|
|
2863
|
+
description: "Set a Bambu printer fan speed percentage. X2D on macOS, or Linux with the native helper installed, uses Bambu's networking plug-in without UI automation; other models use MQTT.",
|
|
2825
2864
|
inputSchema: {
|
|
2826
2865
|
type: "object",
|
|
2827
2866
|
properties: {
|
|
@@ -2880,7 +2919,7 @@ class BambuPrinterMCPServer {
|
|
|
2880
2919
|
},
|
|
2881
2920
|
{
|
|
2882
2921
|
name: "set_ams_drying",
|
|
2883
|
-
description: "Start or stop the AMS filament drying cycle. X2D on macOS uses the native official plug-in route and supports AMS-HT id 128.",
|
|
2922
|
+
description: "Start or stop the AMS filament drying cycle. X2D on macOS, or Linux with the native helper installed, uses the native official plug-in route and supports AMS-HT id 128.",
|
|
2884
2923
|
inputSchema: {
|
|
2885
2924
|
type: "object",
|
|
2886
2925
|
properties: {
|
|
@@ -2952,6 +2991,11 @@ class BambuPrinterMCPServer {
|
|
|
2952
2991
|
enum: [...VALID_NOZZLE_TYPES],
|
|
2953
2992
|
description: "Installed nozzle material, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_TYPE, else the preset's stock nozzle)."
|
|
2954
2993
|
},
|
|
2994
|
+
nozzle_volume_type: {
|
|
2995
|
+
type: "string",
|
|
2996
|
+
enum: [...VALID_NOZZLE_VOLUME_TYPES],
|
|
2997
|
+
description: "Installed nozzle flow type, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_VOLUME_TYPE, else standard)."
|
|
2998
|
+
},
|
|
2955
2999
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
2956
3000
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
2957
3001
|
bambu_token: { type: "string", description: "Access token (default: value from env)" },
|
|
@@ -2965,12 +3009,12 @@ class BambuPrinterMCPServer {
|
|
|
2965
3009
|
use_ams: { type: "boolean", description: "Whether to use the AMS (default: auto-detect from 3MF)" },
|
|
2966
3010
|
ams_mapping: {
|
|
2967
3011
|
type: "array",
|
|
2968
|
-
description: "Project-level AMS mapping array. Position = project filament index, value = absolute AMS tray (0-3=AMS 0, 4-7=AMS 1, 8-11=AMS 2, 128+=AMS-HT, 254=external, -1=unused). Prefer ams_slots unless you know the project-level layout.",
|
|
3012
|
+
description: "Project-level AMS mapping array. Position = project filament index, value = absolute AMS tray (0-3=AMS 0, 4-7=AMS 1, 8-11=AMS 2, 128+=AMS-HT, 254=external, -1=unused). On dual-nozzle printers (H2D, H2D Pro, H2C, X2D) 254 is the LEFT/deputy nozzle external spool and 255 the RIGHT/main nozzle external spool, matching Bambu Studio's virtual tray ids; the gcode header's filament_map (1=left, 2=right) says which nozzle each filament was sliced for. Prefer ams_slots unless you know the project-level layout.",
|
|
2969
3013
|
items: { type: "number" }
|
|
2970
3014
|
},
|
|
2971
3015
|
ams_slots: {
|
|
2972
3016
|
type: "array",
|
|
2973
|
-
description: "Preferred AMS input: one absolute tray index per USED filament in plate order, e.g. [1] for a single-filament print pulling from AMS 0 slot 1. Expanded to project-level ams_mapping automatically from the 3MF's plate_N.json and gcode header.",
|
|
3017
|
+
description: "Preferred AMS input: one absolute tray index per USED filament in plate order, e.g. [1] for a single-filament print pulling from AMS 0 slot 1. Use 254 for the external spool (on dual-nozzle printers: 254 = left/deputy nozzle external, 255 = right/main nozzle external). Expanded to project-level ams_mapping automatically from the 3MF's plate_N.json and gcode header.",
|
|
2974
3018
|
items: { type: "number" }
|
|
2975
3019
|
},
|
|
2976
3020
|
auto_match_ams: {
|
|
@@ -3147,9 +3191,43 @@ class BambuPrinterMCPServer {
|
|
|
3147
3191
|
const host = String(args?.host || DEFAULT_HOST);
|
|
3148
3192
|
const bambuSerial = String(args?.bambu_serial || DEFAULT_BAMBU_SERIAL);
|
|
3149
3193
|
const bambuToken = String(args?.bambu_token || DEFAULT_BAMBU_TOKEN);
|
|
3150
|
-
const
|
|
3151
|
-
(
|
|
3152
|
-
|
|
3194
|
+
const x2dTarget = bambuSerial.trim().toUpperCase().startsWith("20P") ||
|
|
3195
|
+
(DEFAULT_BAMBU_MODEL === "x2d" && bambuSerial === DEFAULT_BAMBU_SERIAL);
|
|
3196
|
+
// Resolved lazily: the Linux availability check reads files, and most
|
|
3197
|
+
// tools (status, slicing, STL) never need it.
|
|
3198
|
+
// One resolution per request; null means native X2D controls are usable.
|
|
3199
|
+
let nativeUnavailable: string | null | undefined;
|
|
3200
|
+
const nativeUnavailableReason = () =>
|
|
3201
|
+
(nativeUnavailable ??= x2dTarget ? bambuNativeUnavailableReason() ?? null : "The selected printer is not an X2D.");
|
|
3202
|
+
const nativeControls = () => nativeUnavailableReason() === null;
|
|
3203
|
+
// Linux sent X2D stop and heater-off over MQTT before the native route
|
|
3204
|
+
// existed. Send them over both routes there: a stuck helper must not
|
|
3205
|
+
// delay an emergency control, and a repeated stop or heater-off is
|
|
3206
|
+
// harmless. The immediate MQTT send can precede the exit of an older
|
|
3207
|
+
// print or heating helper that then still dispatches, so MQTT is sent
|
|
3208
|
+
// again once every older helper for this printer has exited, and only
|
|
3209
|
+
// that send counts. Either counted route succeeding is success. macOS
|
|
3210
|
+
// stays native-only.
|
|
3211
|
+
const linuxEmergency = (native: () => Promise<any>, mqtt: () => Promise<any>) => async () => {
|
|
3212
|
+
if (process.platform !== "linux") return native();
|
|
3213
|
+
const [nativeResult, immediateResult, mqttResult] = await Promise.allSettled([
|
|
3214
|
+
native(),
|
|
3215
|
+
mqtt(),
|
|
3216
|
+
interruptNativeOperations(bambuSerial).then(() => mqtt()),
|
|
3217
|
+
]);
|
|
3218
|
+
const reason = (result: PromiseSettledResult<any>) =>
|
|
3219
|
+
result.status === "rejected" ? (result.reason instanceof Error ? result.reason.message : String(result.reason)) : undefined;
|
|
3220
|
+
if (nativeResult.status === "rejected" && mqttResult.status === "rejected") {
|
|
3221
|
+
throw new Error(`native route: ${reason(nativeResult)}; MQTT route: ${reason(mqttResult)}`);
|
|
3222
|
+
}
|
|
3223
|
+
const state = (result: PromiseSettledResult<any>) => result.status === "fulfilled" ? "sent" : "failed";
|
|
3224
|
+
return {
|
|
3225
|
+
status: "success",
|
|
3226
|
+
routes: { native: state(nativeResult), mqtt: state(mqttResult), mqtt_immediate: state(immediateResult) },
|
|
3227
|
+
...(reason(nativeResult) ? { native_error: reason(nativeResult) } : {}),
|
|
3228
|
+
...(reason(mqttResult) ? { mqtt_error: reason(mqttResult) } : {}),
|
|
3229
|
+
};
|
|
3230
|
+
};
|
|
3153
3231
|
const requestedTemplateDir =
|
|
3154
3232
|
typeof args?.template_dir === "string" && args.template_dir.trim().length > 0
|
|
3155
3233
|
? String(args.template_dir)
|
|
@@ -3405,15 +3483,17 @@ class BambuPrinterMCPServer {
|
|
|
3405
3483
|
|
|
3406
3484
|
case "cancel_print":
|
|
3407
3485
|
result = await this.bambu.cancelJob(host, bambuSerial, bambuToken,
|
|
3408
|
-
nativeControls
|
|
3409
|
-
? (
|
|
3410
|
-
|
|
3486
|
+
nativeControls()
|
|
3487
|
+
? linuxEmergency(
|
|
3488
|
+
() => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
3489
|
+
messageJson: JSON.stringify({ print: { command: "stop", param: "", sequence_id: String(Date.now()) } }), qos: 1 }),
|
|
3490
|
+
() => this.bambu.sendStopOverMqtt(host, bambuSerial, bambuToken))
|
|
3411
3491
|
: undefined);
|
|
3412
3492
|
break;
|
|
3413
3493
|
|
|
3414
3494
|
case "pause_print":
|
|
3415
3495
|
result = await this.bambu.pauseJob(host, bambuSerial, bambuToken,
|
|
3416
|
-
nativeControls
|
|
3496
|
+
nativeControls()
|
|
3417
3497
|
? () => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
3418
3498
|
messageJson: JSON.stringify({ print: { command: "pause", param: "", sequence_id: String(Date.now()) } }), qos: 1 })
|
|
3419
3499
|
: undefined);
|
|
@@ -3421,7 +3501,7 @@ class BambuPrinterMCPServer {
|
|
|
3421
3501
|
|
|
3422
3502
|
case "resume_print":
|
|
3423
3503
|
result = await this.bambu.resumeJob(host, bambuSerial, bambuToken,
|
|
3424
|
-
nativeControls
|
|
3504
|
+
nativeControls()
|
|
3425
3505
|
? (assertActive, beforeDispatch) => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
3426
3506
|
messageJson: JSON.stringify({ print: { command: "resume", param: "", sequence_id: String(Date.now()) } }), qos: 1 }, { signal: extra.signal, assertActive, beforeDispatch })
|
|
3427
3507
|
: undefined);
|
|
@@ -3431,8 +3511,9 @@ class BambuPrinterMCPServer {
|
|
|
3431
3511
|
if (!args?.message_json) {
|
|
3432
3512
|
throw new Error("Missing required parameter: message_json");
|
|
3433
3513
|
}
|
|
3434
|
-
if (!nativeControls) {
|
|
3435
|
-
throw new Error("x2d_native_control is restricted to a selected X2D printer on macOS."
|
|
3514
|
+
if (!nativeControls()) {
|
|
3515
|
+
throw new Error("x2d_native_control is restricted to a selected X2D printer on macOS, or on Linux with the native helper installed. " +
|
|
3516
|
+
nativeUnavailableReason());
|
|
3436
3517
|
}
|
|
3437
3518
|
const messageJson = validateBambuNativeMetadata(String(args.message_json));
|
|
3438
3519
|
result = await sendNativeCommand({
|
|
@@ -3448,7 +3529,7 @@ class BambuPrinterMCPServer {
|
|
|
3448
3529
|
|
|
3449
3530
|
case "clear_hms_errors":
|
|
3450
3531
|
result = await this.bambu.clearHmsErrors(host, bambuSerial, bambuToken,
|
|
3451
|
-
nativeControls
|
|
3532
|
+
nativeControls()
|
|
3452
3533
|
? (assertActive, beforeDispatch) => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
3453
3534
|
messageJson: JSON.stringify({ print: { command: "clean_print_error", param: "", sequence_id: String(Date.now()) } }), qos: 1 }, { signal: extra.signal, assertActive, beforeDispatch })
|
|
3454
3535
|
: undefined);
|
|
@@ -3458,7 +3539,7 @@ class BambuPrinterMCPServer {
|
|
|
3458
3539
|
if (!args?.mode) {
|
|
3459
3540
|
throw new Error("Missing required parameter: mode");
|
|
3460
3541
|
}
|
|
3461
|
-
if (nativeControls) {
|
|
3542
|
+
if (nativeControls()) {
|
|
3462
3543
|
const normalized = String(args.mode).trim().toLowerCase();
|
|
3463
3544
|
const mode = normalized === "silent" ? 1
|
|
3464
3545
|
: normalized === "standard" ? 2
|
|
@@ -3485,7 +3566,7 @@ class BambuPrinterMCPServer {
|
|
|
3485
3566
|
if (!args?.mode) {
|
|
3486
3567
|
throw new Error("Missing required parameter: mode");
|
|
3487
3568
|
}
|
|
3488
|
-
if (nativeControls) {
|
|
3569
|
+
if (nativeControls()) {
|
|
3489
3570
|
const mode = String(args.mode).trim().toLowerCase();
|
|
3490
3571
|
if (mode !== "cooling" && mode !== "heating") {
|
|
3491
3572
|
throw new Error("Airduct mode must be one of: cooling, heating.");
|
|
@@ -3512,7 +3593,7 @@ class BambuPrinterMCPServer {
|
|
|
3512
3593
|
if (args?.ams_id === undefined || args?.slot_id === undefined) {
|
|
3513
3594
|
throw new Error("Missing required parameters: ams_id and slot_id");
|
|
3514
3595
|
}
|
|
3515
|
-
if (nativeControls) {
|
|
3596
|
+
if (nativeControls()) {
|
|
3516
3597
|
const amsId = Number(args.ams_id);
|
|
3517
3598
|
const slotId = Number(args.slot_id);
|
|
3518
3599
|
if ((!Number.isInteger(amsId) || (amsId < 0 || amsId > 3)) && amsId !== 128) {
|
|
@@ -3561,9 +3642,14 @@ class BambuPrinterMCPServer {
|
|
|
3561
3642
|
heaterModel || undefined,
|
|
3562
3643
|
args?.material !== undefined ? String(args.material) : undefined,
|
|
3563
3644
|
Number(args?.nozzle_diameter ?? DEFAULT_NOZZLE_DIAMETER),
|
|
3564
|
-
(args.temperature === 0 ? nativeControls : heaterModel === "x2d" &&
|
|
3565
|
-
? (heater, target, assertActive, beforeDispatch) =>
|
|
3566
|
-
|
|
3645
|
+
(args.temperature === 0 ? nativeControls() : heaterModel === "x2d" && bambuNativeControlsAvailable())
|
|
3646
|
+
? (heater, target, assertActive, beforeDispatch) => {
|
|
3647
|
+
const native = () => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
3648
|
+
messageJson: buildBambuNativeTemperatureCommand(heater, target).messageJson }, { signal: extra.signal, assertActive, beforeDispatch });
|
|
3649
|
+
return target === 0
|
|
3650
|
+
? linuxEmergency(native, () => this.bambu.sendHeaterOffOverMqtt(host, bambuSerial, bambuToken, heater))()
|
|
3651
|
+
: native();
|
|
3652
|
+
}
|
|
3567
3653
|
: undefined
|
|
3568
3654
|
);
|
|
3569
3655
|
break;
|
|
@@ -3573,7 +3659,7 @@ class BambuPrinterMCPServer {
|
|
|
3573
3659
|
if (!args?.fan || args?.speed === undefined) {
|
|
3574
3660
|
throw new Error("Missing required parameters: fan and speed");
|
|
3575
3661
|
}
|
|
3576
|
-
if (nativeControls) {
|
|
3662
|
+
if (nativeControls()) {
|
|
3577
3663
|
const status = await this.bambu.getStatus(host, bambuSerial, bambuToken);
|
|
3578
3664
|
const printerState = String(status?.raw?.gcode_state ?? status?.raw?.print?.gcode_state ?? status?.gcode_state ?? status?.status ?? "").toUpperCase();
|
|
3579
3665
|
if (printerState === "RUNNING" && args?.confirm_during_print !== true) {
|
|
@@ -3607,7 +3693,7 @@ class BambuPrinterMCPServer {
|
|
|
3607
3693
|
if (!args?.light || !args?.mode) {
|
|
3608
3694
|
throw new Error("Missing required parameters: light and mode");
|
|
3609
3695
|
}
|
|
3610
|
-
if (nativeControls) {
|
|
3696
|
+
if (nativeControls()) {
|
|
3611
3697
|
const light = String(args.light).trim();
|
|
3612
3698
|
const mode = String(args.mode).trim().toLowerCase();
|
|
3613
3699
|
if (light !== "chamber_light" && light !== "chamber_light2") {
|
|
@@ -3648,7 +3734,7 @@ class BambuPrinterMCPServer {
|
|
|
3648
3734
|
if (!Array.isArray(args?.object_ids)) {
|
|
3649
3735
|
throw new Error("Missing required parameter: object_ids");
|
|
3650
3736
|
}
|
|
3651
|
-
if (nativeControls) {
|
|
3737
|
+
if (nativeControls()) {
|
|
3652
3738
|
const objectIds = args.object_ids.map((id: unknown) => Number(id));
|
|
3653
3739
|
if (objectIds.length === 0 || objectIds.some((id: number) => !Number.isInteger(id) || id < 0)) {
|
|
3654
3740
|
throw new Error("object_ids must contain one or more non-negative integers.");
|
|
@@ -3676,7 +3762,7 @@ class BambuPrinterMCPServer {
|
|
|
3676
3762
|
if (!args?.action || args?.ams_id === undefined) {
|
|
3677
3763
|
throw new Error("Missing required parameters: action and ams_id");
|
|
3678
3764
|
}
|
|
3679
|
-
if (nativeControls) {
|
|
3765
|
+
if (nativeControls()) {
|
|
3680
3766
|
const action = String(args.action).trim().toLowerCase();
|
|
3681
3767
|
const amsId = Number(args.ams_id);
|
|
3682
3768
|
if (action !== "start" && action !== "stop") {
|
|
@@ -3808,6 +3894,7 @@ class BambuPrinterMCPServer {
|
|
|
3808
3894
|
if (args?.filament_colours !== undefined) sliceBambuOptions.filamentColours = parseFilamentColours(String(args.filament_colours));
|
|
3809
3895
|
sliceBambuOptions.bedType = resolveBambuStudioCliBedType(args?.bed_type as string | undefined);
|
|
3810
3896
|
sliceBambuOptions.nozzleType = resolveNozzleType(args?.nozzle_type);
|
|
3897
|
+
sliceBambuOptions.nozzleVolumeType = resolveNozzleVolumeType(args?.nozzle_volume_type);
|
|
3811
3898
|
if (args?.enable_timelapse !== undefined) sliceBambuOptions.enableTimelapse = Boolean(args.enable_timelapse);
|
|
3812
3899
|
if (args?.allow_mix_temp !== undefined) sliceBambuOptions.allowMixTemp = Boolean(args.allow_mix_temp);
|
|
3813
3900
|
if (args?.scale !== undefined) sliceBambuOptions.scale = Number(args.scale);
|
|
@@ -3895,6 +3982,7 @@ class BambuPrinterMCPServer {
|
|
|
3895
3982
|
if (args?.filament_colours !== undefined) sliceBambuOptions.filamentColours = parseFilamentColours(String(args.filament_colours));
|
|
3896
3983
|
sliceBambuOptions.bedType = resolveBambuStudioCliBedType(args?.bed_type as string | undefined);
|
|
3897
3984
|
sliceBambuOptions.nozzleType = resolveNozzleType(args?.nozzle_type);
|
|
3985
|
+
sliceBambuOptions.nozzleVolumeType = resolveNozzleVolumeType(args?.nozzle_volume_type);
|
|
3898
3986
|
if (args?.enable_timelapse !== undefined) sliceBambuOptions.enableTimelapse = Boolean(args.enable_timelapse);
|
|
3899
3987
|
if (args?.allow_mix_temp !== undefined) sliceBambuOptions.allowMixTemp = Boolean(args.allow_mix_temp);
|
|
3900
3988
|
if (args?.scale !== undefined) sliceBambuOptions.scale = Number(args.scale);
|
|
@@ -4017,12 +4105,12 @@ class BambuPrinterMCPServer {
|
|
|
4017
4105
|
}
|
|
4018
4106
|
console.error(`3MF has no gcode — auto-slicing with ${slicerType} for ${printModel}`);
|
|
4019
4107
|
const autoSliceOptions: BambuSliceOptions = {
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
skipModifiedGcodes: true,
|
|
4108
|
+
...(slicerType === "orca-slicer-api" ? { slicePlate: plateIndex + 1 } : {
|
|
4109
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
4110
|
+
}),
|
|
4024
4111
|
bedType: printBedType,
|
|
4025
4112
|
nozzleType: resolveNozzleType(args?.nozzle_type),
|
|
4113
|
+
nozzleVolumeType: resolveNozzleVolumeType(args?.nozzle_volume_type),
|
|
4026
4114
|
};
|
|
4027
4115
|
if (!explicitSlicerProfile) {
|
|
4028
4116
|
try {
|
|
@@ -4051,9 +4139,10 @@ class BambuPrinterMCPServer {
|
|
|
4051
4139
|
|
|
4052
4140
|
const parsed3MFData = await parse3MF(threeMFPath);
|
|
4053
4141
|
const isH2Print = H2_BAMBU_MODELS.has(printModel);
|
|
4142
|
+
const trayOptions = { dualNozzle: isDualNozzleModel(printModel) };
|
|
4054
4143
|
let parsedAmsMapping: number[] | undefined;
|
|
4055
4144
|
if (!isH2Print && parsed3MFData.slicerConfig?.ams_mapping) {
|
|
4056
|
-
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping as Record<string, unknown
|
|
4145
|
+
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping as Record<string, unknown>, "ams_mapping", trayOptions);
|
|
4057
4146
|
if (slots.length > 0) {
|
|
4058
4147
|
parsedAmsMapping = slots;
|
|
4059
4148
|
}
|
|
@@ -4069,10 +4158,10 @@ class BambuPrinterMCPServer {
|
|
|
4069
4158
|
let userMappingOverride: number[] | undefined;
|
|
4070
4159
|
if (Array.isArray(args.ams_mapping)) {
|
|
4071
4160
|
userMappingOverride = args.ams_mapping.map((v: unknown, i: number) =>
|
|
4072
|
-
normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]
|
|
4161
|
+
normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]`, trayOptions)
|
|
4073
4162
|
);
|
|
4074
4163
|
} else if (args.ams_mapping && typeof args.ams_mapping === 'object') {
|
|
4075
|
-
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping as Record<string, unknown
|
|
4164
|
+
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping as Record<string, unknown>, "ams_mapping", trayOptions);
|
|
4076
4165
|
}
|
|
4077
4166
|
if (userMappingOverride && userMappingOverride.length > 0) {
|
|
4078
4167
|
finalAmsMapping = userMappingOverride;
|
|
@@ -4126,8 +4215,8 @@ class BambuPrinterMCPServer {
|
|
|
4126
4215
|
}
|
|
4127
4216
|
|
|
4128
4217
|
if (args?.use_ams === false) {
|
|
4129
|
-
finalAmsMapping = undefined;
|
|
4130
|
-
finalAmsSlots = undefined;
|
|
4218
|
+
if (!hasUserAmsMapping) finalAmsMapping = undefined;
|
|
4219
|
+
if (!hasUserAmsSlots) finalAmsSlots = undefined;
|
|
4131
4220
|
useAMS = false;
|
|
4132
4221
|
}
|
|
4133
4222
|
if ((!finalAmsMapping || finalAmsMapping.length === 0) && (!finalAmsSlots || finalAmsSlots.length === 0)) {
|