bambu-printer-mcp 1.1.31 → 1.1.32
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 +2 -1
- package/README.md +3 -3
- package/dist/bambu-native.d.ts +2 -0
- package/dist/bambu-native.js +2 -0
- package/dist/index.js +30 -3
- package/dist/safety/printer-state.d.ts +8 -0
- package/dist/safety/printer-state.js +18 -0
- package/package.json +1 -1
- package/src/bambu-native.ts +4 -0
- package/src/index.ts +29 -3
- package/src/safety/printer-state.ts +19 -0
package/CONTRIBUTORS.md
CHANGED
|
@@ -6,7 +6,8 @@ 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. 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. |
|
|
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. In [#64](https://github.com/DMontgomery40/bambu-printer-mcp/pull/64), contributes ready-card native job storage, fresh dispatch rechecks and tests, and reports Linux X2D firmware 01.01.01.00 printing to external storage with plug-in 02.08.02.54 and a Bambuddy thumbnail; maintainer verification uses source and mocked transports. |
|
|
10
|
+
| [maziggy](https://github.com/maziggy) | Proposes choosing ready external storage by default in [Bambu Studio #12146](https://github.com/bambulab/BambuStudio/pull/12146), a related upstream proposal supporting the storage choice in [#64](https://github.com/DMontgomery40/bambu-printer-mcp/pull/64). The upstream proposal remains unmerged; this credit does not claim it shipped in Bambu Studio. |
|
|
10
11
|
| [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
12
|
| [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
13
|
| [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. |
|
package/README.md
CHANGED
|
@@ -196,7 +196,7 @@ The optional FULU **BambuNetwork bridge** exposes `bambu_network_bridge_status`,
|
|
|
196
196
|
- Capture a JPEG snapshot from the chamber camera. Supports A1, A1 mini, P1S, P1P (TCP-on-6000), and X1, X1C, X1E, P2S, H2, H2S, H2D, H2C, H2D Pro, X2D (RTSP via ffmpeg). Requires ffmpeg in PATH for the RTSP path.
|
|
197
197
|
- Upload and print pre-sliced `.3mf` projects with checked plate selection and calibration flags. Legacy `.gcode.3mf` routes require a single external-spool-only plate; see the [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
|
|
198
198
|
- Slice through BambuStudio CLI with automatic BBL inheritance/include resolution, per-slot filament colours, and fallback prime-tower placement for multi-nozzle printers. Missing dependencies stop the slice; custom settings and saved project tower positions are preserved. Multi-colour slicing is verified by the contributor on BambuStudio 02.08.02.60 for Windows; older CLI versions have separate limitations. See [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
|
|
199
|
-
- Recognize X2D status and slice with its own installed BambuStudio preset (`BAMBU_MODEL=x2d`). On macOS and x86-64 Linux, `print_3mf` selects the native
|
|
199
|
+
- Recognize X2D status and slice with its own installed BambuStudio preset (`BAMBU_MODEL=x2d`). On macOS and x86-64 Linux, `print_3mf` selects the native route after resolving the model, including an elicited model. Install the optional helper as described below; Windows native print requests, and Linux requests without the helper, fail before slicing or contacting the printer. Legacy FTPS and remote G-code starts remain unsupported for X2D.
|
|
200
200
|
- Parse AMS mapping from the 3MF's embedded slicer metadata (`Metadata/plate_<n>.json` + gcode filament header) and send it correctly formatted per the OpenBambuAPI spec, with correct H2S/H2D/H2C `ams_mapping2` parallel array format
|
|
201
201
|
- **Auto-match AMS slots by RFID** (`auto_match_ams` flag on `print_3mf`). Resolves required `tray_info_idx` from the sliced 3MF against live AMS inventory. Handles same-SKU different-color filaments by matching on `(tray_info_idx, tray_color)` and tracking already-claimed slots. Dry-run with `resolve_3mf_ams_slots` before printing.
|
|
202
202
|
- Cancel, pause, and resume in-progress print jobs via MQTT
|
|
@@ -906,7 +906,7 @@ The primary tool for starting a Bambu print. **Recommended input: a pre-sliced `
|
|
|
906
906
|
2. If no G-code is found, attempts to auto-slice via the configured slicer. Profile preparation or slicing failures stop the operation before upload. See the slicing guide for tested CLI versions and combinations.
|
|
907
907
|
3. Parses the sliced 3MF to extract the correct plate file and compute its MD5 hash.
|
|
908
908
|
4. Reads slicer metadata and any explicit AMS selection to build the filament mapping.
|
|
909
|
-
5. Uploads via `basic-ftp` to the model-specific location: SD root for H2/full-size A1, `cache/` for P1/X1/A1 mini/P2S. X2D `print_3mf` instead uses its checked native
|
|
909
|
+
5. Uploads via `basic-ftp` to the model-specific location: SD root for H2/full-size A1, `cache/` for P1/X1/A1 mini/P2S. X2D `print_3mf` instead uses its checked native route on macOS or Linux, which stores the job on a ready SD card or USB stick and otherwise on internal storage (eMMC).
|
|
910
910
|
6. Sends the correct MQTT print command for the target printer family. For H2S/H2D/H2C that means `project_file` with project-length `ams_mapping`, parallel `ams_mapping2`, and H2-compatible calibration flags.
|
|
911
911
|
|
|
912
912
|
```json
|
|
@@ -966,7 +966,7 @@ Use the same package version as the server, for example `bambu-printer-mcp@1.2.3
|
|
|
966
966
|
|
|
967
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.
|
|
968
968
|
|
|
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.
|
|
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. On both systems a print goes to the printer's SD card or USB stick when the printer reports one ready, so it can be reprinted from the printer and archived by tools that read the card; otherwise it goes to internal storage (eMMC), as Bambu Studio's Print button always does. `BAMBU_NATIVE_TRY_EMMC_PRINT=1` (or `0`) forces the choice. 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.
|
|
970
970
|
|
|
971
971
|
#### x2d_native_control
|
|
972
972
|
|
package/dist/bambu-native.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type BambuNativePrintOptions = {
|
|
|
19
19
|
vibrationCalibration?: boolean;
|
|
20
20
|
layerInspect?: boolean;
|
|
21
21
|
timelapse?: boolean;
|
|
22
|
+
/** true: the SD card or USB stick; false: internal storage (eMMC); unset: BAMBU_NATIVE_TRY_EMMC_PRINT or internal. Reported as the requested storage. */
|
|
23
|
+
externalStorage?: boolean;
|
|
22
24
|
};
|
|
23
25
|
export type BambuNativeControlOptions = {
|
|
24
26
|
host: string;
|
package/dist/bambu-native.js
CHANGED
|
@@ -748,6 +748,7 @@ export async function printWithBambuNative(options, onUpdate, execution) {
|
|
|
748
748
|
BAMBU_NATIVE_VIBRATION_CALIBRATION: boolEnv(options.vibrationCalibration, true),
|
|
749
749
|
BAMBU_NATIVE_LAYER_INSPECT: boolEnv(options.layerInspect, false),
|
|
750
750
|
BAMBU_NATIVE_TIMELAPSE: boolEnv(options.timelapse, false),
|
|
751
|
+
...(options.externalStorage === undefined ? {} : { BAMBU_NATIVE_TRY_EMMC_PRINT: String(!options.externalStorage) }),
|
|
751
752
|
}, 300000, onUpdate, execution);
|
|
752
753
|
if (result.resultCode !== 0) {
|
|
753
754
|
const explicitRejection = result.updates.some(line => /^native_print result=-?[1-9]\d*$/.test(line));
|
|
@@ -765,6 +766,7 @@ export async function printWithBambuNative(options, onUpdate, execution) {
|
|
|
765
766
|
return {
|
|
766
767
|
status: "success",
|
|
767
768
|
route: "bambu:///local",
|
|
769
|
+
...(options.externalStorage === undefined ? {} : { storage: options.externalStorage ? "external" : "internal" }),
|
|
768
770
|
updates: result.updates,
|
|
769
771
|
};
|
|
770
772
|
}
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { bridgeAmsMapping2Entry, bridgeAmsMappingValue, externalTrayForNozzle, h
|
|
|
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
|
-
import { validatePrinterState } from "./safety/printer-state.js";
|
|
21
|
+
import { externalStorageState, validatePrinterState } from "./safety/printer-state.js";
|
|
22
22
|
import { isDualNozzleModel } from "./safety/limits.js";
|
|
23
23
|
import { withPrinterOperation, withPrintSnapshot, uniquePrintName } from "./safety/artifact.js";
|
|
24
24
|
import { detectProfilesRoot } from "./slicer/profile-flatten.js";
|
|
@@ -67,6 +67,21 @@ function withEmergencyMqttTimeout(send) {
|
|
|
67
67
|
});
|
|
68
68
|
return Promise.race([Promise.resolve().then(() => send(controller.signal)), timeout]).finally(() => clearTimeout(timer));
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether an X2D native print stores its job on the SD card or USB stick
|
|
72
|
+
* (true) or internal storage (eMMC). An explicit try_emmc_print wins, then
|
|
73
|
+
* BAMBU_NATIVE_TRY_EMMC_PRINT read as the helper reads it; otherwise a ready
|
|
74
|
+
* card is used, as OrcaSlicer does, so the job can be reprinted from the
|
|
75
|
+
* printer and read over FTPS.
|
|
76
|
+
*/
|
|
77
|
+
function nativeExternalStorage(tryEmmcPrint, raw) {
|
|
78
|
+
if (tryEmmcPrint !== undefined)
|
|
79
|
+
return !tryEmmcPrint;
|
|
80
|
+
const configured = process.env.BAMBU_NATIVE_TRY_EMMC_PRINT?.trim();
|
|
81
|
+
if (configured)
|
|
82
|
+
return !["1", "true", "TRUE", "yes"].includes(configured);
|
|
83
|
+
return externalStorageState(raw) === "normal";
|
|
84
|
+
}
|
|
70
85
|
function requestedPrintNozzles(args) {
|
|
71
86
|
if (args.nozzle_diameters !== undefined && args.nozzle_diameter !== undefined) {
|
|
72
87
|
throw new Error("Supply nozzle_diameters or nozzle_diameter, not both.");
|
|
@@ -1136,6 +1151,9 @@ class BambuPrinterMCPServer {
|
|
|
1136
1151
|
if (!args?.three_mf_path) {
|
|
1137
1152
|
throw new Error("Missing required parameter: three_mf_path");
|
|
1138
1153
|
}
|
|
1154
|
+
if (args?.try_emmc_print !== undefined && typeof args.try_emmc_print !== "boolean") {
|
|
1155
|
+
throw new Error("try_emmc_print must be true or false.");
|
|
1156
|
+
}
|
|
1139
1157
|
const printModel = await this.resolveBambuModel(args?.bambu_model);
|
|
1140
1158
|
const printBedType = resolveBedType(args?.bed_type);
|
|
1141
1159
|
const printNozzles = requestedPrintNozzles(args);
|
|
@@ -1259,6 +1277,10 @@ class BambuPrinterMCPServer {
|
|
|
1259
1277
|
};
|
|
1260
1278
|
assertActive();
|
|
1261
1279
|
if (native) {
|
|
1280
|
+
const externalStorage = nativeExternalStorage(args?.try_emmc_print, confirmedStatus.raw);
|
|
1281
|
+
if (externalStorage && externalStorageState(confirmedStatus.raw) !== "normal") {
|
|
1282
|
+
throw new Error("External storage was requested, but the printer reports no ready SD card or USB stick.");
|
|
1283
|
+
}
|
|
1262
1284
|
// Release the telemetry client before the installed plug-in opens its session.
|
|
1263
1285
|
await this.bambu.disconnectAll();
|
|
1264
1286
|
assertActive();
|
|
@@ -1272,11 +1294,15 @@ class BambuPrinterMCPServer {
|
|
|
1272
1294
|
vibrationCalibration: params.task_vibration_cali,
|
|
1273
1295
|
layerInspect: params.task_layer_inspect,
|
|
1274
1296
|
timelapse: params.task_record_timelapse,
|
|
1297
|
+
externalStorage,
|
|
1275
1298
|
}, native.onUpdate, { signal: native.signal, assertActive, beforeDispatch: async () => {
|
|
1276
1299
|
assertActive();
|
|
1277
1300
|
const dispatchStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1278
1301
|
validatePrinterState(dispatchStatus, requirements);
|
|
1279
1302
|
this.bambu.assertBedClearance(dispatchStatus, bedClearance);
|
|
1303
|
+
if (externalStorage && externalStorageState(dispatchStatus.raw) !== "normal") {
|
|
1304
|
+
throw new Error("The printer's SD card or USB stick is no longer ready. Check it and start the print again.");
|
|
1305
|
+
}
|
|
1280
1306
|
assertActive();
|
|
1281
1307
|
} });
|
|
1282
1308
|
assertActive();
|
|
@@ -2082,7 +2108,7 @@ class BambuPrinterMCPServer {
|
|
|
2082
2108
|
use_ssl_for_ftp: { type: "boolean", description: "Whether FULU local print should use SSL for FTP (default: true)." },
|
|
2083
2109
|
use_ssl_for_mqtt: { type: "boolean", description: "Whether FULU local print should use SSL for MQTT (default: true)." },
|
|
2084
2110
|
external_change_assist: { type: "boolean", description: "Enable FULU PrintParams task_ext_change_assist for external filament change assistance." },
|
|
2085
|
-
try_emmc_print: { type: "boolean", description: "
|
|
2111
|
+
try_emmc_print: { type: "boolean", description: "Store the job on internal storage (eMMC) instead of the SD card or USB stick. FULU bridge: default false. X2D native printing: omit to use a ready card, else eMMC." },
|
|
2086
2112
|
extra_options: { type: "string", description: "Raw JSON string or text for FULU PrintParams extra_options." },
|
|
2087
2113
|
client_job_id: { type: "number", description: "Optional client job id sent to the bridge; defaults to the current timestamp." }
|
|
2088
2114
|
},
|
|
@@ -2458,8 +2484,9 @@ class BambuPrinterMCPServer {
|
|
|
2458
2484
|
connection_mode: {
|
|
2459
2485
|
type: "string",
|
|
2460
2486
|
enum: ["lan_mqtt_ftps", "bambu_network", "bambu_native", "bambu_connect"],
|
|
2461
|
-
description: "Print path: bambu_connect hands a printable file to the signed-in Bambu Connect app for cloud-mode review; X2D otherwise defaults to bambu_native (Bambu Studio's installed local networking plug-in
|
|
2487
|
+
description: "Print path: bambu_connect hands a printable file to the signed-in Bambu Connect app for cloud-mode review; X2D otherwise defaults to bambu_native (Bambu Studio's installed local networking plug-in, storing the job on a ready SD card or USB stick, otherwise internal eMMC); lan_mqtt_ftps is the legacy direct MQTT/FTPS path; bambu_network uses the FULU bridge."
|
|
2462
2488
|
},
|
|
2489
|
+
try_emmc_print: { type: "boolean", description: "X2D native printing: true stores the job on internal storage (eMMC), false on the SD card or USB stick (refused unless the printer reports one ready). Omit to use the card when ready, else eMMC; BAMBU_NATIVE_TRY_EMMC_PRINT sets a server default." },
|
|
2463
2490
|
connection_type: { type: "string", enum: ["cloud", "lan"], description: "BambuNetwork connection type when connection_mode is bambu_network; cloud uses restored internet printing, lan uses local bridge printing." },
|
|
2464
2491
|
bambu_network_method: { type: "string", enum: BAMBU_NETWORK_PRINT_METHODS, description: "FULU print method when connection_mode is bambu_network; defaults to start_print for cloud and start_local_print for lan." },
|
|
2465
2492
|
dev_id: { type: "string", description: "Bambu device id for FULU BambuNetwork printing; defaults to BAMBU_DEV_ID or BAMBU_SERIAL." },
|
|
@@ -51,4 +51,12 @@ export declare function hiddenHmsKeys(raw: unknown, serial: string, model: strin
|
|
|
51
51
|
export declare function validatePrinterState(status: any, requirements: PrinterStateRequirements): ValidatedPrinterState;
|
|
52
52
|
/** Manual M104 cannot remap filament slots. Validate the material already at the nozzle. */
|
|
53
53
|
export declare function manualHeatingRequirements(status: any, model: string, nozzleDiameter: number, material: string): PrinterStateRequirements;
|
|
54
|
+
export type ExternalStorageState = "none" | "normal" | "abnormal" | "readonly";
|
|
55
|
+
/**
|
|
56
|
+
* The SD card or USB stick state, read as Bambu Studio does: the `sdcard`
|
|
57
|
+
* flag, then `home_flag` bits 8-9, then on firmware that reports cfg/fun/aux/stat
|
|
58
|
+
* the `aux` bits 12-13, each later source overriding the earlier one
|
|
59
|
+
* (DevStorage.cpp, DeviceManager.cpp parse_home_flag and parse_new_info).
|
|
60
|
+
*/
|
|
61
|
+
export declare function externalStorageState(raw: unknown): ExternalStorageState;
|
|
54
62
|
export {};
|
|
@@ -451,3 +451,21 @@ export function manualHeatingRequirements(status, model, nozzleDiameter, materia
|
|
|
451
451
|
}
|
|
452
452
|
return requirements;
|
|
453
453
|
}
|
|
454
|
+
const EXTERNAL_STORAGE_STATES = ["none", "normal", "abnormal", "readonly"];
|
|
455
|
+
/**
|
|
456
|
+
* The SD card or USB stick state, read as Bambu Studio does: the `sdcard`
|
|
457
|
+
* flag, then `home_flag` bits 8-9, then on firmware that reports cfg/fun/aux/stat
|
|
458
|
+
* the `aux` bits 12-13, each later source overriding the earlier one
|
|
459
|
+
* (DevStorage.cpp, DeviceManager.cpp parse_home_flag and parse_new_info).
|
|
460
|
+
*/
|
|
461
|
+
export function externalStorageState(raw) {
|
|
462
|
+
if (!object(raw))
|
|
463
|
+
return "none";
|
|
464
|
+
let state = raw.sdcard === true ? "normal" : "none";
|
|
465
|
+
if (Number.isInteger(raw.home_flag))
|
|
466
|
+
state = EXTERNAL_STORAGE_STATES[(raw.home_flag >> 8) & 3];
|
|
467
|
+
if (["cfg", "fun", "aux", "stat"].every(key => key in raw) && typeof raw.aux === "string" && /^[0-9a-f]+$/i.test(raw.aux)) {
|
|
468
|
+
state = EXTERNAL_STORAGE_STATES[Number((BigInt(`0x${raw.aux}`) >> 12n) & 3n)];
|
|
469
|
+
}
|
|
470
|
+
return state;
|
|
471
|
+
}
|
package/package.json
CHANGED
package/src/bambu-native.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type BambuNativePrintOptions = {
|
|
|
26
26
|
vibrationCalibration?: boolean;
|
|
27
27
|
layerInspect?: boolean;
|
|
28
28
|
timelapse?: boolean;
|
|
29
|
+
/** true: the SD card or USB stick; false: internal storage (eMMC); unset: BAMBU_NATIVE_TRY_EMMC_PRINT or internal. Reported as the requested storage. */
|
|
30
|
+
externalStorage?: boolean;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export type BambuNativeControlOptions = {
|
|
@@ -864,6 +866,7 @@ export async function printWithBambuNative(
|
|
|
864
866
|
BAMBU_NATIVE_VIBRATION_CALIBRATION: boolEnv(options.vibrationCalibration, true),
|
|
865
867
|
BAMBU_NATIVE_LAYER_INSPECT: boolEnv(options.layerInspect, false),
|
|
866
868
|
BAMBU_NATIVE_TIMELAPSE: boolEnv(options.timelapse, false),
|
|
869
|
+
...(options.externalStorage === undefined ? {} : { BAMBU_NATIVE_TRY_EMMC_PRINT: String(!options.externalStorage) }),
|
|
867
870
|
},
|
|
868
871
|
300_000,
|
|
869
872
|
onUpdate,
|
|
@@ -885,6 +888,7 @@ export async function printWithBambuNative(
|
|
|
885
888
|
return {
|
|
886
889
|
status: "success",
|
|
887
890
|
route: "bambu:///local",
|
|
891
|
+
...(options.externalStorage === undefined ? {} : { storage: options.externalStorage ? "external" : "internal" }),
|
|
888
892
|
updates: result.updates,
|
|
889
893
|
};
|
|
890
894
|
}
|
package/src/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ import { analyze3MFAmsRequirements, analyze3MFPlateObjects, analyzeCollarCharm3M
|
|
|
45
45
|
import type { ThreeMFAmsRequirements } from "./types.js";
|
|
46
46
|
import { BambuImplementation, assertDirectPrintSupported } from "./printers/bambu.js";
|
|
47
47
|
import { inspectPrintFile } from "./safety/print-file.js";
|
|
48
|
-
import { validatePrinterState } from "./safety/printer-state.js";
|
|
48
|
+
import { externalStorageState, validatePrinterState } from "./safety/printer-state.js";
|
|
49
49
|
import { isDualNozzleModel } from "./safety/limits.js";
|
|
50
50
|
import { withPrinterOperation, withPrintSnapshot, uniquePrintName } from "./safety/artifact.js";
|
|
51
51
|
import { detectProfilesRoot } from "./slicer/profile-flatten.js";
|
|
@@ -101,6 +101,20 @@ function withEmergencyMqttTimeout<T>(send: (signal: AbortSignal) => Promise<T>):
|
|
|
101
101
|
return Promise.race([Promise.resolve().then(() => send(controller.signal)), timeout]).finally(() => clearTimeout(timer));
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Whether an X2D native print stores its job on the SD card or USB stick
|
|
106
|
+
* (true) or internal storage (eMMC). An explicit try_emmc_print wins, then
|
|
107
|
+
* BAMBU_NATIVE_TRY_EMMC_PRINT read as the helper reads it; otherwise a ready
|
|
108
|
+
* card is used, as OrcaSlicer does, so the job can be reprinted from the
|
|
109
|
+
* printer and read over FTPS.
|
|
110
|
+
*/
|
|
111
|
+
function nativeExternalStorage(tryEmmcPrint: boolean | undefined, raw: unknown): boolean {
|
|
112
|
+
if (tryEmmcPrint !== undefined) return !tryEmmcPrint;
|
|
113
|
+
const configured = process.env.BAMBU_NATIVE_TRY_EMMC_PRINT?.trim();
|
|
114
|
+
if (configured) return !["1", "true", "TRUE", "yes"].includes(configured);
|
|
115
|
+
return externalStorageState(raw) === "normal";
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
function requestedPrintNozzles(args: Record<string, any>): number[] | undefined {
|
|
105
119
|
if (args.nozzle_diameters !== undefined && args.nozzle_diameter !== undefined) {
|
|
106
120
|
throw new Error("Supply nozzle_diameters or nozzle_diameter, not both.");
|
|
@@ -1527,6 +1541,9 @@ class BambuPrinterMCPServer {
|
|
|
1527
1541
|
if (!args?.three_mf_path) {
|
|
1528
1542
|
throw new Error("Missing required parameter: three_mf_path");
|
|
1529
1543
|
}
|
|
1544
|
+
if (args?.try_emmc_print !== undefined && typeof args.try_emmc_print !== "boolean") {
|
|
1545
|
+
throw new Error("try_emmc_print must be true or false.");
|
|
1546
|
+
}
|
|
1530
1547
|
|
|
1531
1548
|
const printModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
|
|
1532
1549
|
const printBedType = resolveBedType(args?.bed_type as string | undefined);
|
|
@@ -1674,6 +1691,10 @@ class BambuPrinterMCPServer {
|
|
|
1674
1691
|
|
|
1675
1692
|
assertActive();
|
|
1676
1693
|
if (native) {
|
|
1694
|
+
const externalStorage = nativeExternalStorage(args?.try_emmc_print, confirmedStatus.raw);
|
|
1695
|
+
if (externalStorage && externalStorageState(confirmedStatus.raw) !== "normal") {
|
|
1696
|
+
throw new Error("External storage was requested, but the printer reports no ready SD card or USB stick.");
|
|
1697
|
+
}
|
|
1677
1698
|
// Release the telemetry client before the installed plug-in opens its session.
|
|
1678
1699
|
await this.bambu.disconnectAll();
|
|
1679
1700
|
assertActive();
|
|
@@ -1687,11 +1708,15 @@ class BambuPrinterMCPServer {
|
|
|
1687
1708
|
vibrationCalibration: params.task_vibration_cali as boolean,
|
|
1688
1709
|
layerInspect: params.task_layer_inspect as boolean,
|
|
1689
1710
|
timelapse: params.task_record_timelapse as boolean,
|
|
1711
|
+
externalStorage,
|
|
1690
1712
|
}, native.onUpdate, { signal: native.signal, assertActive, beforeDispatch: async () => {
|
|
1691
1713
|
assertActive();
|
|
1692
1714
|
const dispatchStatus = await this.bambu.getSafetyStatus(devIp, devId, password || bambuToken);
|
|
1693
1715
|
validatePrinterState(dispatchStatus, requirements);
|
|
1694
1716
|
this.bambu.assertBedClearance(dispatchStatus, bedClearance);
|
|
1717
|
+
if (externalStorage && externalStorageState(dispatchStatus.raw) !== "normal") {
|
|
1718
|
+
throw new Error("The printer's SD card or USB stick is no longer ready. Check it and start the print again.");
|
|
1719
|
+
}
|
|
1695
1720
|
assertActive();
|
|
1696
1721
|
} });
|
|
1697
1722
|
assertActive();
|
|
@@ -2611,7 +2636,7 @@ class BambuPrinterMCPServer {
|
|
|
2611
2636
|
use_ssl_for_ftp: { type: "boolean", description: "Whether FULU local print should use SSL for FTP (default: true)." },
|
|
2612
2637
|
use_ssl_for_mqtt: { type: "boolean", description: "Whether FULU local print should use SSL for MQTT (default: true)." },
|
|
2613
2638
|
external_change_assist: { type: "boolean", description: "Enable FULU PrintParams task_ext_change_assist for external filament change assistance." },
|
|
2614
|
-
try_emmc_print: { type: "boolean", description: "
|
|
2639
|
+
try_emmc_print: { type: "boolean", description: "Store the job on internal storage (eMMC) instead of the SD card or USB stick. FULU bridge: default false. X2D native printing: omit to use a ready card, else eMMC." },
|
|
2615
2640
|
extra_options: { type: "string", description: "Raw JSON string or text for FULU PrintParams extra_options." },
|
|
2616
2641
|
client_job_id: { type: "number", description: "Optional client job id sent to the bridge; defaults to the current timestamp." }
|
|
2617
2642
|
},
|
|
@@ -2987,8 +3012,9 @@ class BambuPrinterMCPServer {
|
|
|
2987
3012
|
connection_mode: {
|
|
2988
3013
|
type: "string",
|
|
2989
3014
|
enum: ["lan_mqtt_ftps", "bambu_network", "bambu_native", "bambu_connect"],
|
|
2990
|
-
description: "Print path: bambu_connect hands a printable file to the signed-in Bambu Connect app for cloud-mode review; X2D otherwise defaults to bambu_native (Bambu Studio's installed local networking plug-in
|
|
3015
|
+
description: "Print path: bambu_connect hands a printable file to the signed-in Bambu Connect app for cloud-mode review; X2D otherwise defaults to bambu_native (Bambu Studio's installed local networking plug-in, storing the job on a ready SD card or USB stick, otherwise internal eMMC); lan_mqtt_ftps is the legacy direct MQTT/FTPS path; bambu_network uses the FULU bridge."
|
|
2991
3016
|
},
|
|
3017
|
+
try_emmc_print: { type: "boolean", description: "X2D native printing: true stores the job on internal storage (eMMC), false on the SD card or USB stick (refused unless the printer reports one ready). Omit to use the card when ready, else eMMC; BAMBU_NATIVE_TRY_EMMC_PRINT sets a server default." },
|
|
2992
3018
|
connection_type: { type: "string", enum: ["cloud", "lan"], description: "BambuNetwork connection type when connection_mode is bambu_network; cloud uses restored internet printing, lan uses local bridge printing." },
|
|
2993
3019
|
bambu_network_method: { type: "string", enum: BAMBU_NETWORK_PRINT_METHODS, description: "FULU print method when connection_mode is bambu_network; defaults to start_print for cloud and start_local_print for lan." },
|
|
2994
3020
|
dev_id: { type: "string", description: "Bambu device id for FULU BambuNetwork printing; defaults to BAMBU_DEV_ID or BAMBU_SERIAL." },
|
|
@@ -424,3 +424,22 @@ export function manualHeatingRequirements(status: any, model: string, nozzleDiam
|
|
|
424
424
|
if (tray < 254) { requirements.useAMS = true; requirements.amsMapping = [tray]; }
|
|
425
425
|
return requirements;
|
|
426
426
|
}
|
|
427
|
+
|
|
428
|
+
export type ExternalStorageState = "none" | "normal" | "abnormal" | "readonly";
|
|
429
|
+
const EXTERNAL_STORAGE_STATES: readonly ExternalStorageState[] = ["none", "normal", "abnormal", "readonly"];
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The SD card or USB stick state, read as Bambu Studio does: the `sdcard`
|
|
433
|
+
* flag, then `home_flag` bits 8-9, then on firmware that reports cfg/fun/aux/stat
|
|
434
|
+
* the `aux` bits 12-13, each later source overriding the earlier one
|
|
435
|
+
* (DevStorage.cpp, DeviceManager.cpp parse_home_flag and parse_new_info).
|
|
436
|
+
*/
|
|
437
|
+
export function externalStorageState(raw: unknown): ExternalStorageState {
|
|
438
|
+
if (!object(raw)) return "none";
|
|
439
|
+
let state: ExternalStorageState = raw.sdcard === true ? "normal" : "none";
|
|
440
|
+
if (Number.isInteger(raw.home_flag)) state = EXTERNAL_STORAGE_STATES[(raw.home_flag >> 8) & 3];
|
|
441
|
+
if (["cfg", "fun", "aux", "stat"].every(key => key in raw) && typeof raw.aux === "string" && /^[0-9a-f]+$/i.test(raw.aux)) {
|
|
442
|
+
state = EXTERNAL_STORAGE_STATES[Number((BigInt(`0x${raw.aux}`) >> 12n) & 3n)];
|
|
443
|
+
}
|
|
444
|
+
return state;
|
|
445
|
+
}
|