bambu-printer-mcp 1.1.27 → 1.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTORS.md +7 -1
- package/README.md +29 -10
- package/dist/ams-mapping.d.ts +18 -2
- package/dist/ams-mapping.js +36 -5
- package/dist/bambu-native.d.ts +55 -0
- package/dist/bambu-native.js +212 -13
- package/dist/index.js +154 -71
- package/dist/printers/bambu.d.ts +29 -2
- package/dist/printers/bambu.js +59 -28
- package/dist/safety/hidden-hms.d.ts +32 -0
- package/dist/safety/hidden-hms.js +405 -0
- package/dist/safety/limits.d.ts +6 -0
- package/dist/safety/limits.js +9 -0
- package/dist/safety/print-file.d.ts +2 -0
- package/dist/safety/print-file.js +83 -7
- package/dist/safety/printer-state.d.ts +11 -0
- package/dist/safety/printer-state.js +115 -22
- package/dist/slicer/profile-flatten.js +25 -0
- package/dist/slicer/remote-slicer.d.ts +12 -0
- package/dist/slicer/remote-slicer.js +240 -0
- package/dist/stl/stl-manipulator.d.ts +2 -1
- package/dist/stl/stl-manipulator.js +71 -42
- package/native/bambu-native-print.cpp +100 -26
- package/package.json +7 -5
- package/scripts/build-bambu-native.sh +53 -0
- package/scripts/setup-bambu-native.mjs +372 -0
- package/src/ams-mapping.ts +38 -5
- package/src/bambu-native.ts +229 -14
- package/src/index.ts +156 -67
- package/src/printers/bambu.ts +65 -25
- package/src/safety/hidden-hms.ts +406 -0
- package/src/safety/limits.ts +9 -0
- package/src/safety/print-file.ts +71 -7
- package/src/safety/printer-state.ts +117 -22
- package/src/slicer/profile-flatten.ts +32 -0
- package/src/slicer/remote-slicer.ts +216 -0
- package/src/stl/stl-manipulator.ts +70 -43
- package/scripts/build-bambu-native.zsh +0 -33
package/src/printers/bambu.ts
CHANGED
|
@@ -7,8 +7,9 @@ import os from "node:os";
|
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
import { readSafe3mfArchive } from "../safety/archive.js";
|
|
9
9
|
import { inspectPrintFile, type PrintFileInspection } from "../safety/print-file.js";
|
|
10
|
-
import { normalizeModel, validateTemperature } from "../safety/limits.js";
|
|
11
|
-
import {
|
|
10
|
+
import { isDualNozzleModel, normalizeModel, validateTemperature } from "../safety/limits.js";
|
|
11
|
+
import { bridgeAmsMapping2Entry, bridgeAmsMappingValue, externalTrayForNozzle, describeAmsTrayRange } from "../ams-mapping.js";
|
|
12
|
+
import { describeHmsKey, hiddenHmsKeys, hiddenHmsNotices, reportedHmsKey, validatePrinterState, manualHeatingRequirements, type PrinterStateRequirements } from "../safety/printer-state.js";
|
|
12
13
|
import { withPrinterOperation, withPrintSnapshot, uniquePrintName, normalizedRemotePath, cancelPendingPrinterOperations } from "../safety/artifact.js";
|
|
13
14
|
import { Client as FTPClient } from "basic-ftp";
|
|
14
15
|
import { BambuPrinter } from "bambu-js";
|
|
@@ -85,7 +86,7 @@ const H2_MODEL_NAMES = new Set(["h2", "h2c", "h2d", "h2dpro", "h2d pro", "h2s",
|
|
|
85
86
|
export function assertDirectPrintSupported(model: string | undefined, serial?: string): void {
|
|
86
87
|
if (model?.trim().toLowerCase() === "x2d" || serial?.trim().toUpperCase().startsWith("20P")) {
|
|
87
88
|
throw new Error(
|
|
88
|
-
"X2D direct printing is not supported through legacy MQTT/FTPS. Use the
|
|
89
|
+
"X2D direct printing is not supported through legacy MQTT/FTPS. Use the native eMMC route via print_3mf (macOS, or Linux with the native helper installed). " +
|
|
89
90
|
"X2D status and slicing remain available on all supported platforms."
|
|
90
91
|
);
|
|
91
92
|
}
|
|
@@ -461,6 +462,9 @@ class BambuClientStore {
|
|
|
461
462
|
}
|
|
462
463
|
}
|
|
463
464
|
|
|
465
|
+
/** What the human confirmed in confirmPrintPreflight, checked again before dispatch. */
|
|
466
|
+
export type PrintPreflight = { finishedJob?: string; serial: string; model: string; notices: string[] };
|
|
467
|
+
|
|
464
468
|
export class BambuImplementation {
|
|
465
469
|
private printerStore: BambuClientStore;
|
|
466
470
|
private checkedJobs = new Map<string, { remotePath: string; requirements: PrinterStateRequirements }>();
|
|
@@ -481,25 +485,41 @@ export class BambuImplementation {
|
|
|
481
485
|
return status.raw.gcode_state === "FINISH" ? JSON.stringify([status.raw.gcode_file, status.raw.subtask_name, status.raw.task_id, status.raw.subtask_id]) : undefined;
|
|
482
486
|
}
|
|
483
487
|
|
|
484
|
-
|
|
488
|
+
/**
|
|
489
|
+
* The human print confirmation. Its result goes to assertBedClearance at
|
|
490
|
+
* every later state check of the same print, which stops the print if the
|
|
491
|
+
* situation the human confirmed has changed.
|
|
492
|
+
*/
|
|
493
|
+
async confirmPrintPreflight(serial: string, status: any, inspection: Awaited<ReturnType<typeof inspectPrintFile>>): Promise<PrintPreflight> {
|
|
485
494
|
const finishedJob = this.finishedJobIdentity(status);
|
|
486
495
|
const cleared = this.clearedErrors.get(serial);
|
|
496
|
+
const notices = hiddenHmsNotices(status?.raw, serial, inspection.model);
|
|
487
497
|
await this.confirmHardwareAction(
|
|
488
498
|
`Start a checked print on ${inspection.model.toUpperCase()} (${serial})? Nozzles: ${inspection.nozzleDiameters.join(", ")} mm. ` +
|
|
489
499
|
`Materials: ${inspection.materials.join(", ")}. Peak targets: nozzle ${inspection.maxNozzleTemperature}°C, bed ${inspection.maxBedTemperature}°C, chamber ${inspection.maxChamberTemperature}°C. ` +
|
|
490
500
|
`File SHA-256: ${inspection.sha256}. Confirm the physical spool labels and that the build plate is clear.` +
|
|
491
501
|
(finishedJob !== undefined ? " The printer reports FINISH: remove the previous part and debris before confirming." : "") +
|
|
492
|
-
(cleared ? ` Previously cleared hardware codes: ${cleared.join(", ")}. Confirm their physical causes have been resolved.` : "")
|
|
502
|
+
(cleared ? ` Previously cleared hardware codes: ${cleared.map(describeHmsKey).join(", ")}. Confirm their physical causes have been resolved.` : "") +
|
|
503
|
+
(notices.length ? ` The printer also reports ${notices.join(", ")}, which Bambu lists for this model without text and never shows as a fault; ` +
|
|
504
|
+
`${notices.length === 1 ? "it does" : "they do"} not block printing.` : ""),
|
|
493
505
|
finishedJob !== undefined || !!cleared
|
|
494
506
|
);
|
|
495
|
-
return finishedJob;
|
|
507
|
+
return { finishedJob, serial, model: inspection.model, notices };
|
|
496
508
|
}
|
|
497
509
|
|
|
498
|
-
|
|
510
|
+
/** Stop a print whose confirmed situation changed: a newly finished job, or a hidden HMS notice the preflight did not name. */
|
|
511
|
+
assertBedClearance(status: any, preflight: PrintPreflight): void {
|
|
499
512
|
const current = this.finishedJobIdentity(status);
|
|
500
|
-
if (current !== undefined && current !==
|
|
513
|
+
if (current !== undefined && current !== preflight.finishedJob) {
|
|
501
514
|
throw new Error("Printer reports a newly finished job. Confirm that its part and debris have been removed before retrying the print.");
|
|
502
515
|
}
|
|
516
|
+
// An X2D raises its hidden camera notice about 15 seconds after boot, so it
|
|
517
|
+
// can appear after the human confirmed. Never pass a notice nobody was told about.
|
|
518
|
+
const unnamed = hiddenHmsNotices(status?.raw, preflight.serial, preflight.model).filter(notice => !preflight.notices.includes(notice));
|
|
519
|
+
if (unnamed.length) {
|
|
520
|
+
throw new Error(`Printer reported ${unnamed.join(", ")} after the print confirmation. ` +
|
|
521
|
+
"Bambu hides it and it does not block printing, but it was not named when you confirmed; start the print again to review it.");
|
|
522
|
+
}
|
|
503
523
|
}
|
|
504
524
|
|
|
505
525
|
private async getPrinter(host: string, serial: string, token: string): Promise<BambuClient> {
|
|
@@ -728,15 +748,20 @@ export class BambuImplementation {
|
|
|
728
748
|
// `plate_N.json.filament_ids` -- and expand to a full project-level
|
|
729
749
|
// array here. `amsMapping` is the raw escape hatch (takes precedence
|
|
730
750
|
// when both are supplied).
|
|
751
|
+
// Dual-nozzle printers address two external spools: 254 feeds the left/deputy
|
|
752
|
+
// nozzle and 255 the right/main nozzle (Bambu Studio VIRTUAL_TRAY_DEPUTY_ID /
|
|
753
|
+
// VIRTUAL_TRAY_MAIN_ID). Single-nozzle printers keep 254 as the only external tray.
|
|
754
|
+
const dualNozzle = isDualNozzleModel(model);
|
|
755
|
+
const externalTrays = dualNozzle ? [254, 255] : [254];
|
|
731
756
|
const validateTrayValue = (v: unknown, label: string): void => {
|
|
732
757
|
if (
|
|
733
758
|
!Number.isInteger(v) ||
|
|
734
759
|
(v as number) < -1 ||
|
|
735
760
|
((v as number) > 15 && (v as number) < 128) ||
|
|
736
|
-
(v as number) > 254
|
|
761
|
+
(v as number) > (dualNozzle ? 255 : 254)
|
|
737
762
|
) {
|
|
738
763
|
throw new Error(
|
|
739
|
-
`${label} values must be integers in
|
|
764
|
+
`${label} values must be integers in ${describeAmsTrayRange({ dualNozzle })}; got ${v}`
|
|
740
765
|
);
|
|
741
766
|
}
|
|
742
767
|
};
|
|
@@ -775,7 +800,7 @@ export class BambuImplementation {
|
|
|
775
800
|
);
|
|
776
801
|
baseMapping = Array<number>(projectLen).fill(-1);
|
|
777
802
|
positions.forEach((pos, i) => {
|
|
778
|
-
baseMapping[pos] = options.useAMS === false ?
|
|
803
|
+
baseMapping[pos] = options.useAMS === false ? externalTrayForNozzle(inspection.filamentNozzleIndices?.[pos], dualNozzle) : i;
|
|
779
804
|
});
|
|
780
805
|
}
|
|
781
806
|
|
|
@@ -786,12 +811,7 @@ export class BambuImplementation {
|
|
|
786
811
|
amsMapping = Array.from({ length: projLen }, (_, i) =>
|
|
787
812
|
i < baseMapping.length ? baseMapping[i] : -1
|
|
788
813
|
);
|
|
789
|
-
amsMapping2 = amsMapping.map((v) =>
|
|
790
|
-
if (v < 0 || v === 255) return { ams_id: 255, slot_id: 255 };
|
|
791
|
-
if (v === 254) return { ams_id: 254, slot_id: 254 };
|
|
792
|
-
if (v >= 128) return { ams_id: 128, slot_id: v - 128 };
|
|
793
|
-
return { ams_id: Math.floor(v / 4), slot_id: v % 4 };
|
|
794
|
-
});
|
|
814
|
+
amsMapping2 = amsMapping.map((v) => bridgeAmsMapping2Entry(v, dualNozzle));
|
|
795
815
|
} else {
|
|
796
816
|
amsMapping = Array.from({ length: Math.max(5, baseMapping.length, projectMetadata.projectFilamentCount) }, (_, i) =>
|
|
797
817
|
i < baseMapping.length ? baseMapping[i] : -1
|
|
@@ -803,8 +823,8 @@ export class BambuImplementation {
|
|
|
803
823
|
if (baseMapping[position] === undefined || baseMapping[position] < 0) {
|
|
804
824
|
throw new Error(`Missing physical filament mapping for project filament ${position}.`);
|
|
805
825
|
}
|
|
806
|
-
if (options.useAMS === false && baseMapping[position]
|
|
807
|
-
throw new Error(
|
|
826
|
+
if (options.useAMS === false && !externalTrays.includes(baseMapping[position])) {
|
|
827
|
+
throw new Error(`External-spool printing requires external spool mapping (${externalTrays.join(" or ")}).`);
|
|
808
828
|
}
|
|
809
829
|
}
|
|
810
830
|
const legacyContainer = options.filePath.toLowerCase().endsWith(".gcode.3mf") && !usesH2ProjectFile && !isA1;
|
|
@@ -879,7 +899,7 @@ export class BambuImplementation {
|
|
|
879
899
|
project_id: submissionId,
|
|
880
900
|
subtask_id: submissionId,
|
|
881
901
|
task_id: submissionId,
|
|
882
|
-
ams_mapping: amsMapping,
|
|
902
|
+
ams_mapping: amsMapping.map(value => bridgeAmsMappingValue(value, dualNozzle)),
|
|
883
903
|
ams_mapping2: amsMapping2,
|
|
884
904
|
},
|
|
885
905
|
};
|
|
@@ -944,6 +964,21 @@ export class BambuImplementation {
|
|
|
944
964
|
}
|
|
945
965
|
}
|
|
946
966
|
|
|
967
|
+
/** Raw MQTT stop for routes that already ran the shared cancellation steps. */
|
|
968
|
+
async sendStopOverMqtt(host: string, serial: string, token: string): Promise<any> {
|
|
969
|
+
const printer = await this.getPrinter(host, serial, token);
|
|
970
|
+
await invokeWithoutAck(printer, new UpdateStateCommand({ state: "stop" }));
|
|
971
|
+
return { status: "success", message: "Cancel command sent over MQTT." };
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/** Raw MQTT heater-off; switching off needs no material or model declaration. */
|
|
975
|
+
async sendHeaterOffOverMqtt(host: string, serial: string, token: string, heater: "bed" | "nozzle"): Promise<any> {
|
|
976
|
+
const printer = await this.getPrinter(host, serial, token);
|
|
977
|
+
const gcode = `${heater === "bed" ? "M140" : "M104"} S0`;
|
|
978
|
+
await invokeWithoutAck(printer, new GCodeLineCommand({ gcodes: [gcode] }));
|
|
979
|
+
return { status: "success", message: `Heater-off sent over MQTT for ${heater}.`, command: gcode };
|
|
980
|
+
}
|
|
981
|
+
|
|
947
982
|
async pauseJob(host: string, serial: string, token: string, dispatch?: () => Promise<any>): Promise<any> {
|
|
948
983
|
const printer = dispatch ? undefined : await this.getPrinter(host, serial, token);
|
|
949
984
|
try {
|
|
@@ -982,10 +1017,12 @@ export class BambuImplementation {
|
|
|
982
1017
|
return withPrinterOperation(host, serial, async assertActive => {
|
|
983
1018
|
const codesFor = (status: any): string[] => {
|
|
984
1019
|
if (!Array.isArray(status.raw?.hms) || status.raw?.print_error === undefined) throw new Error("Fresh error codes are required before clearing hardware errors.");
|
|
985
|
-
|
|
1020
|
+
// Hidden notices never block and survive clean_print_error, so they are not errors to clear or re-acknowledge.
|
|
1021
|
+
const hidden = new Set(hiddenHmsKeys(status.raw, serial, String(status.model ?? "")));
|
|
1022
|
+
return [`print_error:${status.raw.print_error}`, ...status.raw.hms.map(reportedHmsKey).filter((key: string) => !hidden.has(key))].sort();
|
|
986
1023
|
};
|
|
987
1024
|
const codes = codesFor(await this.getSafetyStatus(host, serial, token));
|
|
988
|
-
await this.confirmHardwareAction(`Clear reported hardware errors on ${serial}: ${codes.join(", ")}? Inspect the printer and resolve the physical cause first. Confirm only after removing obstructions and correcting the fault.`, true);
|
|
1025
|
+
await this.confirmHardwareAction(`Clear reported hardware errors on ${serial}: ${codes.map(describeHmsKey).join(", ")}? Inspect the printer and resolve the physical cause first. Confirm only after removing obstructions and correcting the fault.`, true);
|
|
989
1026
|
const beforeDispatch = async () => {
|
|
990
1027
|
assertActive();
|
|
991
1028
|
const current = codesFor(await this.getSafetyStatus(host, serial, token));
|
|
@@ -1141,9 +1178,12 @@ export class BambuImplementation {
|
|
|
1141
1178
|
}
|
|
1142
1179
|
const printer = dispatch ? undefined : await this.getPrinter(host, serial, token);
|
|
1143
1180
|
assertActive();
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1181
|
+
const dispatched = dispatch ? await dispatch(heater, targetTemperature, assertActive, beforeDispatch) : undefined;
|
|
1182
|
+
if (!dispatch) await invokeWithoutAck(printer!, new GCodeLineCommand({ gcodes: [gcode] }));
|
|
1183
|
+
// Keep route details from a dual-route Linux heater-off.
|
|
1184
|
+
const routeDetails = dispatched && typeof dispatched === "object" && "routes" in dispatched
|
|
1185
|
+
? { routes: dispatched.routes, native_error: dispatched.native_error, mqtt_error: dispatched.mqtt_error } : {};
|
|
1186
|
+
return { status: "success", message: `Temperature command sent for ${normalizedComponent}.`, command: gcode, ...routeDetails };
|
|
1147
1187
|
};
|
|
1148
1188
|
if (targetTemperature === 0) {
|
|
1149
1189
|
cancelPendingPrinterOperations(host, serial);
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// GENERATED by scripts/generate-hidden-hms.mjs from Bambu Studio v02.08.02.61
|
|
2
|
+
// (commit 926a7192574bcb9b3a732e1ec59a46d79cb45466, resources/hms/hms_en_<serial prefix>.json).
|
|
3
|
+
// Codes Bambu lists for a serial prefix with empty text in every entry. Do not edit by hand.
|
|
4
|
+
export const HIDDEN_HMS_SOURCE = {
|
|
5
|
+
studioTag: "v02.08.02.61",
|
|
6
|
+
studioCommit: "926a7192574bcb9b3a732e1ec59a46d79cb45466",
|
|
7
|
+
/** Per table: Bambu's device_hms version and the SHA-256 of the English file used. */
|
|
8
|
+
tables: {
|
|
9
|
+
"093": { version: 202508191204, sha256: "7801fd6c355854b62ecb62ee32727b4e5dcca908841dbcfd5e5de45b3dfd1c25" },
|
|
10
|
+
"094": { version: 202503231214, sha256: "047c15e2140738ec08258b74536e6cb996cddf0c586c5c39e2318e54125a9e76" },
|
|
11
|
+
"20P": { version: 202511162200, sha256: "e4f067d027b9bcbb9bbf6cb90f41f11130cf4d83dffd3dee101db7826dc808c3" },
|
|
12
|
+
"22E": { version: 202510142200, sha256: "87426021aea2bd4e8d721e46e1c86808fd7c6fc06f52baf6d952ead9e8d14d1e" },
|
|
13
|
+
"239": { version: 202506171403, sha256: "c1d23d5302756d57df5cb9bc795d95fc54ef1db7565dca1f04b69a024886426a" },
|
|
14
|
+
"31B": { version: 202511162200, sha256: "dcf63657bff5451f0715ea3c032efe482279bc8cbc28cb9b8ae51c7354826574" },
|
|
15
|
+
},
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export const HIDDEN_HMS: Readonly<Record<string, readonly string[]>> = {
|
|
19
|
+
"093": [
|
|
20
|
+
"0300180000000000",
|
|
21
|
+
"050001000001000A",
|
|
22
|
+
"0500010000020003",
|
|
23
|
+
"0500010000020008",
|
|
24
|
+
"0500010000020009",
|
|
25
|
+
"0500010000030009",
|
|
26
|
+
"0500030000010053",
|
|
27
|
+
"0500030000020010",
|
|
28
|
+
"0500030000020011",
|
|
29
|
+
"0500030000020012",
|
|
30
|
+
"0500030000020013",
|
|
31
|
+
"0500030000020014",
|
|
32
|
+
"0500030000020015",
|
|
33
|
+
"0500030000020016",
|
|
34
|
+
"0500030000020017",
|
|
35
|
+
"0500030000020018",
|
|
36
|
+
"0500030000030007",
|
|
37
|
+
"0500040000020020",
|
|
38
|
+
"0500050000010008",
|
|
39
|
+
"0500050000010009",
|
|
40
|
+
"050005000001000A",
|
|
41
|
+
"050005000001000B",
|
|
42
|
+
"050005000001000C",
|
|
43
|
+
"0500050000020030",
|
|
44
|
+
"0500060000020005",
|
|
45
|
+
"0500060000020007",
|
|
46
|
+
"0500060000020008",
|
|
47
|
+
"0500060000020011",
|
|
48
|
+
"0500060000020012",
|
|
49
|
+
"0500060000020013",
|
|
50
|
+
"0500060000020014",
|
|
51
|
+
"0500060000020021",
|
|
52
|
+
"0500060000020022",
|
|
53
|
+
"0500060000020023",
|
|
54
|
+
"0500060000020024",
|
|
55
|
+
"0500060000020041",
|
|
56
|
+
"0500060000020043",
|
|
57
|
+
"0500060000020044",
|
|
58
|
+
"0500060000020051",
|
|
59
|
+
"0500060000020053",
|
|
60
|
+
"0500060000020054",
|
|
61
|
+
"0500060000020061",
|
|
62
|
+
"0500060000020062",
|
|
63
|
+
"05000600000200A0",
|
|
64
|
+
"05000600000200A1",
|
|
65
|
+
"0C00010000010018",
|
|
66
|
+
"0C0001000002000E",
|
|
67
|
+
"0C00010000020015",
|
|
68
|
+
"0C00010000020016",
|
|
69
|
+
"0C00010000020019",
|
|
70
|
+
"0C0001000002001A",
|
|
71
|
+
"0C0001000002001B",
|
|
72
|
+
"0C00040000020006",
|
|
73
|
+
],
|
|
74
|
+
"094": [
|
|
75
|
+
"0300180000000000",
|
|
76
|
+
"0300260000000000",
|
|
77
|
+
"050001000001000A",
|
|
78
|
+
"0500010000020003",
|
|
79
|
+
"0500010000020008",
|
|
80
|
+
"0500010000020009",
|
|
81
|
+
"0500010000030009",
|
|
82
|
+
"0500030000020010",
|
|
83
|
+
"0500030000020011",
|
|
84
|
+
"0500030000020012",
|
|
85
|
+
"0500030000020013",
|
|
86
|
+
"0500030000020014",
|
|
87
|
+
"0500030000020015",
|
|
88
|
+
"0500030000020016",
|
|
89
|
+
"0500030000020017",
|
|
90
|
+
"0500030000020018",
|
|
91
|
+
"0500030000030007",
|
|
92
|
+
"0500040000020020",
|
|
93
|
+
"0500050000010008",
|
|
94
|
+
"0500050000010009",
|
|
95
|
+
"050005000001000A",
|
|
96
|
+
"050005000001000B",
|
|
97
|
+
"050005000001000C",
|
|
98
|
+
"0500060000020001",
|
|
99
|
+
"0500060000020002",
|
|
100
|
+
"0500060000020003",
|
|
101
|
+
"0500060000020005",
|
|
102
|
+
"0500060000020006",
|
|
103
|
+
"0500060000020007",
|
|
104
|
+
"0500060000020008",
|
|
105
|
+
"0500060000020011",
|
|
106
|
+
"0500060000020012",
|
|
107
|
+
"0500060000020013",
|
|
108
|
+
"0500060000020014",
|
|
109
|
+
"0500060000020021",
|
|
110
|
+
"0500060000020022",
|
|
111
|
+
"0500060000020023",
|
|
112
|
+
"0500060000020024",
|
|
113
|
+
"0500060000020041",
|
|
114
|
+
"0500060000020042",
|
|
115
|
+
"0500060000020043",
|
|
116
|
+
"0500060000020044",
|
|
117
|
+
"0500060000020051",
|
|
118
|
+
"0500060000020052",
|
|
119
|
+
"0500060000020053",
|
|
120
|
+
"0500060000020054",
|
|
121
|
+
"0500060000020061",
|
|
122
|
+
"0500060000020062",
|
|
123
|
+
"05000600000200A0",
|
|
124
|
+
"05000600000200A1",
|
|
125
|
+
"0C00010000020015",
|
|
126
|
+
"0C00010000020016",
|
|
127
|
+
"0C00010000020019",
|
|
128
|
+
"0C0001000002001A",
|
|
129
|
+
"0C0001000002001B",
|
|
130
|
+
"0C00040000020006",
|
|
131
|
+
],
|
|
132
|
+
"20P": [
|
|
133
|
+
"0300180000000000",
|
|
134
|
+
"0300260000000000",
|
|
135
|
+
"050001000001000A",
|
|
136
|
+
"0500010000020003",
|
|
137
|
+
"0500010000020008",
|
|
138
|
+
"0500010000020009",
|
|
139
|
+
"050001000002000B",
|
|
140
|
+
"050001000002000F",
|
|
141
|
+
"0500010000020010",
|
|
142
|
+
"0500010000020011",
|
|
143
|
+
"0500010000030009",
|
|
144
|
+
"0500020000030009",
|
|
145
|
+
"050002000003000A",
|
|
146
|
+
"050002000003000B",
|
|
147
|
+
"050002000003000C",
|
|
148
|
+
"0500030000010053",
|
|
149
|
+
"0500030000020010",
|
|
150
|
+
"0500030000020011",
|
|
151
|
+
"0500030000020012",
|
|
152
|
+
"0500030000020013",
|
|
153
|
+
"0500030000020014",
|
|
154
|
+
"0500030000020015",
|
|
155
|
+
"0500030000020016",
|
|
156
|
+
"0500030000020017",
|
|
157
|
+
"0500030000020018",
|
|
158
|
+
"0500030000020019",
|
|
159
|
+
"0500030000020050",
|
|
160
|
+
"0500030000020051",
|
|
161
|
+
"0500030000020052",
|
|
162
|
+
"0500030000020053",
|
|
163
|
+
"0500030000020054",
|
|
164
|
+
"0500030000030007",
|
|
165
|
+
"0500040000010056",
|
|
166
|
+
"0500040000010060",
|
|
167
|
+
"0500040000010061",
|
|
168
|
+
"0500040000020020",
|
|
169
|
+
"0500050000010008",
|
|
170
|
+
"0500050000010009",
|
|
171
|
+
"050005000001000A",
|
|
172
|
+
"050005000001000B",
|
|
173
|
+
"050005000001000C",
|
|
174
|
+
"0500050000020023",
|
|
175
|
+
"0500050000020030",
|
|
176
|
+
"0500060000020005",
|
|
177
|
+
"0500060000020006",
|
|
178
|
+
"0500060000020007",
|
|
179
|
+
"0500060000020008",
|
|
180
|
+
"0500060000020009",
|
|
181
|
+
"0500060000020011",
|
|
182
|
+
"0500060000020012",
|
|
183
|
+
"0500060000020013",
|
|
184
|
+
"0500060000020014",
|
|
185
|
+
"0500060000020015",
|
|
186
|
+
"0500060000020021",
|
|
187
|
+
"0500060000020022",
|
|
188
|
+
"0500060000020023",
|
|
189
|
+
"0500060000020024",
|
|
190
|
+
"0500060000020025",
|
|
191
|
+
"0500060000020035",
|
|
192
|
+
"0500060000020041",
|
|
193
|
+
"0500060000020042",
|
|
194
|
+
"0500060000020044",
|
|
195
|
+
"0500060000020045",
|
|
196
|
+
"0500060000020051",
|
|
197
|
+
"0500060000020052",
|
|
198
|
+
"0500060000020054",
|
|
199
|
+
"0500060000020061",
|
|
200
|
+
"0500060000020062",
|
|
201
|
+
"0500060000020070",
|
|
202
|
+
"0500060000020071",
|
|
203
|
+
"05000600000200A0",
|
|
204
|
+
"05000600000200A1",
|
|
205
|
+
"0500070000020001",
|
|
206
|
+
"0500070000020002",
|
|
207
|
+
"0500070000020003",
|
|
208
|
+
"0500070000020101",
|
|
209
|
+
"0500070000020201",
|
|
210
|
+
"0503000000030027",
|
|
211
|
+
"0503000000030028",
|
|
212
|
+
"0503000000030029",
|
|
213
|
+
"0C00010000010018",
|
|
214
|
+
"0C0001000002000E",
|
|
215
|
+
"0C00010000020015",
|
|
216
|
+
"0C00010000020016",
|
|
217
|
+
"0C00010000020019",
|
|
218
|
+
"0C0001000002001A",
|
|
219
|
+
"0C0001000002001B",
|
|
220
|
+
"0C0003000003001A",
|
|
221
|
+
"0C00040000020006",
|
|
222
|
+
],
|
|
223
|
+
"22E": [
|
|
224
|
+
"0300180000000000",
|
|
225
|
+
"050001000001000A",
|
|
226
|
+
"0500010000020003",
|
|
227
|
+
"0500010000020008",
|
|
228
|
+
"0500010000020009",
|
|
229
|
+
"0500010000030009",
|
|
230
|
+
"0500030000010053",
|
|
231
|
+
"0500030000020010",
|
|
232
|
+
"0500030000020011",
|
|
233
|
+
"0500030000020012",
|
|
234
|
+
"0500030000020013",
|
|
235
|
+
"0500030000020014",
|
|
236
|
+
"0500030000020015",
|
|
237
|
+
"0500030000020016",
|
|
238
|
+
"0500030000020017",
|
|
239
|
+
"0500030000020018",
|
|
240
|
+
"0500030000030007",
|
|
241
|
+
"0500040000010056",
|
|
242
|
+
"0500040000020020",
|
|
243
|
+
"0500050000010008",
|
|
244
|
+
"0500050000010009",
|
|
245
|
+
"050005000001000A",
|
|
246
|
+
"050005000001000B",
|
|
247
|
+
"050005000001000C",
|
|
248
|
+
"0500050000020030",
|
|
249
|
+
"0500060000020005",
|
|
250
|
+
"0500060000020007",
|
|
251
|
+
"0500060000020008",
|
|
252
|
+
"0500060000020011",
|
|
253
|
+
"0500060000020012",
|
|
254
|
+
"0500060000020013",
|
|
255
|
+
"0500060000020014",
|
|
256
|
+
"0500060000020021",
|
|
257
|
+
"0500060000020022",
|
|
258
|
+
"0500060000020023",
|
|
259
|
+
"0500060000020024",
|
|
260
|
+
"0500060000020044",
|
|
261
|
+
"0500060000020054",
|
|
262
|
+
"0500060000020061",
|
|
263
|
+
"0500060000020062",
|
|
264
|
+
"05000600000200A0",
|
|
265
|
+
"05000600000200A1",
|
|
266
|
+
"0500070000020001",
|
|
267
|
+
"0500070000020002",
|
|
268
|
+
"0500070000020003",
|
|
269
|
+
"0500070000020101",
|
|
270
|
+
"0500070000020201",
|
|
271
|
+
"0503000000030027",
|
|
272
|
+
"0503000000030028",
|
|
273
|
+
"0503000000030029",
|
|
274
|
+
"0C00010000010018",
|
|
275
|
+
"0C0001000002000E",
|
|
276
|
+
"0C00010000020015",
|
|
277
|
+
"0C00010000020016",
|
|
278
|
+
"0C00010000020019",
|
|
279
|
+
"0C0001000002001A",
|
|
280
|
+
"0C0001000002001B",
|
|
281
|
+
"0C00040000020006",
|
|
282
|
+
],
|
|
283
|
+
"239": [
|
|
284
|
+
"0300180000000000",
|
|
285
|
+
"0300260000000000",
|
|
286
|
+
"0500010000020003",
|
|
287
|
+
"0500010000020008",
|
|
288
|
+
"0500010000020009",
|
|
289
|
+
"0500010000030009",
|
|
290
|
+
"0500030000020010",
|
|
291
|
+
"0500030000020011",
|
|
292
|
+
"0500030000020012",
|
|
293
|
+
"0500030000020013",
|
|
294
|
+
"0500030000020014",
|
|
295
|
+
"0500030000020015",
|
|
296
|
+
"0500030000020016",
|
|
297
|
+
"0500030000020017",
|
|
298
|
+
"0500030000020018",
|
|
299
|
+
"0500030000030007",
|
|
300
|
+
"0500040000020020",
|
|
301
|
+
"0500050000010008",
|
|
302
|
+
"0500050000010009",
|
|
303
|
+
"050005000001000A",
|
|
304
|
+
"050005000001000B",
|
|
305
|
+
"050005000001000C",
|
|
306
|
+
"0500060000020005",
|
|
307
|
+
"0500060000020006",
|
|
308
|
+
"0500060000020007",
|
|
309
|
+
"0500060000020008",
|
|
310
|
+
"0500060000020011",
|
|
311
|
+
"0500060000020012",
|
|
312
|
+
"0500060000020013",
|
|
313
|
+
"0500060000020014",
|
|
314
|
+
"0500060000020021",
|
|
315
|
+
"0500060000020022",
|
|
316
|
+
"0500060000020023",
|
|
317
|
+
"0500060000020024",
|
|
318
|
+
"0500060000020041",
|
|
319
|
+
"0500060000020042",
|
|
320
|
+
"0500060000020043",
|
|
321
|
+
"0500060000020044",
|
|
322
|
+
"0500060000020051",
|
|
323
|
+
"0500060000020052",
|
|
324
|
+
"0500060000020053",
|
|
325
|
+
"0500060000020054",
|
|
326
|
+
"0500060000020061",
|
|
327
|
+
"0500060000020062",
|
|
328
|
+
"05000600000200A0",
|
|
329
|
+
"05000600000200A1",
|
|
330
|
+
"0C00010000020015",
|
|
331
|
+
"0C00010000020016",
|
|
332
|
+
"0C00010000020019",
|
|
333
|
+
"0C0001000002001A",
|
|
334
|
+
"0C0001000002001B",
|
|
335
|
+
"0C00040000020006",
|
|
336
|
+
],
|
|
337
|
+
"31B": [
|
|
338
|
+
"0300180000000000",
|
|
339
|
+
"0300260000000000",
|
|
340
|
+
"050001000001000A",
|
|
341
|
+
"0500010000020003",
|
|
342
|
+
"0500010000020008",
|
|
343
|
+
"0500010000020009",
|
|
344
|
+
"0500010000030009",
|
|
345
|
+
"0500030000010053",
|
|
346
|
+
"0500030000020010",
|
|
347
|
+
"0500030000020011",
|
|
348
|
+
"0500030000020012",
|
|
349
|
+
"0500030000020013",
|
|
350
|
+
"0500030000020014",
|
|
351
|
+
"0500030000020015",
|
|
352
|
+
"0500030000020016",
|
|
353
|
+
"0500030000020017",
|
|
354
|
+
"0500030000020018",
|
|
355
|
+
"0500030000030007",
|
|
356
|
+
"0500040000010056",
|
|
357
|
+
"0500040000020020",
|
|
358
|
+
"0500050000010008",
|
|
359
|
+
"0500050000010009",
|
|
360
|
+
"050005000001000A",
|
|
361
|
+
"050005000001000B",
|
|
362
|
+
"050005000001000C",
|
|
363
|
+
"0500050000020030",
|
|
364
|
+
"0500060000020005",
|
|
365
|
+
"0500060000020006",
|
|
366
|
+
"0500060000020007",
|
|
367
|
+
"0500060000020008",
|
|
368
|
+
"0500060000020011",
|
|
369
|
+
"0500060000020012",
|
|
370
|
+
"0500060000020013",
|
|
371
|
+
"0500060000020014",
|
|
372
|
+
"0500060000020021",
|
|
373
|
+
"0500060000020022",
|
|
374
|
+
"0500060000020023",
|
|
375
|
+
"0500060000020024",
|
|
376
|
+
"0500060000020041",
|
|
377
|
+
"0500060000020042",
|
|
378
|
+
"0500060000020043",
|
|
379
|
+
"0500060000020044",
|
|
380
|
+
"0500060000020051",
|
|
381
|
+
"0500060000020052",
|
|
382
|
+
"0500060000020053",
|
|
383
|
+
"0500060000020054",
|
|
384
|
+
"0500060000020061",
|
|
385
|
+
"0500060000020062",
|
|
386
|
+
"05000600000200A0",
|
|
387
|
+
"05000600000200A1",
|
|
388
|
+
"0500070000020001",
|
|
389
|
+
"0500070000020002",
|
|
390
|
+
"0500070000020003",
|
|
391
|
+
"0500070000020101",
|
|
392
|
+
"0500070000020201",
|
|
393
|
+
"0503000000030027",
|
|
394
|
+
"0503000000030028",
|
|
395
|
+
"0503000000030029",
|
|
396
|
+
"0C00010000010018",
|
|
397
|
+
"0C0001000002000E",
|
|
398
|
+
"0C00010000020015",
|
|
399
|
+
"0C00010000020016",
|
|
400
|
+
"0C00010000020019",
|
|
401
|
+
"0C0001000002001A",
|
|
402
|
+
"0C0001000002001B",
|
|
403
|
+
"0C0003000003001A",
|
|
404
|
+
"0C00040000020006",
|
|
405
|
+
],
|
|
406
|
+
};
|
package/src/safety/limits.ts
CHANGED
|
@@ -31,6 +31,15 @@ export const MACHINE_LIMITS: Readonly<Record<string, {nozzle: number; bed: numbe
|
|
|
31
31
|
x2d: {nozzle:300,bed:120,chamber:65,volume:[256,256,260]},
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/** Printers with two physical nozzles. Bambu Studio numbers the right/main nozzle 0
|
|
35
|
+
* (MAIN_EXTRUDER_ID) and the left/deputy nozzle 1 (DEPUTY_EXTRUDER_ID), and reports one
|
|
36
|
+
* virtual external-spool slot per nozzle: 255 (VIRTUAL_TRAY_MAIN_ID) and 254
|
|
37
|
+
* (VIRTUAL_TRAY_DEPUTY_ID). The single-nozzle H2S is deliberately absent. */
|
|
38
|
+
export const DUAL_NOZZLE_MODELS: ReadonlySet<string> = new Set(['h2d','h2dpro','h2c','x2d']);
|
|
39
|
+
export function isDualNozzleModel(model: unknown): boolean {
|
|
40
|
+
const normalized = normalizeModel(model);
|
|
41
|
+
return normalized !== undefined && DUAL_NOZZLE_MODELS.has(normalized);
|
|
42
|
+
}
|
|
34
43
|
export function normalizeModel(value: unknown): string | undefined {
|
|
35
44
|
if (typeof value !== 'string') return undefined;
|
|
36
45
|
let model = value.trim().toLowerCase().replace(/^bambu\s*(?:lab\s*)?/, '').replace(/[\s_-]/g, '');
|