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/dist/index.js
CHANGED
|
@@ -11,14 +11,15 @@ import { createServer as createHttpServer } from "node:http";
|
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
12
|
import { STLManipulator, SLICER_TYPES, normalizeSlicerType, } from "./stl/stl-manipulator.js";
|
|
13
13
|
import { BambuNetworkBridge } from "./bambu-network-bridge.js";
|
|
14
|
-
import { assertBambuNativeAvailable, validateBambuNativeMetadata, buildBambuNativeFanCommand, buildBambuNativeTemperatureCommand, printWithBambuNative, sendCommandWithBambuNative, uploadWithBambuNative, } from "./bambu-native.js";
|
|
14
|
+
import { assertBambuNativeAvailable, bambuNativeControlsAvailable, bambuNativeUnavailableReason, interruptNativeOperations, validateBambuNativeMetadata, buildBambuNativeFanCommand, buildBambuNativeTemperatureCommand, printWithBambuNative, sendCommandWithBambuNative, uploadWithBambuNative, } from "./bambu-native.js";
|
|
15
15
|
import { importFileViaBambuConnect } from "./bambu-connect.js";
|
|
16
16
|
import { BlenderMcpBridge } from "./blender-mcp-bridge.js";
|
|
17
|
-
import { hasAmsMappingInput, normalizeAmsMappingObject, normalizeBridgeAmsTrayValue } from "./ams-mapping.js";
|
|
17
|
+
import { bridgeAmsMapping2Entry, bridgeAmsMappingValue, externalTrayForNozzle, hasAmsMappingInput, normalizeAmsMappingObject, normalizeBridgeAmsTrayValue } from "./ams-mapping.js";
|
|
18
18
|
import { analyze3MFAmsRequirements, analyze3MFPlateObjects, analyzeCollarCharm3MF, extractBambuTemplateSettings, getCollarCharmRolePolicy, parse3MF } from './3mf_parser.js';
|
|
19
19
|
import { BambuImplementation, assertDirectPrintSupported } from "./printers/bambu.js";
|
|
20
20
|
import { inspectPrintFile } from "./safety/print-file.js";
|
|
21
21
|
import { validatePrinterState } from "./safety/printer-state.js";
|
|
22
|
+
import { isDualNozzleModel } from "./safety/limits.js";
|
|
22
23
|
import { withPrinterOperation, withPrintSnapshot, uniquePrintName } from "./safety/artifact.js";
|
|
23
24
|
import { detectProfilesRoot } from "./slicer/profile-flatten.js";
|
|
24
25
|
dotenv.config();
|
|
@@ -90,6 +91,20 @@ function resolveNozzleType(value) {
|
|
|
90
91
|
}
|
|
91
92
|
return normalized;
|
|
92
93
|
}
|
|
94
|
+
/** BambuStudio's nozzle_volume_type value for each tool argument value. */
|
|
95
|
+
const NOZZLE_VOLUME_TYPES = { standard: "Standard", high_flow: "High Flow" };
|
|
96
|
+
const VALID_NOZZLE_VOLUME_TYPES = Object.keys(NOZZLE_VOLUME_TYPES);
|
|
97
|
+
/** The installed nozzle's flow type. Bambu machine presets assume Standard; the print gate compares this with the printer. */
|
|
98
|
+
function resolveNozzleVolumeType(value) {
|
|
99
|
+
const raw = value === undefined || value === null || value === "" ? process.env.BAMBU_NOZZLE_VOLUME_TYPE?.trim() : value;
|
|
100
|
+
if (raw === undefined || raw === "")
|
|
101
|
+
return undefined;
|
|
102
|
+
const normalized = String(raw).trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
103
|
+
if (!VALID_NOZZLE_VOLUME_TYPES.includes(normalized)) {
|
|
104
|
+
throw new Error(`Invalid nozzle volume type "${raw}". Valid types: ${VALID_NOZZLE_VOLUME_TYPES.join(", ")}`);
|
|
105
|
+
}
|
|
106
|
+
return NOZZLE_VOLUME_TYPES[normalized];
|
|
107
|
+
}
|
|
93
108
|
const VALID_BAMBUSTUDIO_CLI_BED_TYPES = ["textured_plate", "cool_plate", "engineering_plate", "hot_plate"];
|
|
94
109
|
// Map model IDs to BambuStudio --load-machine preset names
|
|
95
110
|
const BAMBU_MODEL_PRESETS = {
|
|
@@ -509,7 +524,7 @@ function defaultSlicerPathFor(slicerType) {
|
|
|
509
524
|
}
|
|
510
525
|
function resolveSlicerConfig(args) {
|
|
511
526
|
const slicerType = normalizeSlicerType(String(args?.slicer_type || DEFAULT_SLICER_TYPE));
|
|
512
|
-
const slicerPath = String(args?.slicer_path || process.env.SLICER_PATH || defaultSlicerPathFor(slicerType));
|
|
527
|
+
const slicerPath = slicerType === "orca-slicer-api" ? "" : String(args?.slicer_path || process.env.SLICER_PATH || defaultSlicerPathFor(slicerType));
|
|
513
528
|
const slicerProfile = String(args?.slicer_profile || DEFAULT_SLICER_PROFILE);
|
|
514
529
|
return { slicerType, slicerPath, slicerProfile };
|
|
515
530
|
}
|
|
@@ -721,18 +736,10 @@ function stringifyBridgeJson(value) {
|
|
|
721
736
|
return value;
|
|
722
737
|
return JSON.stringify(value);
|
|
723
738
|
}
|
|
724
|
-
function nativeAmsMapping2(mapping) {
|
|
739
|
+
function nativeAmsMapping2(mapping, dualNozzle = false) {
|
|
725
740
|
if (!mapping || mapping.length === 0)
|
|
726
741
|
return undefined;
|
|
727
|
-
return JSON.stringify(mapping.map((value) =>
|
|
728
|
-
if (value < 0 || value === 255)
|
|
729
|
-
return { ams_id: 255, slot_id: 255 };
|
|
730
|
-
if (value === 254)
|
|
731
|
-
return { ams_id: 254, slot_id: 254 };
|
|
732
|
-
if (value >= 128)
|
|
733
|
-
return { ams_id: 128, slot_id: value - 128 };
|
|
734
|
-
return { ams_id: Math.floor(value / 4), slot_id: value % 4 };
|
|
735
|
-
}));
|
|
742
|
+
return JSON.stringify(mapping.map((value) => bridgeAmsMapping2Entry(value, dualNozzle)));
|
|
736
743
|
}
|
|
737
744
|
function redactPrintParams(params) {
|
|
738
745
|
return {
|
|
@@ -1011,6 +1018,9 @@ class BambuPrinterMCPServer {
|
|
|
1011
1018
|
resolveSlicerConfigFromArgs(args, toolName) {
|
|
1012
1019
|
const config = resolveSlicerConfig(args);
|
|
1013
1020
|
if (args?.slicer_path !== undefined) {
|
|
1021
|
+
if (config.slicerType === "orca-slicer-api") {
|
|
1022
|
+
throw new Error(`${toolName}: remote slicing uses trusted SLICER_API_URL and BAMBU_PROFILES_ROOT configuration; slicer_path is unsupported.`);
|
|
1023
|
+
}
|
|
1014
1024
|
const resolvedSlicerPath = this.resolveExecutableSelectorArg(args.slicer_path, toolName, "slicer_path");
|
|
1015
1025
|
if (resolvedSlicerPath !== undefined) {
|
|
1016
1026
|
config.slicerPath = resolvedSlicerPath;
|
|
@@ -1034,12 +1044,13 @@ class BambuPrinterMCPServer {
|
|
|
1034
1044
|
}
|
|
1035
1045
|
console.error(`3MF has no gcode - auto-slicing with ${slicerType} for ${printModel}`);
|
|
1036
1046
|
const autoSliceOptions = {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1047
|
+
...(slicerType === "orca-slicer-api" ? {} : {
|
|
1048
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
1049
|
+
}),
|
|
1050
|
+
...(slicerType === "orca-slicer-api" ? { slicePlate: Number(args.plate_index ?? 0) + 1 } : {}),
|
|
1041
1051
|
bedType: bedType ? resolveBambuStudioCliBedType(bedType) : undefined,
|
|
1042
1052
|
nozzleType: resolveNozzleType(args?.nozzle_type),
|
|
1053
|
+
nozzleVolumeType: resolveNozzleVolumeType(args?.nozzle_volume_type),
|
|
1043
1054
|
};
|
|
1044
1055
|
threeMFPath = await this.stlManipulator.sliceSTL(threeMFPath, slicerType, slicerPath, slicerProfile || undefined, undefined, printPreset, autoSliceOptions);
|
|
1045
1056
|
console.error("Auto-sliced to: " + threeMFPath);
|
|
@@ -1048,8 +1059,9 @@ class BambuPrinterMCPServer {
|
|
|
1048
1059
|
async resolveAmsPrintSettings(threeMFPath, args, host, bambuSerial, bambuToken, printModel, printNozzle) {
|
|
1049
1060
|
const parsed3MFData = await parse3MF(threeMFPath);
|
|
1050
1061
|
let parsedAmsMapping;
|
|
1062
|
+
const trayOptions = { dualNozzle: isDualNozzleModel(printModel) };
|
|
1051
1063
|
if (parsed3MFData.slicerConfig?.ams_mapping) {
|
|
1052
|
-
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping);
|
|
1064
|
+
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping, "ams_mapping", trayOptions);
|
|
1053
1065
|
if (slots.length > 0) {
|
|
1054
1066
|
parsedAmsMapping = slots;
|
|
1055
1067
|
}
|
|
@@ -1062,10 +1074,10 @@ class BambuPrinterMCPServer {
|
|
|
1062
1074
|
if (hasUserAmsMapping) {
|
|
1063
1075
|
let userMappingOverride;
|
|
1064
1076
|
if (Array.isArray(args.ams_mapping)) {
|
|
1065
|
-
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]
|
|
1077
|
+
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]`, trayOptions));
|
|
1066
1078
|
}
|
|
1067
1079
|
else if (args.ams_mapping && typeof args.ams_mapping === 'object') {
|
|
1068
|
-
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping);
|
|
1080
|
+
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping, "ams_mapping", trayOptions);
|
|
1069
1081
|
}
|
|
1070
1082
|
if (userMappingOverride && userMappingOverride.length > 0) {
|
|
1071
1083
|
finalAmsMapping = userMappingOverride;
|
|
@@ -1074,7 +1086,7 @@ class BambuPrinterMCPServer {
|
|
|
1074
1086
|
}
|
|
1075
1087
|
}
|
|
1076
1088
|
if (!hasUserAmsMapping && hasUserAmsSlots) {
|
|
1077
|
-
const userSlots = args.ams_slots.map((slot, i) => normalizeBridgeAmsTrayValue(slot, `ams_slots[${i}]
|
|
1089
|
+
const userSlots = args.ams_slots.map((slot, i) => normalizeBridgeAmsTrayValue(slot, `ams_slots[${i}]`, trayOptions));
|
|
1078
1090
|
finalAmsSlots = userSlots;
|
|
1079
1091
|
finalAmsMapping = undefined;
|
|
1080
1092
|
useAMS = userSlots.length > 0;
|
|
@@ -1091,8 +1103,10 @@ class BambuPrinterMCPServer {
|
|
|
1091
1103
|
useAMS = true;
|
|
1092
1104
|
}
|
|
1093
1105
|
if (args?.use_ams === false) {
|
|
1094
|
-
|
|
1095
|
-
|
|
1106
|
+
if (!hasUserAmsMapping)
|
|
1107
|
+
finalAmsMapping = undefined;
|
|
1108
|
+
if (!hasUserAmsSlots)
|
|
1109
|
+
finalAmsSlots = undefined;
|
|
1096
1110
|
useAMS = false;
|
|
1097
1111
|
}
|
|
1098
1112
|
if ((!finalAmsMapping || finalAmsMapping.length === 0) && (!finalAmsSlots || finalAmsSlots.length === 0)) {
|
|
@@ -1162,8 +1176,16 @@ class BambuPrinterMCPServer {
|
|
|
1162
1176
|
inspection.usedFilamentPositions.forEach((position, index) => { mapping[position] = finalAmsSlots[index]; });
|
|
1163
1177
|
}
|
|
1164
1178
|
if (!useAMS) {
|
|
1165
|
-
mapping = Array(inspection.materials.length).fill(-1);
|
|
1166
|
-
|
|
1179
|
+
mapping ?? (mapping = Array(inspection.materials.length).fill(-1));
|
|
1180
|
+
const dualNozzle = isDualNozzleModel(printModel);
|
|
1181
|
+
for (const position of inspection.usedFilamentPositions) {
|
|
1182
|
+
if (mapping[position] === undefined || mapping[position] < 0) {
|
|
1183
|
+
mapping[position] = externalTrayForNozzle(inspection.filamentNozzleIndices?.[position], dualNozzle);
|
|
1184
|
+
}
|
|
1185
|
+
if (mapping[position] !== 254 && !(dualNozzle && mapping[position] === 255)) {
|
|
1186
|
+
throw new Error("use_ams:false requires an external-spool mapping for every used filament.");
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1167
1189
|
}
|
|
1168
1190
|
const requirements = { ...inspection, amsMapping: mapping, useAMS };
|
|
1169
1191
|
const initialStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
@@ -1172,7 +1194,7 @@ class BambuPrinterMCPServer {
|
|
|
1172
1194
|
const confirmedStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1173
1195
|
validatePrinterState(confirmedStatus, requirements);
|
|
1174
1196
|
this.bambu.assertBedClearance(confirmedStatus, bedClearance);
|
|
1175
|
-
const amsMapping = stringifyBridgeJson(mapping);
|
|
1197
|
+
const amsMapping = stringifyBridgeJson(mapping?.map(value => bridgeAmsMappingValue(value, isDualNozzleModel(printModel))));
|
|
1176
1198
|
const remoteFileName = uniquePrintName(threeMFPath);
|
|
1177
1199
|
const params = {
|
|
1178
1200
|
dev_id: devId,
|
|
@@ -1189,7 +1211,7 @@ class BambuPrinterMCPServer {
|
|
|
1189
1211
|
ftp_file_md5: String(args?.ftp_file_md5 || ""),
|
|
1190
1212
|
nozzle_mapping: stringifyBridgeJson(args?.nozzle_mapping) || "",
|
|
1191
1213
|
ams_mapping: amsMapping || "",
|
|
1192
|
-
ams_mapping2:
|
|
1214
|
+
ams_mapping2: nativeAmsMapping2(mapping, isDualNozzleModel(printModel)) || "",
|
|
1193
1215
|
ams_mapping_info: stringifyBridgeJson(args?.ams_mapping_info) || "",
|
|
1194
1216
|
nozzles_info: stringifyBridgeJson(args?.nozzles_info) || "",
|
|
1195
1217
|
connection_type: connectionType,
|
|
@@ -1229,7 +1251,7 @@ class BambuPrinterMCPServer {
|
|
|
1229
1251
|
host: devIp, serial: devId, token: password || bambuToken, filePath: snapshot,
|
|
1230
1252
|
projectName: String(params.task_name), presetName, remoteName: remoteFileName,
|
|
1231
1253
|
plateIndex, bedType: printBedType, useAMS,
|
|
1232
|
-
amsMapping, amsMapping2: nativeAmsMapping2(mapping),
|
|
1254
|
+
amsMapping, amsMapping2: nativeAmsMapping2(mapping, isDualNozzleModel(printModel)),
|
|
1233
1255
|
bedLeveling: params.task_bed_leveling,
|
|
1234
1256
|
flowCalibration: params.task_flow_cali,
|
|
1235
1257
|
vibrationCalibration: params.task_vibration_cali,
|
|
@@ -1326,12 +1348,14 @@ class BambuPrinterMCPServer {
|
|
|
1326
1348
|
return this.print3mfViaBambuNetwork({ ...args, bambu_model: model, connection_type: "lan", bambu_network_method: "start_local_print" }, host, bambuSerial, bambuToken, { onUpdate, signal });
|
|
1327
1349
|
}
|
|
1328
1350
|
async uploadFileViaBambuNative(args, host, bambuSerial, bambuToken, onUpdate, signal) {
|
|
1329
|
-
assertBambuNativeAvailable();
|
|
1330
1351
|
if (args.print)
|
|
1331
1352
|
throw new Error("Native upload never starts printing; use print_3mf.");
|
|
1353
|
+
// Resolve and enforce the model first, as the print path does, so a
|
|
1354
|
+
// non-X2D request is never told to install the native helper.
|
|
1332
1355
|
const model = await this.resolveBambuModel(args.bambu_model);
|
|
1333
1356
|
if (model !== "x2d")
|
|
1334
1357
|
throw new Error("connection_mode=bambu_native is restricted to X2D.");
|
|
1358
|
+
assertBambuNativeAvailable();
|
|
1335
1359
|
const unsupported = ["use_ams", "ams_mapping", "ams_mapping2", "ams_mapping_info", "nozzle_mapping", "nozzles_info",
|
|
1336
1360
|
"bed_leveling", "flow_calibration", "vibration_calibration", "layer_inspect", "timelapse"];
|
|
1337
1361
|
for (const key of unsupported) {
|
|
@@ -1430,10 +1454,9 @@ class BambuPrinterMCPServer {
|
|
|
1430
1454
|
throw new Error('BambuStudio CLI SuperTack bed type is not verified; use a pre-sliced 3MF for SuperTack or choose textured_plate, cool_plate, engineering_plate, or hot_plate.');
|
|
1431
1455
|
}
|
|
1432
1456
|
const autoSliceOptions = {
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
skipModifiedGcodes: true,
|
|
1457
|
+
...(slicerType === "orca-slicer-api" ? {} : {
|
|
1458
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
1459
|
+
}),
|
|
1437
1460
|
bedType,
|
|
1438
1461
|
};
|
|
1439
1462
|
try {
|
|
@@ -1841,6 +1864,11 @@ class BambuPrinterMCPServer {
|
|
|
1841
1864
|
enum: [...VALID_NOZZLE_TYPES],
|
|
1842
1865
|
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."
|
|
1843
1866
|
},
|
|
1867
|
+
nozzle_volume_type: {
|
|
1868
|
+
type: "string",
|
|
1869
|
+
enum: [...VALID_NOZZLE_VOLUME_TYPES],
|
|
1870
|
+
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."
|
|
1871
|
+
},
|
|
1844
1872
|
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." },
|
|
1845
1873
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
1846
1874
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
@@ -1896,6 +1924,11 @@ class BambuPrinterMCPServer {
|
|
|
1896
1924
|
enum: [...VALID_NOZZLE_TYPES],
|
|
1897
1925
|
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."
|
|
1898
1926
|
},
|
|
1927
|
+
nozzle_volume_type: {
|
|
1928
|
+
type: "string",
|
|
1929
|
+
enum: [...VALID_NOZZLE_VOLUME_TYPES],
|
|
1930
|
+
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."
|
|
1931
|
+
},
|
|
1899
1932
|
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." },
|
|
1900
1933
|
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." },
|
|
1901
1934
|
repetitions: { type: "number", description: "Print N identical copies of the model. Each copy gets its own plate placement. Example: 3 prints three copies." },
|
|
@@ -1916,7 +1949,7 @@ class BambuPrinterMCPServer {
|
|
|
1916
1949
|
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." },
|
|
1917
1950
|
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." },
|
|
1918
1951
|
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." },
|
|
1919
|
-
slice_plate: { type: "number", description: "Which plate index to slice. 0 = all plates
|
|
1952
|
+
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." }
|
|
1920
1953
|
},
|
|
1921
1954
|
required: ["stl_path", "bambu_model"]
|
|
1922
1955
|
}
|
|
@@ -2002,6 +2035,7 @@ class BambuPrinterMCPServer {
|
|
|
2002
2035
|
password: { type: "string", description: "Printer password/access code override for LAN/local bridge methods." },
|
|
2003
2036
|
bed_type: { type: "string", enum: ["textured_plate", "cool_plate", "engineering_plate", "hot_plate", "supertack_plate"], description: "Bed plate type currently installed (default: textured_plate)." },
|
|
2004
2037
|
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)." },
|
|
2038
|
+
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)." },
|
|
2005
2039
|
plate_index: { type: "number", description: "Zero-based plate index to print from the sliced 3MF; converted to FULU's one-based PrintParams plate_index." },
|
|
2006
2040
|
project_name: { type: "string", description: "Optional project name sent in FULU PrintParams; defaults to the 3MF filename without extension." },
|
|
2007
2041
|
preset_name: { type: "string", description: "Optional preset name sent in FULU PrintParams; defaults to project plus one-based plate index." },
|
|
@@ -2018,8 +2052,8 @@ class BambuPrinterMCPServer {
|
|
|
2018
2052
|
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)." },
|
|
2019
2053
|
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." },
|
|
2020
2054
|
use_ams: { type: "boolean", description: "Whether to use the AMS; defaults to auto-detect from the 3MF mapping." },
|
|
2021
|
-
ams_mapping: { type: "array", description: "AMS slot mapping array used by both local MCP printing and FULU PrintParams.", items: { type: "number" } },
|
|
2022
|
-
ams_slots: { type: "array", description: "Per-used-filament AMS slot list, matching the local LAN print path.", items: { type: "number" } },
|
|
2055
|
+
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" } },
|
|
2056
|
+
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" } },
|
|
2023
2057
|
ams_mapping_bridge: { type: "string", description: "Raw JSON string override for FULU PrintParams ams_mapping when the automatic array is not enough." },
|
|
2024
2058
|
ams_mapping2: { type: "string", description: "Raw JSON string for FULU PrintParams ams_mapping2, matching OrcaSlicer-bambulab's v1 AMS mapping field." },
|
|
2025
2059
|
ams_mapping_info: { type: "string", description: "Raw JSON string for FULU PrintParams ams_mapping_info, matching OrcaSlicer-bambulab's detailed AMS mapping field." },
|
|
@@ -2265,7 +2299,7 @@ class BambuPrinterMCPServer {
|
|
|
2265
2299
|
},
|
|
2266
2300
|
{
|
|
2267
2301
|
name: "reread_ams_rfid",
|
|
2268
|
-
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.",
|
|
2302
|
+
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.",
|
|
2269
2303
|
inputSchema: {
|
|
2270
2304
|
type: "object",
|
|
2271
2305
|
properties: {
|
|
@@ -2298,7 +2332,7 @@ class BambuPrinterMCPServer {
|
|
|
2298
2332
|
},
|
|
2299
2333
|
{
|
|
2300
2334
|
name: "set_fan_speed",
|
|
2301
|
-
description: "Set a Bambu printer fan speed percentage. X2D on macOS
|
|
2335
|
+
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.",
|
|
2302
2336
|
inputSchema: {
|
|
2303
2337
|
type: "object",
|
|
2304
2338
|
properties: {
|
|
@@ -2357,7 +2391,7 @@ class BambuPrinterMCPServer {
|
|
|
2357
2391
|
},
|
|
2358
2392
|
{
|
|
2359
2393
|
name: "set_ams_drying",
|
|
2360
|
-
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.",
|
|
2394
|
+
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.",
|
|
2361
2395
|
inputSchema: {
|
|
2362
2396
|
type: "object",
|
|
2363
2397
|
properties: {
|
|
@@ -2429,6 +2463,11 @@ class BambuPrinterMCPServer {
|
|
|
2429
2463
|
enum: [...VALID_NOZZLE_TYPES],
|
|
2430
2464
|
description: "Installed nozzle material, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_TYPE, else the preset's stock nozzle)."
|
|
2431
2465
|
},
|
|
2466
|
+
nozzle_volume_type: {
|
|
2467
|
+
type: "string",
|
|
2468
|
+
enum: [...VALID_NOZZLE_VOLUME_TYPES],
|
|
2469
|
+
description: "Installed nozzle flow type, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_VOLUME_TYPE, else standard)."
|
|
2470
|
+
},
|
|
2432
2471
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
2433
2472
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
2434
2473
|
bambu_token: { type: "string", description: "Access token (default: value from env)" },
|
|
@@ -2442,12 +2481,12 @@ class BambuPrinterMCPServer {
|
|
|
2442
2481
|
use_ams: { type: "boolean", description: "Whether to use the AMS (default: auto-detect from 3MF)" },
|
|
2443
2482
|
ams_mapping: {
|
|
2444
2483
|
type: "array",
|
|
2445
|
-
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.",
|
|
2484
|
+
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.",
|
|
2446
2485
|
items: { type: "number" }
|
|
2447
2486
|
},
|
|
2448
2487
|
ams_slots: {
|
|
2449
2488
|
type: "array",
|
|
2450
|
-
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.",
|
|
2489
|
+
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.",
|
|
2451
2490
|
items: { type: "number" }
|
|
2452
2491
|
},
|
|
2453
2492
|
auto_match_ams: {
|
|
@@ -2618,9 +2657,42 @@ class BambuPrinterMCPServer {
|
|
|
2618
2657
|
const host = String(args?.host || DEFAULT_HOST);
|
|
2619
2658
|
const bambuSerial = String(args?.bambu_serial || DEFAULT_BAMBU_SERIAL);
|
|
2620
2659
|
const bambuToken = String(args?.bambu_token || DEFAULT_BAMBU_TOKEN);
|
|
2621
|
-
const
|
|
2622
|
-
(
|
|
2623
|
-
|
|
2660
|
+
const x2dTarget = bambuSerial.trim().toUpperCase().startsWith("20P") ||
|
|
2661
|
+
(DEFAULT_BAMBU_MODEL === "x2d" && bambuSerial === DEFAULT_BAMBU_SERIAL);
|
|
2662
|
+
// Resolved lazily: the Linux availability check reads files, and most
|
|
2663
|
+
// tools (status, slicing, STL) never need it.
|
|
2664
|
+
// One resolution per request; null means native X2D controls are usable.
|
|
2665
|
+
let nativeUnavailable;
|
|
2666
|
+
const nativeUnavailableReason = () => (nativeUnavailable ?? (nativeUnavailable = x2dTarget ? bambuNativeUnavailableReason() ?? null : "The selected printer is not an X2D."));
|
|
2667
|
+
const nativeControls = () => nativeUnavailableReason() === null;
|
|
2668
|
+
// Linux sent X2D stop and heater-off over MQTT before the native route
|
|
2669
|
+
// existed. Send them over both routes there: a stuck helper must not
|
|
2670
|
+
// delay an emergency control, and a repeated stop or heater-off is
|
|
2671
|
+
// harmless. The immediate MQTT send can precede the exit of an older
|
|
2672
|
+
// print or heating helper that then still dispatches, so MQTT is sent
|
|
2673
|
+
// again once every older helper for this printer has exited, and only
|
|
2674
|
+
// that send counts. Either counted route succeeding is success. macOS
|
|
2675
|
+
// stays native-only.
|
|
2676
|
+
const linuxEmergency = (native, mqtt) => async () => {
|
|
2677
|
+
if (process.platform !== "linux")
|
|
2678
|
+
return native();
|
|
2679
|
+
const [nativeResult, immediateResult, mqttResult] = await Promise.allSettled([
|
|
2680
|
+
native(),
|
|
2681
|
+
mqtt(),
|
|
2682
|
+
interruptNativeOperations(bambuSerial).then(() => mqtt()),
|
|
2683
|
+
]);
|
|
2684
|
+
const reason = (result) => result.status === "rejected" ? (result.reason instanceof Error ? result.reason.message : String(result.reason)) : undefined;
|
|
2685
|
+
if (nativeResult.status === "rejected" && mqttResult.status === "rejected") {
|
|
2686
|
+
throw new Error(`native route: ${reason(nativeResult)}; MQTT route: ${reason(mqttResult)}`);
|
|
2687
|
+
}
|
|
2688
|
+
const state = (result) => result.status === "fulfilled" ? "sent" : "failed";
|
|
2689
|
+
return {
|
|
2690
|
+
status: "success",
|
|
2691
|
+
routes: { native: state(nativeResult), mqtt: state(mqttResult), mqtt_immediate: state(immediateResult) },
|
|
2692
|
+
...(reason(nativeResult) ? { native_error: reason(nativeResult) } : {}),
|
|
2693
|
+
...(reason(mqttResult) ? { mqtt_error: reason(mqttResult) } : {}),
|
|
2694
|
+
};
|
|
2695
|
+
};
|
|
2624
2696
|
const requestedTemplateDir = typeof args?.template_dir === "string" && args.template_dir.trim().length > 0
|
|
2625
2697
|
? String(args.template_dir)
|
|
2626
2698
|
: undefined;
|
|
@@ -2822,19 +2894,19 @@ class BambuPrinterMCPServer {
|
|
|
2822
2894
|
break;
|
|
2823
2895
|
}
|
|
2824
2896
|
case "cancel_print":
|
|
2825
|
-
result = await this.bambu.cancelJob(host, bambuSerial, bambuToken, nativeControls
|
|
2826
|
-
? () => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
2827
|
-
messageJson: JSON.stringify({ print: { command: "stop", param: "", sequence_id: String(Date.now()) } }), qos: 1 })
|
|
2897
|
+
result = await this.bambu.cancelJob(host, bambuSerial, bambuToken, nativeControls()
|
|
2898
|
+
? linuxEmergency(() => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
2899
|
+
messageJson: JSON.stringify({ print: { command: "stop", param: "", sequence_id: String(Date.now()) } }), qos: 1 }), () => this.bambu.sendStopOverMqtt(host, bambuSerial, bambuToken))
|
|
2828
2900
|
: undefined);
|
|
2829
2901
|
break;
|
|
2830
2902
|
case "pause_print":
|
|
2831
|
-
result = await this.bambu.pauseJob(host, bambuSerial, bambuToken, nativeControls
|
|
2903
|
+
result = await this.bambu.pauseJob(host, bambuSerial, bambuToken, nativeControls()
|
|
2832
2904
|
? () => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
2833
2905
|
messageJson: JSON.stringify({ print: { command: "pause", param: "", sequence_id: String(Date.now()) } }), qos: 1 })
|
|
2834
2906
|
: undefined);
|
|
2835
2907
|
break;
|
|
2836
2908
|
case "resume_print":
|
|
2837
|
-
result = await this.bambu.resumeJob(host, bambuSerial, bambuToken, nativeControls
|
|
2909
|
+
result = await this.bambu.resumeJob(host, bambuSerial, bambuToken, nativeControls()
|
|
2838
2910
|
? (assertActive, beforeDispatch) => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
2839
2911
|
messageJson: JSON.stringify({ print: { command: "resume", param: "", sequence_id: String(Date.now()) } }), qos: 1 }, { signal: extra.signal, assertActive, beforeDispatch })
|
|
2840
2912
|
: undefined);
|
|
@@ -2843,8 +2915,9 @@ class BambuPrinterMCPServer {
|
|
|
2843
2915
|
if (!args?.message_json) {
|
|
2844
2916
|
throw new Error("Missing required parameter: message_json");
|
|
2845
2917
|
}
|
|
2846
|
-
if (!nativeControls) {
|
|
2847
|
-
throw new Error("x2d_native_control is restricted to a selected X2D printer on macOS."
|
|
2918
|
+
if (!nativeControls()) {
|
|
2919
|
+
throw new Error("x2d_native_control is restricted to a selected X2D printer on macOS, or on Linux with the native helper installed. " +
|
|
2920
|
+
nativeUnavailableReason());
|
|
2848
2921
|
}
|
|
2849
2922
|
const messageJson = validateBambuNativeMetadata(String(args.message_json));
|
|
2850
2923
|
result = await sendNativeCommand({
|
|
@@ -2858,7 +2931,7 @@ class BambuPrinterMCPServer {
|
|
|
2858
2931
|
break;
|
|
2859
2932
|
}
|
|
2860
2933
|
case "clear_hms_errors":
|
|
2861
|
-
result = await this.bambu.clearHmsErrors(host, bambuSerial, bambuToken, nativeControls
|
|
2934
|
+
result = await this.bambu.clearHmsErrors(host, bambuSerial, bambuToken, nativeControls()
|
|
2862
2935
|
? (assertActive, beforeDispatch) => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
2863
2936
|
messageJson: JSON.stringify({ print: { command: "clean_print_error", param: "", sequence_id: String(Date.now()) } }), qos: 1 }, { signal: extra.signal, assertActive, beforeDispatch })
|
|
2864
2937
|
: undefined);
|
|
@@ -2867,7 +2940,7 @@ class BambuPrinterMCPServer {
|
|
|
2867
2940
|
if (!args?.mode) {
|
|
2868
2941
|
throw new Error("Missing required parameter: mode");
|
|
2869
2942
|
}
|
|
2870
|
-
if (nativeControls) {
|
|
2943
|
+
if (nativeControls()) {
|
|
2871
2944
|
const normalized = String(args.mode).trim().toLowerCase();
|
|
2872
2945
|
const mode = normalized === "silent" ? 1
|
|
2873
2946
|
: normalized === "standard" ? 2
|
|
@@ -2894,7 +2967,7 @@ class BambuPrinterMCPServer {
|
|
|
2894
2967
|
if (!args?.mode) {
|
|
2895
2968
|
throw new Error("Missing required parameter: mode");
|
|
2896
2969
|
}
|
|
2897
|
-
if (nativeControls) {
|
|
2970
|
+
if (nativeControls()) {
|
|
2898
2971
|
const mode = String(args.mode).trim().toLowerCase();
|
|
2899
2972
|
if (mode !== "cooling" && mode !== "heating") {
|
|
2900
2973
|
throw new Error("Airduct mode must be one of: cooling, heating.");
|
|
@@ -2921,7 +2994,7 @@ class BambuPrinterMCPServer {
|
|
|
2921
2994
|
if (args?.ams_id === undefined || args?.slot_id === undefined) {
|
|
2922
2995
|
throw new Error("Missing required parameters: ams_id and slot_id");
|
|
2923
2996
|
}
|
|
2924
|
-
if (nativeControls) {
|
|
2997
|
+
if (nativeControls()) {
|
|
2925
2998
|
const amsId = Number(args.ams_id);
|
|
2926
2999
|
const slotId = Number(args.slot_id);
|
|
2927
3000
|
if ((!Number.isInteger(amsId) || (amsId < 0 || amsId > 3)) && amsId !== 128) {
|
|
@@ -2958,9 +3031,14 @@ class BambuPrinterMCPServer {
|
|
|
2958
3031
|
const heaterModel = args.temperature === 0
|
|
2959
3032
|
? String(args.bambu_model || DEFAULT_BAMBU_MODEL).trim().toLowerCase()
|
|
2960
3033
|
: await this.resolveBambuModel(args.bambu_model);
|
|
2961
|
-
result = await this.bambu.setTemperature(host, bambuSerial, bambuToken, String(args.component), args.temperature, heaterModel || undefined, args?.material !== undefined ? String(args.material) : undefined, Number(args?.nozzle_diameter ?? DEFAULT_NOZZLE_DIAMETER), (args.temperature === 0 ? nativeControls : heaterModel === "x2d" &&
|
|
2962
|
-
? (heater, target, assertActive, beforeDispatch) =>
|
|
2963
|
-
|
|
3034
|
+
result = await this.bambu.setTemperature(host, bambuSerial, bambuToken, String(args.component), args.temperature, heaterModel || undefined, args?.material !== undefined ? String(args.material) : undefined, Number(args?.nozzle_diameter ?? DEFAULT_NOZZLE_DIAMETER), (args.temperature === 0 ? nativeControls() : heaterModel === "x2d" && bambuNativeControlsAvailable())
|
|
3035
|
+
? (heater, target, assertActive, beforeDispatch) => {
|
|
3036
|
+
const native = () => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
3037
|
+
messageJson: buildBambuNativeTemperatureCommand(heater, target).messageJson }, { signal: extra.signal, assertActive, beforeDispatch });
|
|
3038
|
+
return target === 0
|
|
3039
|
+
? linuxEmergency(native, () => this.bambu.sendHeaterOffOverMqtt(host, bambuSerial, bambuToken, heater))()
|
|
3040
|
+
: native();
|
|
3041
|
+
}
|
|
2964
3042
|
: undefined);
|
|
2965
3043
|
break;
|
|
2966
3044
|
}
|
|
@@ -2968,7 +3046,7 @@ class BambuPrinterMCPServer {
|
|
|
2968
3046
|
if (!args?.fan || args?.speed === undefined) {
|
|
2969
3047
|
throw new Error("Missing required parameters: fan and speed");
|
|
2970
3048
|
}
|
|
2971
|
-
if (nativeControls) {
|
|
3049
|
+
if (nativeControls()) {
|
|
2972
3050
|
const status = await this.bambu.getStatus(host, bambuSerial, bambuToken);
|
|
2973
3051
|
const printerState = String(status?.raw?.gcode_state ?? status?.raw?.print?.gcode_state ?? status?.gcode_state ?? status?.status ?? "").toUpperCase();
|
|
2974
3052
|
if (printerState === "RUNNING" && args?.confirm_during_print !== true) {
|
|
@@ -2996,7 +3074,7 @@ class BambuPrinterMCPServer {
|
|
|
2996
3074
|
if (!args?.light || !args?.mode) {
|
|
2997
3075
|
throw new Error("Missing required parameters: light and mode");
|
|
2998
3076
|
}
|
|
2999
|
-
if (nativeControls) {
|
|
3077
|
+
if (nativeControls()) {
|
|
3000
3078
|
const light = String(args.light).trim();
|
|
3001
3079
|
const mode = String(args.mode).trim().toLowerCase();
|
|
3002
3080
|
if (light !== "chamber_light" && light !== "chamber_light2") {
|
|
@@ -3031,7 +3109,7 @@ class BambuPrinterMCPServer {
|
|
|
3031
3109
|
if (!Array.isArray(args?.object_ids)) {
|
|
3032
3110
|
throw new Error("Missing required parameter: object_ids");
|
|
3033
3111
|
}
|
|
3034
|
-
if (nativeControls) {
|
|
3112
|
+
if (nativeControls()) {
|
|
3035
3113
|
const objectIds = args.object_ids.map((id) => Number(id));
|
|
3036
3114
|
if (objectIds.length === 0 || objectIds.some((id) => !Number.isInteger(id) || id < 0)) {
|
|
3037
3115
|
throw new Error("object_ids must contain one or more non-negative integers.");
|
|
@@ -3054,7 +3132,7 @@ class BambuPrinterMCPServer {
|
|
|
3054
3132
|
if (!args?.action || args?.ams_id === undefined) {
|
|
3055
3133
|
throw new Error("Missing required parameters: action and ams_id");
|
|
3056
3134
|
}
|
|
3057
|
-
if (nativeControls) {
|
|
3135
|
+
if (nativeControls()) {
|
|
3058
3136
|
const action = String(args.action).trim().toLowerCase();
|
|
3059
3137
|
const amsId = Number(args.ams_id);
|
|
3060
3138
|
if (action !== "start" && action !== "stop") {
|
|
@@ -3164,6 +3242,7 @@ class BambuPrinterMCPServer {
|
|
|
3164
3242
|
sliceBambuOptions.filamentColours = parseFilamentColours(String(args.filament_colours));
|
|
3165
3243
|
sliceBambuOptions.bedType = resolveBambuStudioCliBedType(args?.bed_type);
|
|
3166
3244
|
sliceBambuOptions.nozzleType = resolveNozzleType(args?.nozzle_type);
|
|
3245
|
+
sliceBambuOptions.nozzleVolumeType = resolveNozzleVolumeType(args?.nozzle_volume_type);
|
|
3167
3246
|
if (args?.enable_timelapse !== undefined)
|
|
3168
3247
|
sliceBambuOptions.enableTimelapse = Boolean(args.enable_timelapse);
|
|
3169
3248
|
if (args?.allow_mix_temp !== undefined)
|
|
@@ -3244,6 +3323,7 @@ class BambuPrinterMCPServer {
|
|
|
3244
3323
|
sliceBambuOptions.filamentColours = parseFilamentColours(String(args.filament_colours));
|
|
3245
3324
|
sliceBambuOptions.bedType = resolveBambuStudioCliBedType(args?.bed_type);
|
|
3246
3325
|
sliceBambuOptions.nozzleType = resolveNozzleType(args?.nozzle_type);
|
|
3326
|
+
sliceBambuOptions.nozzleVolumeType = resolveNozzleVolumeType(args?.nozzle_volume_type);
|
|
3247
3327
|
if (args?.enable_timelapse !== undefined)
|
|
3248
3328
|
sliceBambuOptions.enableTimelapse = Boolean(args.enable_timelapse);
|
|
3249
3329
|
if (args?.allow_mix_temp !== undefined)
|
|
@@ -3341,12 +3421,12 @@ class BambuPrinterMCPServer {
|
|
|
3341
3421
|
}
|
|
3342
3422
|
console.error(`3MF has no gcode — auto-slicing with ${slicerType} for ${printModel}`);
|
|
3343
3423
|
const autoSliceOptions = {
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
skipModifiedGcodes: true,
|
|
3424
|
+
...(slicerType === "orca-slicer-api" ? { slicePlate: plateIndex + 1 } : {
|
|
3425
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
3426
|
+
}),
|
|
3348
3427
|
bedType: printBedType,
|
|
3349
3428
|
nozzleType: resolveNozzleType(args?.nozzle_type),
|
|
3429
|
+
nozzleVolumeType: resolveNozzleVolumeType(args?.nozzle_volume_type),
|
|
3350
3430
|
};
|
|
3351
3431
|
if (!explicitSlicerProfile) {
|
|
3352
3432
|
try {
|
|
@@ -3365,9 +3445,10 @@ class BambuPrinterMCPServer {
|
|
|
3365
3445
|
}
|
|
3366
3446
|
const parsed3MFData = await parse3MF(threeMFPath);
|
|
3367
3447
|
const isH2Print = H2_BAMBU_MODELS.has(printModel);
|
|
3448
|
+
const trayOptions = { dualNozzle: isDualNozzleModel(printModel) };
|
|
3368
3449
|
let parsedAmsMapping;
|
|
3369
3450
|
if (!isH2Print && parsed3MFData.slicerConfig?.ams_mapping) {
|
|
3370
|
-
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping);
|
|
3451
|
+
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping, "ams_mapping", trayOptions);
|
|
3371
3452
|
if (slots.length > 0) {
|
|
3372
3453
|
parsedAmsMapping = slots;
|
|
3373
3454
|
}
|
|
@@ -3380,10 +3461,10 @@ class BambuPrinterMCPServer {
|
|
|
3380
3461
|
if (hasUserAmsMapping) {
|
|
3381
3462
|
let userMappingOverride;
|
|
3382
3463
|
if (Array.isArray(args.ams_mapping)) {
|
|
3383
|
-
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]
|
|
3464
|
+
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]`, trayOptions));
|
|
3384
3465
|
}
|
|
3385
3466
|
else if (args.ams_mapping && typeof args.ams_mapping === 'object') {
|
|
3386
|
-
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping);
|
|
3467
|
+
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping, "ams_mapping", trayOptions);
|
|
3387
3468
|
}
|
|
3388
3469
|
if (userMappingOverride && userMappingOverride.length > 0) {
|
|
3389
3470
|
finalAmsMapping = userMappingOverride;
|
|
@@ -3424,8 +3505,10 @@ class BambuPrinterMCPServer {
|
|
|
3424
3505
|
}
|
|
3425
3506
|
}
|
|
3426
3507
|
if (args?.use_ams === false) {
|
|
3427
|
-
|
|
3428
|
-
|
|
3508
|
+
if (!hasUserAmsMapping)
|
|
3509
|
+
finalAmsMapping = undefined;
|
|
3510
|
+
if (!hasUserAmsSlots)
|
|
3511
|
+
finalAmsSlots = undefined;
|
|
3429
3512
|
useAMS = false;
|
|
3430
3513
|
}
|
|
3431
3514
|
if ((!finalAmsMapping || finalAmsMapping.length === 0) && (!finalAmsSlots || finalAmsSlots.length === 0)) {
|
package/dist/printers/bambu.d.ts
CHANGED
|
@@ -26,6 +26,13 @@ interface BambuPrintOptionsInternal {
|
|
|
26
26
|
amsSlots?: number[];
|
|
27
27
|
md5?: string;
|
|
28
28
|
}
|
|
29
|
+
/** What the human confirmed in confirmPrintPreflight, checked again before dispatch. */
|
|
30
|
+
export type PrintPreflight = {
|
|
31
|
+
finishedJob?: string;
|
|
32
|
+
serial: string;
|
|
33
|
+
model: string;
|
|
34
|
+
notices: string[];
|
|
35
|
+
};
|
|
29
36
|
export declare class BambuImplementation {
|
|
30
37
|
private readonly confirm?;
|
|
31
38
|
private printerStore;
|
|
@@ -34,8 +41,14 @@ export declare class BambuImplementation {
|
|
|
34
41
|
constructor(confirm?: ((message: string) => Promise<boolean>) | undefined);
|
|
35
42
|
confirmHardwareAction(message: string, physicalCheck?: boolean): Promise<void>;
|
|
36
43
|
private finishedJobIdentity;
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The human print confirmation. Its result goes to assertBedClearance at
|
|
46
|
+
* every later state check of the same print, which stops the print if the
|
|
47
|
+
* situation the human confirmed has changed.
|
|
48
|
+
*/
|
|
49
|
+
confirmPrintPreflight(serial: string, status: any, inspection: Awaited<ReturnType<typeof inspectPrintFile>>): Promise<PrintPreflight>;
|
|
50
|
+
/** Stop a print whose confirmed situation changed: a newly finished job, or a hidden HMS notice the preflight did not name. */
|
|
51
|
+
assertBedClearance(status: any, preflight: PrintPreflight): void;
|
|
39
52
|
private getPrinter;
|
|
40
53
|
/** Internal handoff from a successful inspected transport; never exposed as an MCP tool. */
|
|
41
54
|
recordCheckedJob(host: string, serial: string, remotePath: string, requirements: PrinterStateRequirements): void;
|
|
@@ -47,6 +60,10 @@ export declare class BambuImplementation {
|
|
|
47
60
|
print3mf(host: string, serial: string, token: string, options: BambuPrintOptionsInternal): Promise<any>;
|
|
48
61
|
private print3mfPrepared;
|
|
49
62
|
cancelJob(host: string, serial: string, token: string, dispatch?: () => Promise<any>): Promise<any>;
|
|
63
|
+
/** Raw MQTT stop for routes that already ran the shared cancellation steps. */
|
|
64
|
+
sendStopOverMqtt(host: string, serial: string, token: string): Promise<any>;
|
|
65
|
+
/** Raw MQTT heater-off; switching off needs no material or model declaration. */
|
|
66
|
+
sendHeaterOffOverMqtt(host: string, serial: string, token: string, heater: "bed" | "nozzle"): Promise<any>;
|
|
50
67
|
pauseJob(host: string, serial: string, token: string, dispatch?: () => Promise<any>): Promise<any>;
|
|
51
68
|
resumeJob(host: string, serial: string, token: string, dispatch?: (assertActive: () => void, beforeDispatch: () => Promise<void>) => Promise<any>): Promise<any>;
|
|
52
69
|
clearHmsErrors(host: string, serial: string, token: string, dispatch?: (assertActive: () => void, beforeDispatch: () => Promise<void>) => Promise<any>): Promise<any>;
|
|
@@ -54,6 +71,16 @@ export declare class BambuImplementation {
|
|
|
54
71
|
setAirductMode(host: string, serial: string, token: string, mode: string): Promise<any>;
|
|
55
72
|
rereadAmsRfid(host: string, serial: string, token: string, amsId: number, slotId: number): Promise<any>;
|
|
56
73
|
setTemperature(host: string, serial: string, token: string, component: string, temperature: unknown, bambuModel?: string, material?: string, nozzleDiameter?: number, dispatch?: (heater: "bed" | "nozzle", target: number, assertActive: () => void, beforeDispatch?: () => Promise<void>) => Promise<any>): Promise<{
|
|
74
|
+
routes: any;
|
|
75
|
+
native_error: any;
|
|
76
|
+
mqtt_error: any;
|
|
77
|
+
status: string;
|
|
78
|
+
message: string;
|
|
79
|
+
command: string;
|
|
80
|
+
} | {
|
|
81
|
+
routes?: undefined;
|
|
82
|
+
native_error?: undefined;
|
|
83
|
+
mqtt_error?: undefined;
|
|
57
84
|
status: string;
|
|
58
85
|
message: string;
|
|
59
86
|
command: string;
|