bambu-printer-mcp 1.1.28 → 1.1.30
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 +4 -2
- package/README.md +5 -3
- package/dist/ams-mapping.d.ts +18 -2
- package/dist/ams-mapping.js +36 -5
- package/dist/index.js +81 -49
- package/dist/printers/bambu.d.ts +3 -2
- package/dist/printers/bambu.js +55 -20
- 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 +36 -6
- package/dist/safety/printer-state.d.ts +2 -0
- package/dist/safety/printer-state.js +35 -9
- package/dist/slicer/remote-slicer.d.ts +12 -0
- package/dist/slicer/remote-slicer.js +240 -0
- package/dist/stl/stl-manipulator.d.ts +1 -1
- package/dist/stl/stl-manipulator.js +70 -42
- package/package.json +1 -1
- package/src/ams-mapping.ts +38 -5
- package/src/index.ts +78 -47
- package/src/printers/bambu.ts +56 -17
- package/src/safety/limits.ts +9 -0
- package/src/safety/print-file.ts +34 -6
- package/src/safety/printer-state.ts +32 -9
- package/src/slicer/remote-slicer.ts +216 -0
- package/src/stl/stl-manipulator.ts +68 -43
package/CONTRIBUTORS.md
CHANGED
|
@@ -6,12 +6,14 @@ Thank you to everyone who builds, tests, reports problems, and shares real print
|
|
|
6
6
|
|
|
7
7
|
| Contributor | Contribution |
|
|
8
8
|
| --- | --- |
|
|
9
|
-
| [mygrexit](https://github.com/mygrexit) | Contributes pinned x86-64 Linux X2D native setup, emergency routing and one-shot cleanup in [#56](https://github.com/DMontgomery40/bambu-printer-mcp/pull/56), checked Orca/time-lapse G-code forms in [#57](https://github.com/DMontgomery40/bambu-printer-mcp/pull/57), and model-specific hidden-HMS notices in [#58](https://github.com/DMontgomery40/bambu-printer-mcp/pull/58). Supplies regressions, review corrections, and contributor-reported Debian/X2D read-only, heater-off and print-start evidence in [#55](https://github.com/DMontgomery40/bambu-printer-mcp/issues/55); maintainer validation is offline and mocked. |
|
|
9
|
+
| [mygrexit](https://github.com/mygrexit) | Contributes pinned x86-64 Linux X2D native setup, emergency routing and one-shot cleanup in [#56](https://github.com/DMontgomery40/bambu-printer-mcp/pull/56), checked Orca/time-lapse G-code forms in [#57](https://github.com/DMontgomery40/bambu-printer-mcp/pull/57), and model-specific hidden-HMS notices in [#58](https://github.com/DMontgomery40/bambu-printer-mcp/pull/58). Supplies regressions, review corrections, and contributor-reported Debian/X2D read-only, heater-off and print-start evidence in [#55](https://github.com/DMontgomery40/bambu-printer-mcp/issues/55); maintainer validation is offline and mocked. Also reports and fixes hanging Linux X2D stop/heater-off calls in [#61](https://github.com/DMontgomery40/bambu-printer-mcp/pull/61), with never-connecting transport and helper-exit regressions; the release adds protection against expired commands dispatching after reconnection. |
|
|
10
10
|
| [patrikmadoran-cyber](https://github.com/patrikmadoran-cyber) | Adds explicit Standard/High Flow nozzle selection across slicing and print auto-slicing in [#54](https://github.com/DMontgomery40/bambu-printer-mcp/pull/54), with unsupported-variant regressions and real Windows BambuStudio 02.06.00.51 H2S 0.6 slicing evidence. No physical print is claimed. |
|
|
11
11
|
| [Patrick Juchli (@patrickjuchli)](https://github.com/patrickjuchli) | Maintains basic-ftp and supplies the 6.2.3 fixes for rejected-command recovery, asynchronous credential-error crashes, and password redaction used by direct FTPS transfers. |
|
|
12
12
|
| [Aleksei Popovich (alexpapay)](https://github.com/alexpapay) | Fixes cross-platform AMS profile resolution and packed P2S chamber status in [#43](https://github.com/DMontgomery40/bambu-printer-mcp/pull/43), contributes regressions, and supplies read-only Windows P2S/AMS 2 Pro status and local slicing evidence. No physical print is claimed for this contribution. |
|
|
13
13
|
| [Adam (@adamkaplan)](https://github.com/adamkaplan) | Identifies the reversed H2C/H2D Pro serial prefixes in [#47](https://github.com/DMontgomery40/bambu-printer-mcp/issues/47), verifies corrected H2C status locally, and isolates the official H2C startup commands and config-block marker failure in [#48](https://github.com/DMontgomery40/bambu-printer-mcp/issues/48). Provides exact Bambu Studio version/template hash and unsafe-file reproductions; maintainer validation is offline and mocked. |
|
|
14
|
-
| [
|
|
14
|
+
| [Pavel Bukhatkin (welderpb)](https://github.com/welderpb) | Requests remote API slicing for containerized deployments and specifies authentication, timeout and async needs in [#50](https://github.com/DMontgomery40/bambu-printer-mcp/issues/50). This informs the optional single-slot Orca API backend; validation uses HTTP fixtures and the genuine API application with a fake CLI, without a physical print. |
|
|
15
|
+
| [AFKFelix](https://github.com/AFKFelix) and [orca-slicer-api contributors](https://github.com/AFKFelix/orca-slicer-api) | Provide the upstream HTTP slicing application and published multipart/job contracts used by the optional remote backend. |
|
|
16
|
+
| [travismcashan](https://github.com/travismcashan) | Reports stock X2D material-switch and runout-purge rejection, misleading unchecked confirmation, and stdout diagnostics in [#44](https://github.com/DMontgomery40/bambu-printer-mcp/issues/44). Supplies exact preset syntax and firmware evidence, then reports a clean physical X2D cube print with a local workaround; maintainer validation remains isolated and mocked. Reports the accepted native job and subsequent helper teardown crash in [#46](https://github.com/DMontgomery40/bambu-printer-mcp/issues/46), with completed-print and macOS crash evidence informing result preservation and duplicate-print warnings. Contributes the X2D dual-nozzle slicer-output and dual-external-spool fix in [#59](https://github.com/DMontgomery40/bambu-printer-mcp/pull/59), with a real five-filament X2D job, live `vir_slot` evidence and the Bambu Studio virtual-tray conventions; the change is validated offline and mocked, and no physical print is claimed for it. |
|
|
15
17
|
| [nitpreet22](https://github.com/nitpreet22) | Fixes rejection of the stock A1/A1 mini `M109 H` startup waits in [#41](https://github.com/DMontgomery40/bambu-printer-mcp/pull/41), contributes temperature and model regressions, and supplies Windows Bambu Studio profile and real sliced A1 job inspection evidence. Their physical print used Bambu Studio, not this MCP change. |
|
|
16
18
|
| [Steavie (steavie)](https://github.com/steavie) | Reports the reversed A1/A1 mini serial prefixes in [#39](https://github.com/DMontgomery40/bambu-printer-mcp/issues/39), with A1 status and configuration evidence that identifies the status and fresh-identity regression. |
|
|
17
19
|
| [JaviOFC](https://github.com/JaviOFC) | Reports the X2D identity rejection in [#36](https://github.com/DMontgomery40/bambu-printer-mcp/issues/36) and supplies current firmware, connection-mode, status-field, serial-prefix, and cloud-job evidence showing that `model_id` can contain a print-job identifier while `model` correctly identifies the printer. |
|
package/README.md
CHANGED
|
@@ -210,7 +210,7 @@ The optional FULU **BambuNetwork bridge** exposes `bambu_network_bridge_status`,
|
|
|
210
210
|
- Start G-code files already stored on the printer
|
|
211
211
|
- **Collar charm print wrapper** (`print_collar_charm`) — specialized two-color workflow with fixed tray policy for inner (black, AMS 1 slot 1) and outer (white, AMS 2 slot 1) charm parts
|
|
212
212
|
- STL manipulation: scale, rotate, extend base, merge vertices, center at origin, lay flat, and inspect model info
|
|
213
|
-
- Slice STL or 3MF files using an external CLI. BambuStudio, FULU, and Orca require the exact machine preset and resolve BBL dependencies before slicing; see [FULU/Orca CLI setup and validation limits](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
|
|
213
|
+
- Slice STL or 3MF files using an external CLI, or use the optional [`orca-slicer-api` backend](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md) in a separate container without a local slicer executable. Remote slicing requires a matching read-only Orca profile tree and currently supports one filament slot and one selected plate. BambuStudio, FULU, and Orca require the exact machine preset and resolve BBL dependencies before slicing; see [FULU/Orca CLI setup and validation limits](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
|
|
214
214
|
- Inspect slicer settings from a saved 3MF template or extracted profile via `get_slice_settings`
|
|
215
215
|
- Enumerate saved slicing templates from the local registry via `list_templates`
|
|
216
216
|
- Save templates into the local registry via `save_template`
|
|
@@ -279,6 +279,8 @@ If you are using the direct-feed spool holder (no AMS attached) or want to bypas
|
|
|
279
279
|
|
|
280
280
|
For H2 projects with declared filaments, also provide the required mapping; `use_ams: false` alone does not remove the firmware's mapping requirement. See [`print_3mf`](#print_3mf).
|
|
281
281
|
|
|
282
|
+
Dual-nozzle printers (H2D, H2D Pro, H2C, X2D) have two external spool holders, and the printer reports one virtual slot per nozzle. Use Bambu Studio's ids in `ams_slots`/`ams_mapping`: `254` is the left/deputy nozzle's external spool and `255` the right/main nozzle's. The server composes the selected plate's `filament_map` with `physical_extruder_map` to verify each filament's physical nozzle (`0` = right, `1` = left), and requires the matching fresh `vir_slot` report. An explicit external-spool mapping is preserved with `use_ams: false`; a missing mapping is derived only from these inspected headers. Single-nozzle printers keep `254` as their only external tray.
|
|
283
|
+
|
|
282
284
|
### Auto-match AMS by RFID
|
|
283
285
|
|
|
284
286
|
For pre-sliced 3MFs that declare filament types, the `auto_match_ams` flag on `print_3mf` (or the standalone `resolve_3mf_ams_slots` dry-run tool) automatically resolves the required filaments against your live AMS inventory. The matcher works as follows:
|
|
@@ -948,7 +950,7 @@ Install Bambu Studio and its networking plug-in, plus the Apple command-line dev
|
|
|
948
950
|
|
|
949
951
|
The npm package and desktop bundle include `native/bambu-native-print.cpp`, `scripts/build-bambu-native.sh` and the Linux setup script, never a developer's compiled binary. The server finds the built helper relative to its installed package, regardless of the launch directory. A trusted server setting `BAMBU_NATIVE_HELPER` can select an alternate executable.
|
|
950
952
|
|
|
951
|
-
For X2D, `print_3mf` defaults to `connection_mode: "bambu_native"`; legacy `lan_mqtt_ftps` requests are redirected to it. Supply `ams_slots`, a complete project-level `ams_mapping`, or `auto_match_ams: true`.
|
|
953
|
+
For X2D, `print_3mf` defaults to `connection_mode: "bambu_native"`; legacy `lan_mqtt_ftps` requests are redirected to it. Supply `ams_slots`, a complete project-level `ams_mapping`, or `auto_match_ams: true`. A job that uses only one external spool requires explicit `use_ams: false`; a job mixing AMS trays with the external spools maps them as `254` (left/deputy nozzle) and `255` (right/main nozzle). Raw `ams_mapping2` and nozzle/extra-option overrides are rejected before dispatch; the server derives both AMS representations from the checked structured mapping. Model/nozzle/material inspection, fresh printer-state checks, and human preflight apply before the helper receives a private snapshot. The native helper requests fresh shared printer-state authorization immediately before each print submission, including a certificate retry, and before checked heating, resume, or error-clearing commands; custom helpers must support this handshake. Stop and heater-off remain available without preflight. Native upload-only requests use the same all-plate inspection as FTPS uploads. They accept `project_name`, `preset_name`, `bed_type`, and an existing `plate_index`; AMS, nozzle, and calibration print options are rejected. Use `print_3mf` for checked print settings.
|
|
952
954
|
|
|
953
955
|
The helper waits for connection/certificate exchange and retries only the initial `-4030` send once. Rebuild the helper after upgrading to receive the teardown fix. A successful dispatch receipt ends the helper before plug-in cleanup can turn an accepted job into an error. If an older/custom helper crashes after dispatch, the server reports uncertainty: check the printer state and job name before retrying, because resending can duplicate a print. Previous hardware testing reached `RUNNING` on X2D; this revision is validated with mocked transport regressions and clean installs, not a new physical print. `bambu_connect` is an optional macOS handoff for user review in Bambu Connect and does not start a print. Native task, heater, and error controls retain the common safety checks; raw controls cannot bypass them. Request cancellation, stop, and heater-off interrupt pending native helpers; the server waits for process exit before releasing a checked file. A command already sent to the printer cannot be recalled by cancelling the request, so verify printer state before retrying.
|
|
954
956
|
|
|
@@ -964,7 +966,7 @@ Use the same package version as the server, for example `bambu-printer-mcp@1.2.3
|
|
|
964
966
|
|
|
965
967
|
`--check-update` asks Bambu's plug-in service for the newest Linux plug-in and compares the `PrintParams` layout in the matching Bambu Studio header with the helper. It reports only: the setup never installs an unpinned plug-in, because a changed layout would send wrong print parameters. A pin is raised by a package release after the check passes and a hardware test.
|
|
966
968
|
|
|
967
|
-
Once the helper, plug-in and certificates are installed, X2D printing, upload and controls use the same routing, safety checks and authorization handshake described for macOS above. On Linux, stop and heater-off go out over the native route and MQTT at the same time, so a stuck helper cannot delay them. MQTT is sent again once every older print or heating helper for that printer has exited, so none can dispatch after it; either the native route or that later MQTT send succeeding counts, and the result lists all sends under `routes`. A repeated stop or heater-off is harmless. macOS keeps its native-only route. The server accepts only the pinned plug-in checksums; `BAMBU_NATIVE_ALLOW_UNPINNED_PLUGIN=1` is a trusted override for testing another build at your own risk. Without a complete installation, Linux keeps its previous behavior: X2D status and slicing work, stop, pause and heater-off use MQTT, and native print requests name the setup command before contacting the printer. Bambu publishes the Linux plug-in for x86-64 only; other architectures keep the MQTT behavior.
|
|
969
|
+
Once the helper, plug-in and certificates are installed, X2D printing, upload and controls use the same routing, safety checks and authorization handshake described for macOS above. On Linux, stop and heater-off go out over the native route and MQTT at the same time, so a stuck helper cannot delay them. MQTT is sent again once every older print or heating helper for that printer has exited and the immediate MQTT attempt has settled, so none can dispatch after it; either the native route or that later MQTT send succeeding counts, and the result lists all sends under `routes`. Each MQTT send gives up after 15 seconds (`BAMBU_EMERGENCY_MQTT_TIMEOUT_MS`), so an unreachable MQTT connection cannot hold back a stop the native route already sent. A connection completing after that deadline cannot dispatch its expired command; an already queued MQTT publish is discarded by force-closing and invalidating that client. A repeated stop or heater-off is harmless. macOS keeps its native-only route. The server accepts only the pinned plug-in checksums; `BAMBU_NATIVE_ALLOW_UNPINNED_PLUGIN=1` is a trusted override for testing another build at your own risk. Without a complete installation, Linux keeps its previous behavior: X2D status and slicing work, stop, pause and heater-off use MQTT, and native print requests name the setup command before contacting the printer. Bambu publishes the Linux plug-in for x86-64 only; other architectures keep the MQTT behavior.
|
|
968
970
|
|
|
969
971
|
#### x2d_native_control
|
|
970
972
|
|
package/dist/ams-mapping.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Tray-value options. `dualNozzle` admits 255, the right/main nozzle external spool that
|
|
2
|
+
* dual-nozzle printers report beside the left/deputy external spool 254 (Bambu Studio
|
|
3
|
+
* VIRTUAL_TRAY_MAIN_ID / VIRTUAL_TRAY_DEPUTY_ID). Single-nozzle printers keep 254 only. */
|
|
4
|
+
export interface AmsTrayValueOptions {
|
|
5
|
+
dualNozzle?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function describeAmsTrayRange(options?: AmsTrayValueOptions): string;
|
|
8
|
+
export declare function normalizeBridgeAmsTrayValue(value: unknown, label: string, options?: AmsTrayValueOptions): number;
|
|
9
|
+
export declare function normalizeAmsMappingObject(mapping: Record<string, unknown>, label?: string, options?: AmsTrayValueOptions): number[];
|
|
3
10
|
export declare function hasAmsMappingInput(value: unknown): boolean;
|
|
11
|
+
/** Flat wire entries use -1 for dual-nozzle virtual trays; ams_mapping2 identifies their spool. */
|
|
12
|
+
export declare function bridgeAmsMappingValue(value: number, dualNozzle?: boolean): number;
|
|
13
|
+
/** Select an external spool only from a verified physical nozzle on dual-nozzle printers. */
|
|
14
|
+
export declare function externalTrayForNozzle(nozzle: number | undefined, dualNozzle?: boolean): number;
|
|
15
|
+
/** Virtual trays use slot zero on dual-nozzle printers; retain the historical single-nozzle form. */
|
|
16
|
+
export declare function bridgeAmsMapping2Entry(value: number, dualNozzle?: boolean): {
|
|
17
|
+
ams_id: number;
|
|
18
|
+
slot_id: number;
|
|
19
|
+
};
|
package/dist/ams-mapping.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function describeAmsTrayRange(options = {}) {
|
|
2
|
+
return options.dualNozzle
|
|
3
|
+
? "[-1, 15] (absolute tray), 128-253 (AMS-HT), 254 (left/deputy external) or 255 (right/main external)"
|
|
4
|
+
: "[-1, 15] (absolute tray) or 128-254 (HT/external)";
|
|
5
|
+
}
|
|
6
|
+
export function normalizeBridgeAmsTrayValue(value, label, options = {}) {
|
|
2
7
|
let numeric;
|
|
3
8
|
if (typeof value === "number") {
|
|
4
9
|
numeric = value;
|
|
@@ -12,8 +17,8 @@ export function normalizeBridgeAmsTrayValue(value, label) {
|
|
|
12
17
|
if (!Number.isInteger(numeric) ||
|
|
13
18
|
numeric < -1 ||
|
|
14
19
|
(numeric > 15 && numeric < 128) ||
|
|
15
|
-
numeric > 254) {
|
|
16
|
-
throw new Error(`${label} must be an integer in
|
|
20
|
+
numeric > (options.dualNozzle ? 255 : 254)) {
|
|
21
|
+
throw new Error(`${label} must be an integer in ${describeAmsTrayRange(options)}; got ${JSON.stringify(value)}`);
|
|
17
22
|
}
|
|
18
23
|
return numeric;
|
|
19
24
|
}
|
|
@@ -21,7 +26,7 @@ function isNumericMappingKey(key) {
|
|
|
21
26
|
const numeric = Number(key);
|
|
22
27
|
return Number.isInteger(numeric) && numeric >= 0;
|
|
23
28
|
}
|
|
24
|
-
export function normalizeAmsMappingObject(mapping, label = "ams_mapping") {
|
|
29
|
+
export function normalizeAmsMappingObject(mapping, label = "ams_mapping", options = {}) {
|
|
25
30
|
return Object.entries(mapping)
|
|
26
31
|
.sort(([leftKey], [rightKey]) => {
|
|
27
32
|
const leftNumeric = isNumericMappingKey(leftKey);
|
|
@@ -34,7 +39,7 @@ export function normalizeAmsMappingObject(mapping, label = "ams_mapping") {
|
|
|
34
39
|
return 1;
|
|
35
40
|
return 0;
|
|
36
41
|
})
|
|
37
|
-
.map(([key, value]) => normalizeBridgeAmsTrayValue(value, `${label}[${key}]
|
|
42
|
+
.map(([key, value]) => normalizeBridgeAmsTrayValue(value, `${label}[${key}]`, options));
|
|
38
43
|
}
|
|
39
44
|
export function hasAmsMappingInput(value) {
|
|
40
45
|
if (Array.isArray(value))
|
|
@@ -43,3 +48,29 @@ export function hasAmsMappingInput(value) {
|
|
|
43
48
|
return Object.keys(value).length > 0;
|
|
44
49
|
return value !== undefined && value !== null;
|
|
45
50
|
}
|
|
51
|
+
/** Flat wire entries use -1 for dual-nozzle virtual trays; ams_mapping2 identifies their spool. */
|
|
52
|
+
export function bridgeAmsMappingValue(value, dualNozzle = false) {
|
|
53
|
+
return dualNozzle && (value === 254 || value === 255) ? -1 : value;
|
|
54
|
+
}
|
|
55
|
+
/** Select an external spool only from a verified physical nozzle on dual-nozzle printers. */
|
|
56
|
+
export function externalTrayForNozzle(nozzle, dualNozzle = false) {
|
|
57
|
+
if (!dualNozzle)
|
|
58
|
+
return 254;
|
|
59
|
+
if (nozzle === 0)
|
|
60
|
+
return 255;
|
|
61
|
+
if (nozzle === 1)
|
|
62
|
+
return 254;
|
|
63
|
+
throw new Error("External-spool printing requires inspected filament_map and physical_extruder_map nozzle bindings.");
|
|
64
|
+
}
|
|
65
|
+
/** Virtual trays use slot zero on dual-nozzle printers; retain the historical single-nozzle form. */
|
|
66
|
+
export function bridgeAmsMapping2Entry(value, dualNozzle = false) {
|
|
67
|
+
if (value < 0)
|
|
68
|
+
return { ams_id: 255, slot_id: 255 };
|
|
69
|
+
if (value === 255)
|
|
70
|
+
return dualNozzle ? { ams_id: 255, slot_id: 0 } : { ams_id: 255, slot_id: 255 };
|
|
71
|
+
if (value === 254)
|
|
72
|
+
return { ams_id: 254, slot_id: dualNozzle ? 0 : 254 };
|
|
73
|
+
if (value >= 128)
|
|
74
|
+
return { ams_id: 128, slot_id: value - 128 };
|
|
75
|
+
return { ams_id: Math.floor(value / 4), slot_id: value % 4 };
|
|
76
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,11 +14,12 @@ import { BambuNetworkBridge } from "./bambu-network-bridge.js";
|
|
|
14
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();
|
|
@@ -51,6 +52,21 @@ const DEFAULT_BAMBU_MODEL = process.env.BAMBU_PRINTER_MODEL?.trim().toLowerCase(
|
|
|
51
52
|
const DEFAULT_BAMBU_CONNECTION_MODE = process.env.BAMBU_DEFAULT_CONNECTION_MODE?.trim().toLowerCase() || "";
|
|
52
53
|
const DEFAULT_BED_TYPE = process.env.BED_TYPE?.trim().toLowerCase() || "textured_plate";
|
|
53
54
|
const DEFAULT_NOZZLE_DIAMETER = process.env.NOZZLE_DIAMETER?.trim() || "0.4";
|
|
55
|
+
/** Bound for each Linux X2D emergency MQTT send (BAMBU_EMERGENCY_MQTT_TIMEOUT_MS, 1–60000 ms). */
|
|
56
|
+
function withEmergencyMqttTimeout(send) {
|
|
57
|
+
const raw = Number(process.env.BAMBU_EMERGENCY_MQTT_TIMEOUT_MS?.trim() || 15000);
|
|
58
|
+
const ms = Number.isInteger(raw) && raw >= 1 && raw <= 60000 ? raw : 15000;
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
let timer;
|
|
61
|
+
const timeout = new Promise((_, reject) => {
|
|
62
|
+
timer = setTimeout(() => {
|
|
63
|
+
const error = new Error(`no MQTT confirmation within ${ms / 1000} s`);
|
|
64
|
+
controller.abort(error);
|
|
65
|
+
reject(error);
|
|
66
|
+
}, ms);
|
|
67
|
+
});
|
|
68
|
+
return Promise.race([Promise.resolve().then(() => send(controller.signal)), timeout]).finally(() => clearTimeout(timer));
|
|
69
|
+
}
|
|
54
70
|
function requestedPrintNozzles(args) {
|
|
55
71
|
if (args.nozzle_diameters !== undefined && args.nozzle_diameter !== undefined) {
|
|
56
72
|
throw new Error("Supply nozzle_diameters or nozzle_diameter, not both.");
|
|
@@ -523,7 +539,7 @@ function defaultSlicerPathFor(slicerType) {
|
|
|
523
539
|
}
|
|
524
540
|
function resolveSlicerConfig(args) {
|
|
525
541
|
const slicerType = normalizeSlicerType(String(args?.slicer_type || DEFAULT_SLICER_TYPE));
|
|
526
|
-
const slicerPath = String(args?.slicer_path || process.env.SLICER_PATH || defaultSlicerPathFor(slicerType));
|
|
542
|
+
const slicerPath = slicerType === "orca-slicer-api" ? "" : String(args?.slicer_path || process.env.SLICER_PATH || defaultSlicerPathFor(slicerType));
|
|
527
543
|
const slicerProfile = String(args?.slicer_profile || DEFAULT_SLICER_PROFILE);
|
|
528
544
|
return { slicerType, slicerPath, slicerProfile };
|
|
529
545
|
}
|
|
@@ -735,18 +751,10 @@ function stringifyBridgeJson(value) {
|
|
|
735
751
|
return value;
|
|
736
752
|
return JSON.stringify(value);
|
|
737
753
|
}
|
|
738
|
-
function nativeAmsMapping2(mapping) {
|
|
754
|
+
function nativeAmsMapping2(mapping, dualNozzle = false) {
|
|
739
755
|
if (!mapping || mapping.length === 0)
|
|
740
756
|
return undefined;
|
|
741
|
-
return JSON.stringify(mapping.map((value) =>
|
|
742
|
-
if (value < 0 || value === 255)
|
|
743
|
-
return { ams_id: 255, slot_id: 255 };
|
|
744
|
-
if (value === 254)
|
|
745
|
-
return { ams_id: 254, slot_id: 254 };
|
|
746
|
-
if (value >= 128)
|
|
747
|
-
return { ams_id: 128, slot_id: value - 128 };
|
|
748
|
-
return { ams_id: Math.floor(value / 4), slot_id: value % 4 };
|
|
749
|
-
}));
|
|
757
|
+
return JSON.stringify(mapping.map((value) => bridgeAmsMapping2Entry(value, dualNozzle)));
|
|
750
758
|
}
|
|
751
759
|
function redactPrintParams(params) {
|
|
752
760
|
return {
|
|
@@ -1025,6 +1033,9 @@ class BambuPrinterMCPServer {
|
|
|
1025
1033
|
resolveSlicerConfigFromArgs(args, toolName) {
|
|
1026
1034
|
const config = resolveSlicerConfig(args);
|
|
1027
1035
|
if (args?.slicer_path !== undefined) {
|
|
1036
|
+
if (config.slicerType === "orca-slicer-api") {
|
|
1037
|
+
throw new Error(`${toolName}: remote slicing uses trusted SLICER_API_URL and BAMBU_PROFILES_ROOT configuration; slicer_path is unsupported.`);
|
|
1038
|
+
}
|
|
1028
1039
|
const resolvedSlicerPath = this.resolveExecutableSelectorArg(args.slicer_path, toolName, "slicer_path");
|
|
1029
1040
|
if (resolvedSlicerPath !== undefined) {
|
|
1030
1041
|
config.slicerPath = resolvedSlicerPath;
|
|
@@ -1048,10 +1059,10 @@ class BambuPrinterMCPServer {
|
|
|
1048
1059
|
}
|
|
1049
1060
|
console.error(`3MF has no gcode - auto-slicing with ${slicerType} for ${printModel}`);
|
|
1050
1061
|
const autoSliceOptions = {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1062
|
+
...(slicerType === "orca-slicer-api" ? {} : {
|
|
1063
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
1064
|
+
}),
|
|
1065
|
+
...(slicerType === "orca-slicer-api" ? { slicePlate: Number(args.plate_index ?? 0) + 1 } : {}),
|
|
1055
1066
|
bedType: bedType ? resolveBambuStudioCliBedType(bedType) : undefined,
|
|
1056
1067
|
nozzleType: resolveNozzleType(args?.nozzle_type),
|
|
1057
1068
|
nozzleVolumeType: resolveNozzleVolumeType(args?.nozzle_volume_type),
|
|
@@ -1063,8 +1074,9 @@ class BambuPrinterMCPServer {
|
|
|
1063
1074
|
async resolveAmsPrintSettings(threeMFPath, args, host, bambuSerial, bambuToken, printModel, printNozzle) {
|
|
1064
1075
|
const parsed3MFData = await parse3MF(threeMFPath);
|
|
1065
1076
|
let parsedAmsMapping;
|
|
1077
|
+
const trayOptions = { dualNozzle: isDualNozzleModel(printModel) };
|
|
1066
1078
|
if (parsed3MFData.slicerConfig?.ams_mapping) {
|
|
1067
|
-
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping);
|
|
1079
|
+
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping, "ams_mapping", trayOptions);
|
|
1068
1080
|
if (slots.length > 0) {
|
|
1069
1081
|
parsedAmsMapping = slots;
|
|
1070
1082
|
}
|
|
@@ -1077,10 +1089,10 @@ class BambuPrinterMCPServer {
|
|
|
1077
1089
|
if (hasUserAmsMapping) {
|
|
1078
1090
|
let userMappingOverride;
|
|
1079
1091
|
if (Array.isArray(args.ams_mapping)) {
|
|
1080
|
-
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]
|
|
1092
|
+
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]`, trayOptions));
|
|
1081
1093
|
}
|
|
1082
1094
|
else if (args.ams_mapping && typeof args.ams_mapping === 'object') {
|
|
1083
|
-
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping);
|
|
1095
|
+
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping, "ams_mapping", trayOptions);
|
|
1084
1096
|
}
|
|
1085
1097
|
if (userMappingOverride && userMappingOverride.length > 0) {
|
|
1086
1098
|
finalAmsMapping = userMappingOverride;
|
|
@@ -1089,7 +1101,7 @@ class BambuPrinterMCPServer {
|
|
|
1089
1101
|
}
|
|
1090
1102
|
}
|
|
1091
1103
|
if (!hasUserAmsMapping && hasUserAmsSlots) {
|
|
1092
|
-
const userSlots = args.ams_slots.map((slot, i) => normalizeBridgeAmsTrayValue(slot, `ams_slots[${i}]
|
|
1104
|
+
const userSlots = args.ams_slots.map((slot, i) => normalizeBridgeAmsTrayValue(slot, `ams_slots[${i}]`, trayOptions));
|
|
1093
1105
|
finalAmsSlots = userSlots;
|
|
1094
1106
|
finalAmsMapping = undefined;
|
|
1095
1107
|
useAMS = userSlots.length > 0;
|
|
@@ -1106,8 +1118,10 @@ class BambuPrinterMCPServer {
|
|
|
1106
1118
|
useAMS = true;
|
|
1107
1119
|
}
|
|
1108
1120
|
if (args?.use_ams === false) {
|
|
1109
|
-
|
|
1110
|
-
|
|
1121
|
+
if (!hasUserAmsMapping)
|
|
1122
|
+
finalAmsMapping = undefined;
|
|
1123
|
+
if (!hasUserAmsSlots)
|
|
1124
|
+
finalAmsSlots = undefined;
|
|
1111
1125
|
useAMS = false;
|
|
1112
1126
|
}
|
|
1113
1127
|
if ((!finalAmsMapping || finalAmsMapping.length === 0) && (!finalAmsSlots || finalAmsSlots.length === 0)) {
|
|
@@ -1177,8 +1191,16 @@ class BambuPrinterMCPServer {
|
|
|
1177
1191
|
inspection.usedFilamentPositions.forEach((position, index) => { mapping[position] = finalAmsSlots[index]; });
|
|
1178
1192
|
}
|
|
1179
1193
|
if (!useAMS) {
|
|
1180
|
-
mapping = Array(inspection.materials.length).fill(-1);
|
|
1181
|
-
|
|
1194
|
+
mapping ?? (mapping = Array(inspection.materials.length).fill(-1));
|
|
1195
|
+
const dualNozzle = isDualNozzleModel(printModel);
|
|
1196
|
+
for (const position of inspection.usedFilamentPositions) {
|
|
1197
|
+
if (mapping[position] === undefined || mapping[position] < 0) {
|
|
1198
|
+
mapping[position] = externalTrayForNozzle(inspection.filamentNozzleIndices?.[position], dualNozzle);
|
|
1199
|
+
}
|
|
1200
|
+
if (mapping[position] !== 254 && !(dualNozzle && mapping[position] === 255)) {
|
|
1201
|
+
throw new Error("use_ams:false requires an external-spool mapping for every used filament.");
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1182
1204
|
}
|
|
1183
1205
|
const requirements = { ...inspection, amsMapping: mapping, useAMS };
|
|
1184
1206
|
const initialStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
@@ -1187,7 +1209,7 @@ class BambuPrinterMCPServer {
|
|
|
1187
1209
|
const confirmedStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1188
1210
|
validatePrinterState(confirmedStatus, requirements);
|
|
1189
1211
|
this.bambu.assertBedClearance(confirmedStatus, bedClearance);
|
|
1190
|
-
const amsMapping = stringifyBridgeJson(mapping);
|
|
1212
|
+
const amsMapping = stringifyBridgeJson(mapping?.map(value => bridgeAmsMappingValue(value, isDualNozzleModel(printModel))));
|
|
1191
1213
|
const remoteFileName = uniquePrintName(threeMFPath);
|
|
1192
1214
|
const params = {
|
|
1193
1215
|
dev_id: devId,
|
|
@@ -1204,7 +1226,7 @@ class BambuPrinterMCPServer {
|
|
|
1204
1226
|
ftp_file_md5: String(args?.ftp_file_md5 || ""),
|
|
1205
1227
|
nozzle_mapping: stringifyBridgeJson(args?.nozzle_mapping) || "",
|
|
1206
1228
|
ams_mapping: amsMapping || "",
|
|
1207
|
-
ams_mapping2:
|
|
1229
|
+
ams_mapping2: nativeAmsMapping2(mapping, isDualNozzleModel(printModel)) || "",
|
|
1208
1230
|
ams_mapping_info: stringifyBridgeJson(args?.ams_mapping_info) || "",
|
|
1209
1231
|
nozzles_info: stringifyBridgeJson(args?.nozzles_info) || "",
|
|
1210
1232
|
connection_type: connectionType,
|
|
@@ -1244,7 +1266,7 @@ class BambuPrinterMCPServer {
|
|
|
1244
1266
|
host: devIp, serial: devId, token: password || bambuToken, filePath: snapshot,
|
|
1245
1267
|
projectName: String(params.task_name), presetName, remoteName: remoteFileName,
|
|
1246
1268
|
plateIndex, bedType: printBedType, useAMS,
|
|
1247
|
-
amsMapping, amsMapping2: nativeAmsMapping2(mapping),
|
|
1269
|
+
amsMapping, amsMapping2: nativeAmsMapping2(mapping, isDualNozzleModel(printModel)),
|
|
1248
1270
|
bedLeveling: params.task_bed_leveling,
|
|
1249
1271
|
flowCalibration: params.task_flow_cali,
|
|
1250
1272
|
vibrationCalibration: params.task_vibration_cali,
|
|
@@ -1447,10 +1469,9 @@ class BambuPrinterMCPServer {
|
|
|
1447
1469
|
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.');
|
|
1448
1470
|
}
|
|
1449
1471
|
const autoSliceOptions = {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
skipModifiedGcodes: true,
|
|
1472
|
+
...(slicerType === "orca-slicer-api" ? {} : {
|
|
1473
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
1474
|
+
}),
|
|
1454
1475
|
bedType,
|
|
1455
1476
|
};
|
|
1456
1477
|
try {
|
|
@@ -1943,7 +1964,7 @@ class BambuPrinterMCPServer {
|
|
|
1943
1964
|
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." },
|
|
1944
1965
|
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." },
|
|
1945
1966
|
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." },
|
|
1946
|
-
slice_plate: { type: "number", description: "Which plate index to slice. 0 = all plates
|
|
1967
|
+
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." }
|
|
1947
1968
|
},
|
|
1948
1969
|
required: ["stl_path", "bambu_model"]
|
|
1949
1970
|
}
|
|
@@ -2046,8 +2067,8 @@ class BambuPrinterMCPServer {
|
|
|
2046
2067
|
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)." },
|
|
2047
2068
|
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." },
|
|
2048
2069
|
use_ams: { type: "boolean", description: "Whether to use the AMS; defaults to auto-detect from the 3MF mapping." },
|
|
2049
|
-
ams_mapping: { type: "array", description: "AMS slot mapping array used by both local MCP printing and FULU PrintParams.", items: { type: "number" } },
|
|
2050
|
-
ams_slots: { type: "array", description: "Per-used-filament AMS slot list, matching the local LAN print path.", items: { type: "number" } },
|
|
2070
|
+
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" } },
|
|
2071
|
+
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" } },
|
|
2051
2072
|
ams_mapping_bridge: { type: "string", description: "Raw JSON string override for FULU PrintParams ams_mapping when the automatic array is not enough." },
|
|
2052
2073
|
ams_mapping2: { type: "string", description: "Raw JSON string for FULU PrintParams ams_mapping2, matching OrcaSlicer-bambulab's v1 AMS mapping field." },
|
|
2053
2074
|
ams_mapping_info: { type: "string", description: "Raw JSON string for FULU PrintParams ams_mapping_info, matching OrcaSlicer-bambulab's detailed AMS mapping field." },
|
|
@@ -2475,12 +2496,12 @@ class BambuPrinterMCPServer {
|
|
|
2475
2496
|
use_ams: { type: "boolean", description: "Whether to use the AMS (default: auto-detect from 3MF)" },
|
|
2476
2497
|
ams_mapping: {
|
|
2477
2498
|
type: "array",
|
|
2478
|
-
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.",
|
|
2499
|
+
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.",
|
|
2479
2500
|
items: { type: "number" }
|
|
2480
2501
|
},
|
|
2481
2502
|
ams_slots: {
|
|
2482
2503
|
type: "array",
|
|
2483
|
-
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.",
|
|
2504
|
+
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.",
|
|
2484
2505
|
items: { type: "number" }
|
|
2485
2506
|
},
|
|
2486
2507
|
auto_match_ams: {
|
|
@@ -2670,10 +2691,19 @@ class BambuPrinterMCPServer {
|
|
|
2670
2691
|
const linuxEmergency = (native, mqtt) => async () => {
|
|
2671
2692
|
if (process.platform !== "linux")
|
|
2672
2693
|
return native();
|
|
2694
|
+
// An unreachable printer makes the MQTT connect retry forever; bound each
|
|
2695
|
+
// MQTT send so it cannot hold back a stop the native route already sent.
|
|
2696
|
+
const immediate = withEmergencyMqttTimeout(mqtt);
|
|
2673
2697
|
const [nativeResult, immediateResult, mqttResult] = await Promise.allSettled([
|
|
2674
2698
|
native(),
|
|
2675
|
-
|
|
2676
|
-
|
|
2699
|
+
immediate,
|
|
2700
|
+
// Finish the first attempt before reusing its cached publisher: its
|
|
2701
|
+
// expiry must never close the later, counted send's transport.
|
|
2702
|
+
Promise.allSettled([interruptNativeOperations(bambuSerial), immediate]).then(results => {
|
|
2703
|
+
if (results[0].status === "rejected")
|
|
2704
|
+
throw results[0].reason;
|
|
2705
|
+
return withEmergencyMqttTimeout(mqtt);
|
|
2706
|
+
}),
|
|
2677
2707
|
]);
|
|
2678
2708
|
const reason = (result) => result.status === "rejected" ? (result.reason instanceof Error ? result.reason.message : String(result.reason)) : undefined;
|
|
2679
2709
|
if (nativeResult.status === "rejected" && mqttResult.status === "rejected") {
|
|
@@ -2890,7 +2920,7 @@ class BambuPrinterMCPServer {
|
|
|
2890
2920
|
case "cancel_print":
|
|
2891
2921
|
result = await this.bambu.cancelJob(host, bambuSerial, bambuToken, nativeControls()
|
|
2892
2922
|
? linuxEmergency(() => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
2893
|
-
messageJson: JSON.stringify({ print: { command: "stop", param: "", sequence_id: String(Date.now()) } }), qos: 1 }),
|
|
2923
|
+
messageJson: JSON.stringify({ print: { command: "stop", param: "", sequence_id: String(Date.now()) } }), qos: 1 }), signal => this.bambu.sendStopOverMqtt(host, bambuSerial, bambuToken, signal))
|
|
2894
2924
|
: undefined);
|
|
2895
2925
|
break;
|
|
2896
2926
|
case "pause_print":
|
|
@@ -3030,7 +3060,7 @@ class BambuPrinterMCPServer {
|
|
|
3030
3060
|
const native = () => sendNativeCommand({ host, serial: bambuSerial, token: bambuToken,
|
|
3031
3061
|
messageJson: buildBambuNativeTemperatureCommand(heater, target).messageJson }, { signal: extra.signal, assertActive, beforeDispatch });
|
|
3032
3062
|
return target === 0
|
|
3033
|
-
? linuxEmergency(native,
|
|
3063
|
+
? linuxEmergency(native, signal => this.bambu.sendHeaterOffOverMqtt(host, bambuSerial, bambuToken, heater, signal))()
|
|
3034
3064
|
: native();
|
|
3035
3065
|
}
|
|
3036
3066
|
: undefined);
|
|
@@ -3415,10 +3445,9 @@ class BambuPrinterMCPServer {
|
|
|
3415
3445
|
}
|
|
3416
3446
|
console.error(`3MF has no gcode — auto-slicing with ${slicerType} for ${printModel}`);
|
|
3417
3447
|
const autoSliceOptions = {
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
skipModifiedGcodes: true,
|
|
3448
|
+
...(slicerType === "orca-slicer-api" ? { slicePlate: plateIndex + 1 } : {
|
|
3449
|
+
uptodate: true, ensureOnBed: true, minSave: true, skipModifiedGcodes: true,
|
|
3450
|
+
}),
|
|
3422
3451
|
bedType: printBedType,
|
|
3423
3452
|
nozzleType: resolveNozzleType(args?.nozzle_type),
|
|
3424
3453
|
nozzleVolumeType: resolveNozzleVolumeType(args?.nozzle_volume_type),
|
|
@@ -3440,9 +3469,10 @@ class BambuPrinterMCPServer {
|
|
|
3440
3469
|
}
|
|
3441
3470
|
const parsed3MFData = await parse3MF(threeMFPath);
|
|
3442
3471
|
const isH2Print = H2_BAMBU_MODELS.has(printModel);
|
|
3472
|
+
const trayOptions = { dualNozzle: isDualNozzleModel(printModel) };
|
|
3443
3473
|
let parsedAmsMapping;
|
|
3444
3474
|
if (!isH2Print && parsed3MFData.slicerConfig?.ams_mapping) {
|
|
3445
|
-
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping);
|
|
3475
|
+
const slots = normalizeAmsMappingObject(parsed3MFData.slicerConfig.ams_mapping, "ams_mapping", trayOptions);
|
|
3446
3476
|
if (slots.length > 0) {
|
|
3447
3477
|
parsedAmsMapping = slots;
|
|
3448
3478
|
}
|
|
@@ -3455,10 +3485,10 @@ class BambuPrinterMCPServer {
|
|
|
3455
3485
|
if (hasUserAmsMapping) {
|
|
3456
3486
|
let userMappingOverride;
|
|
3457
3487
|
if (Array.isArray(args.ams_mapping)) {
|
|
3458
|
-
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]
|
|
3488
|
+
userMappingOverride = args.ams_mapping.map((v, i) => normalizeBridgeAmsTrayValue(v, `ams_mapping[${i}]`, trayOptions));
|
|
3459
3489
|
}
|
|
3460
3490
|
else if (args.ams_mapping && typeof args.ams_mapping === 'object') {
|
|
3461
|
-
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping);
|
|
3491
|
+
userMappingOverride = normalizeAmsMappingObject(args.ams_mapping, "ams_mapping", trayOptions);
|
|
3462
3492
|
}
|
|
3463
3493
|
if (userMappingOverride && userMappingOverride.length > 0) {
|
|
3464
3494
|
finalAmsMapping = userMappingOverride;
|
|
@@ -3499,8 +3529,10 @@ class BambuPrinterMCPServer {
|
|
|
3499
3529
|
}
|
|
3500
3530
|
}
|
|
3501
3531
|
if (args?.use_ams === false) {
|
|
3502
|
-
|
|
3503
|
-
|
|
3532
|
+
if (!hasUserAmsMapping)
|
|
3533
|
+
finalAmsMapping = undefined;
|
|
3534
|
+
if (!hasUserAmsSlots)
|
|
3535
|
+
finalAmsSlots = undefined;
|
|
3504
3536
|
useAMS = false;
|
|
3505
3537
|
}
|
|
3506
3538
|
if ((!finalAmsMapping || finalAmsMapping.length === 0) && (!finalAmsSlots || finalAmsSlots.length === 0)) {
|
package/dist/printers/bambu.d.ts
CHANGED
|
@@ -60,10 +60,11 @@ export declare class BambuImplementation {
|
|
|
60
60
|
print3mf(host: string, serial: string, token: string, options: BambuPrintOptionsInternal): Promise<any>;
|
|
61
61
|
private print3mfPrepared;
|
|
62
62
|
cancelJob(host: string, serial: string, token: string, dispatch?: () => Promise<any>): Promise<any>;
|
|
63
|
+
private invokeEmergencyCommand;
|
|
63
64
|
/** Raw MQTT stop for routes that already ran the shared cancellation steps. */
|
|
64
|
-
sendStopOverMqtt(host: string, serial: string, token: string): Promise<any>;
|
|
65
|
+
sendStopOverMqtt(host: string, serial: string, token: string, signal?: AbortSignal): Promise<any>;
|
|
65
66
|
/** 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>;
|
|
67
|
+
sendHeaterOffOverMqtt(host: string, serial: string, token: string, heater: "bed" | "nozzle", signal?: AbortSignal): Promise<any>;
|
|
67
68
|
pauseJob(host: string, serial: string, token: string, dispatch?: () => Promise<any>): Promise<any>;
|
|
68
69
|
resumeJob(host: string, serial: string, token: string, dispatch?: (assertActive: () => void, beforeDispatch: () => Promise<void>) => Promise<any>): Promise<any>;
|
|
69
70
|
clearHmsErrors(host: string, serial: string, token: string, dispatch?: (assertActive: () => void, beforeDispatch: () => Promise<void>) => Promise<any>): Promise<any>;
|