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.
Files changed (38) hide show
  1. package/CONTRIBUTORS.md +7 -1
  2. package/README.md +29 -10
  3. package/dist/ams-mapping.d.ts +18 -2
  4. package/dist/ams-mapping.js +36 -5
  5. package/dist/bambu-native.d.ts +55 -0
  6. package/dist/bambu-native.js +212 -13
  7. package/dist/index.js +154 -71
  8. package/dist/printers/bambu.d.ts +29 -2
  9. package/dist/printers/bambu.js +59 -28
  10. package/dist/safety/hidden-hms.d.ts +32 -0
  11. package/dist/safety/hidden-hms.js +405 -0
  12. package/dist/safety/limits.d.ts +6 -0
  13. package/dist/safety/limits.js +9 -0
  14. package/dist/safety/print-file.d.ts +2 -0
  15. package/dist/safety/print-file.js +83 -7
  16. package/dist/safety/printer-state.d.ts +11 -0
  17. package/dist/safety/printer-state.js +115 -22
  18. package/dist/slicer/profile-flatten.js +25 -0
  19. package/dist/slicer/remote-slicer.d.ts +12 -0
  20. package/dist/slicer/remote-slicer.js +240 -0
  21. package/dist/stl/stl-manipulator.d.ts +2 -1
  22. package/dist/stl/stl-manipulator.js +71 -42
  23. package/native/bambu-native-print.cpp +100 -26
  24. package/package.json +7 -5
  25. package/scripts/build-bambu-native.sh +53 -0
  26. package/scripts/setup-bambu-native.mjs +372 -0
  27. package/src/ams-mapping.ts +38 -5
  28. package/src/bambu-native.ts +229 -14
  29. package/src/index.ts +156 -67
  30. package/src/printers/bambu.ts +65 -25
  31. package/src/safety/hidden-hms.ts +406 -0
  32. package/src/safety/limits.ts +9 -0
  33. package/src/safety/print-file.ts +71 -7
  34. package/src/safety/printer-state.ts +117 -22
  35. package/src/slicer/profile-flatten.ts +32 -0
  36. package/src/slicer/remote-slicer.ts +216 -0
  37. package/src/stl/stl-manipulator.ts +70 -43
  38. 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
- uptodate: true,
1038
- ensureOnBed: true,
1039
- minSave: true,
1040
- skipModifiedGcodes: true,
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
- finalAmsMapping = undefined;
1095
- finalAmsSlots = undefined;
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
- inspection.usedFilamentPositions.forEach(position => { mapping[position] = 254; });
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: stringifyBridgeJson(args?.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
- uptodate: true,
1434
- ensureOnBed: true,
1435
- minSave: true,
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 (default). Use 1, 2, etc. to slice only a specific plate in multi-plate 3MF projects." }
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 uses the installed Bambu networking plug-in without UI automation; other models use MQTT.",
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 nativeControls = process.platform === "darwin" &&
2622
- (bambuSerial.trim().toUpperCase().startsWith("20P") ||
2623
- (DEFAULT_BAMBU_MODEL === "x2d" && bambuSerial === DEFAULT_BAMBU_SERIAL));
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" && process.platform === "darwin")
2962
- ? (heater, target, assertActive, beforeDispatch) => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
2963
- messageJson: buildBambuNativeTemperatureCommand(heater, target).messageJson }, { signal: extra.signal, assertActive, beforeDispatch })
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
- uptodate: true,
3345
- ensureOnBed: true,
3346
- minSave: true,
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
- finalAmsMapping = undefined;
3428
- finalAmsSlots = undefined;
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)) {
@@ -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
- confirmPrintPreflight(serial: string, status: any, inspection: Awaited<ReturnType<typeof inspectPrintFile>>): Promise<string | undefined>;
38
- assertBedClearance(status: any, confirmedFinishedJob: string | undefined): void;
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;