mcp-3d-printer-server 1.2.28 → 1.2.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/CHANGELOG.md +6 -0
- package/dist/bambu-network-bridge.d.ts +8 -2
- package/dist/bambu-network-bridge.js +25 -3
- package/dist/index.js +22 -9
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/bambu-network-bridge.ts +25 -3
- package/src/index.ts +22 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.29] – 2026-11-04
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- With no method given, a FULU print over Bambu Cloud goes the way Bambu Studio sends it to a cloud-bound printer it can reach on the LAN (`PrintJob.cpp`): `start_local_print_with_record`, which uploads to the printer over the LAN and records the task in the account, in the account's cloud session. When that fails before the print command (the plug-in's `-2010` to `-2140` preparation results, other than a missing or oversized file), the same checked job goes once through `start_print`, with the printer checked again first. Any other failure may have reached the printer and is not sent again. A printer that reports no storage goes straight to `start_print`, as in Studio. Same change as bambu-printer-mcp 1.1.55. On a P1S with firmware 01.10.00.00, `start_print` alone returns `-3120`, HTTP 403 "internal blocking", every time. Errors name `-2120`, `-2130` and `-3120`. Checked with the stand-in host and intercepted transports only; not yet on a printer.
|
|
8
|
+
|
|
3
9
|
## [1.2.28] – 2026-11-04
|
|
4
10
|
|
|
5
11
|
### Added
|
|
@@ -186,10 +186,16 @@ export declare class BambuNetworkBridge {
|
|
|
186
186
|
* Shares the LAN session queue: one reader of the plug-in's events at a time.
|
|
187
187
|
*/
|
|
188
188
|
withCloudSession<T>(devId: string, options: BambuNetworkBridgeOptions, assertActive: () => void, timing: Readonly<CloudTiming>, run: (session: CloudSession) => Promise<T>): Promise<T>;
|
|
189
|
-
/**
|
|
190
|
-
|
|
189
|
+
/**
|
|
190
|
+
* A cloud print in a cloud session, so the plug-in has the printer's state. With a fallback this is
|
|
191
|
+
* Bambu Studio's order for a cloud-bound printer it can also reach on the LAN (PrintJob.cpp,
|
|
192
|
+
* SendAttemptRoute::LanWithRecord, then ::Cloud): start_local_print_with_record, then start_print
|
|
193
|
+
* when the first failed before its print command was sent. `firstFailure` is the first method's result.
|
|
194
|
+
*/
|
|
195
|
+
startCloudPrint(devId: string, method: string, payload: JsonObject, options?: BambuNetworkBridgeOptions, assertActive?: () => void, timing?: Readonly<CloudTiming>, fallbackMethod?: string): Promise<{
|
|
191
196
|
result: unknown;
|
|
192
197
|
jobUpdates: BridgeJobUpdate[];
|
|
198
|
+
firstFailure?: number;
|
|
193
199
|
}>;
|
|
194
200
|
/**
|
|
195
201
|
* A print control over Bambu Cloud: how Bambu Studio and FULU's slicer control a printer
|
|
@@ -21,8 +21,16 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 120000;
|
|
|
21
21
|
const LOCAL_CONNECT_STATUS = ["ok", "failed", "lost"];
|
|
22
22
|
// bambu_networking.hpp BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED.
|
|
23
23
|
const LOCAL_PRINT_PUBLISH_FAILED = -4030;
|
|
24
|
+
// A with-record send falls back to start_print only on its own preparation failures (the plug-in's
|
|
25
|
+
// PRINT_WR_* results, -2010 to -2140: project, upload, task record, FTPS), which come before the print
|
|
26
|
+
// command, so the printer has no job. Any other result may have reached the printer and is never
|
|
27
|
+
// replayed. Of those, Studio also stops on a file missing or too large (should_stop_without_fallback).
|
|
28
|
+
const WITH_RECORD_PREPARATION_FAILURE = (value) => value <= -2010 && value >= -2140 && value !== -2040 && value !== -2100;
|
|
24
29
|
// bambu_networking.hpp print errors that name a recoverable cause.
|
|
25
30
|
const BAMBU_NETWORK_PRINT_ERRORS = {
|
|
31
|
+
[-2120]: "Bambu Cloud refused to create the print task",
|
|
32
|
+
[-2130]: "FTPS upload to the printer failed",
|
|
33
|
+
[-3120]: "Bambu Cloud refused to create the print task",
|
|
26
34
|
[-4010]: "the file exceeds the 1 GB upload limit",
|
|
27
35
|
[-4020]: "FTPS upload to the printer failed",
|
|
28
36
|
[LOCAL_PRINT_PUBLISH_FAILED]: "the networking plug-in could not send the print command to the printer over MQTT",
|
|
@@ -703,9 +711,23 @@ export class BambuNetworkBridge {
|
|
|
703
711
|
release();
|
|
704
712
|
}
|
|
705
713
|
}
|
|
706
|
-
/**
|
|
707
|
-
|
|
708
|
-
|
|
714
|
+
/**
|
|
715
|
+
* A cloud print in a cloud session, so the plug-in has the printer's state. With a fallback this is
|
|
716
|
+
* Bambu Studio's order for a cloud-bound printer it can also reach on the LAN (PrintJob.cpp,
|
|
717
|
+
* SendAttemptRoute::LanWithRecord, then ::Cloud): start_local_print_with_record, then start_print
|
|
718
|
+
* when the first failed before its print command was sent. `firstFailure` is the first method's result.
|
|
719
|
+
*/
|
|
720
|
+
async startCloudPrint(devId, method, payload, options = {}, assertActive = () => { }, timing = CLOUD_PRINT_TIMING, fallbackMethod) {
|
|
721
|
+
return await this.withCloudSession(devId, options, assertActive, timing, async (session) => {
|
|
722
|
+
const result = await this.callWithAgent(method, payload, options);
|
|
723
|
+
const value = bridgeNumericResult(result);
|
|
724
|
+
if (!fallbackMethod || value === undefined || !WITH_RECORD_PREPARATION_FAILURE(value))
|
|
725
|
+
return { result, jobUpdates: session.jobUpdates };
|
|
726
|
+
options.trace?.({ step: "fallback", from: method, to: fallbackMethod, result: value });
|
|
727
|
+
const params = payload.params;
|
|
728
|
+
const fallbackPayload = params ? { ...payload, params: { ...params, comments: `failed(${value})` } } : payload;
|
|
729
|
+
return { result: await this.callWithAgent(fallbackMethod, fallbackPayload, options), jobUpdates: session.jobUpdates, firstFailure: value };
|
|
730
|
+
});
|
|
709
731
|
}
|
|
710
732
|
/**
|
|
711
733
|
* A print control over Bambu Cloud: how Bambu Studio and FULU's slicer control a printer
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ import { BambuNetworkBridge, describeBambuNetworkPrintError } from "./bambu-netw
|
|
|
34
34
|
import { PRINT_ROUTES, connectionForRoute, routeOf, routeOptions, writeEnvValues } from "./counterpart/route.js";
|
|
35
35
|
import { resolveStudioPrintOptions, usedNozzleCount } from "./studio-print-options.js";
|
|
36
36
|
import { inspectPrintFile } from "./safety/print-file.js";
|
|
37
|
-
import { validatePrinterState } from "./safety/printer-state.js";
|
|
37
|
+
import { externalStorageState, validatePrinterState } from "./safety/printer-state.js";
|
|
38
38
|
import { uniquePrintName, withPrinterOperation, withPrintSnapshot } from "./safety/artifact.js";
|
|
39
39
|
import { checkedFile } from "./printers/bambu.js";
|
|
40
40
|
import { CONFIRMATION_UNAVAILABLE, confirmationTimedOut, confirmationTimeoutMs } from "./safety/confirmation.js";
|
|
@@ -881,8 +881,10 @@ class ThreeDPrinterMCPServer {
|
|
|
881
881
|
throw new Error("FULU BambuNetwork printing requires bambu_serial, the printer host and its LAN access code: fresh printer state is checked over MQTT before every bridge print, including cloud dispatch.");
|
|
882
882
|
}
|
|
883
883
|
const isLocalBridgePrint = connectionType === "lan";
|
|
884
|
-
|
|
885
|
-
|
|
884
|
+
// Bambu Studio sends a cloud-bound printer it can reach on the LAN with record first, in the cloud
|
|
885
|
+
// session, then start_print (PrintJob.cpp). This server always has the printer's IP and access code.
|
|
886
|
+
let bridgeMethod = isLocalBridgePrint ? "net.start_local_print" : "net.start_local_print_with_record";
|
|
887
|
+
let cloudFallbackMethod = isLocalBridgePrint ? undefined : "net.start_print";
|
|
886
888
|
const { plateIndex } = options;
|
|
887
889
|
const threeMfFilename = path.basename(options.threeMFPath);
|
|
888
890
|
const projectName = threeMfFilename.replace(/\.3mf$/i, "");
|
|
@@ -923,6 +925,11 @@ class ThreeDPrinterMCPServer {
|
|
|
923
925
|
validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
|
|
924
926
|
const confirmedStatus = await bambu.getSafetyStatus(devIp, devId, password);
|
|
925
927
|
validatePrinterState(confirmedStatus, requirements);
|
|
928
|
+
if (cloudFallbackMethod && externalStorageState(confirmedStatus.raw) !== "normal") {
|
|
929
|
+
// Studio sends straight through the cloud when the printer has no storage for the LAN upload.
|
|
930
|
+
bridgeMethod = cloudFallbackMethod;
|
|
931
|
+
cloudFallbackMethod = undefined;
|
|
932
|
+
}
|
|
926
933
|
const wireMapping = mapping.map((value) => bridgeAmsMappingValue(value, dualNozzle));
|
|
927
934
|
const remoteFileName = uniquePrintName(options.threeMFPath);
|
|
928
935
|
const checkedName = remoteFileName.replace(/(?:\.gcode)?\.3mf$/i, "");
|
|
@@ -979,23 +986,29 @@ class ThreeDPrinterMCPServer {
|
|
|
979
986
|
const bridgeOptions = {
|
|
980
987
|
// Trusted in-process preflight immediately before the print frame.
|
|
981
988
|
beforeDispatch: async (method) => {
|
|
982
|
-
if (method !== bridgeMethod)
|
|
989
|
+
if (method !== bridgeMethod && method !== cloudFallbackMethod)
|
|
983
990
|
return;
|
|
984
991
|
assertActive();
|
|
985
992
|
validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
|
|
986
993
|
},
|
|
987
|
-
assertDispatchAllowed: (method) => { if (method === bridgeMethod)
|
|
994
|
+
assertDispatchAllowed: (method) => { if (method === bridgeMethod || method === cloudFallbackMethod)
|
|
988
995
|
assertActive(); },
|
|
989
|
-
onDispatched: (method) => { if (method === bridgeMethod)
|
|
996
|
+
onDispatched: (method) => { if (method === bridgeMethod || method === cloudFallbackMethod)
|
|
990
997
|
retainForDispatchedUpload(); },
|
|
991
998
|
};
|
|
992
999
|
let jobUpdates = [];
|
|
993
1000
|
let bridgeResult;
|
|
1001
|
+
let firstAttempt = "";
|
|
994
1002
|
if (isLocalBridgePrint) {
|
|
995
1003
|
({ result: bridgeResult, jobUpdates } = await this.bambuNetwork.startLocalPrint({ devId, devIp, username: "bblp", password, useSsl: true }, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive));
|
|
996
1004
|
}
|
|
997
1005
|
else {
|
|
998
|
-
|
|
1006
|
+
let firstFailure;
|
|
1007
|
+
({ result: bridgeResult, jobUpdates, firstFailure } = await this.bambuNetwork.startCloudPrint(devId, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive, undefined, cloudFallbackMethod));
|
|
1008
|
+
if (firstFailure !== undefined) {
|
|
1009
|
+
bridgeMethod = cloudFallbackMethod;
|
|
1010
|
+
firstAttempt = ` It first tried net.start_local_print_with_record, as Bambu Studio does, which returned ${firstFailure}${describeBambuNetworkPrintError(firstFailure)}.`;
|
|
1011
|
+
}
|
|
999
1012
|
}
|
|
1000
1013
|
if (typeof bridgeResult === "object" && bridgeResult !== null && bridgeResult.ok === false) {
|
|
1001
1014
|
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} failed: ${String(bridgeResult.error || "unknown bridge error")}`);
|
|
@@ -1005,14 +1018,14 @@ class ThreeDPrinterMCPServer {
|
|
|
1005
1018
|
const reported = jobUpdates.filter((update) => update.code !== 0 || update.msg).slice(-3)
|
|
1006
1019
|
.map((update) => `status ${update.status}, code ${update.code}${update.msg ? `: ${update.msg}` : ""}`).join("; ");
|
|
1007
1020
|
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} returned non-zero result ${value}${describeBambuNetworkPrintError(value)}.` +
|
|
1008
|
-
(reported ? ` Plug-in reported: ${reported}.` : ""));
|
|
1021
|
+
(reported ? ` Plug-in reported: ${reported}.` : "") + firstAttempt);
|
|
1009
1022
|
}
|
|
1010
1023
|
assertActive();
|
|
1011
1024
|
// Bambu's plug-in placed the file, so a resume re-reads it if it can and otherwise asks a person.
|
|
1012
1025
|
bambu.recordCheckedJob(devIp, devId, remoteFileName, requirements, resumeFile, false);
|
|
1013
1026
|
return {
|
|
1014
1027
|
status: "success",
|
|
1015
|
-
message: `FULU BambuNetwork ${
|
|
1028
|
+
message: `FULU BambuNetwork ${bridgeMethod.replace(/^net\./, "")} command for ${threeMfFilename} sent successfully.${firstAttempt}`,
|
|
1016
1029
|
connectionMode: "bambu_network",
|
|
1017
1030
|
connectionType,
|
|
1018
1031
|
bridgeMethod,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-3d-printer-server",
|
|
3
3
|
"mcpName": "io.github.DMontgomery40/mcp-3d-printer-server",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.29",
|
|
5
5
|
"description": "MCP server for connecting Claude with 3D printer management systems",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"repository": "https://github.com/DMontgomery40/mcp-3D-printer-server",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/DMontgomery40/mcp-3D-printer-server",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.2.
|
|
10
|
+
"version": "1.2.29",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "mcp-3d-printer-server",
|
|
15
|
-
"version": "1.2.
|
|
15
|
+
"version": "1.2.29",
|
|
16
16
|
"runtimeHint": "npx",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
@@ -23,8 +23,16 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
|
|
|
23
23
|
const LOCAL_CONNECT_STATUS = ["ok", "failed", "lost"];
|
|
24
24
|
// bambu_networking.hpp BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED.
|
|
25
25
|
const LOCAL_PRINT_PUBLISH_FAILED = -4030;
|
|
26
|
+
// A with-record send falls back to start_print only on its own preparation failures (the plug-in's
|
|
27
|
+
// PRINT_WR_* results, -2010 to -2140: project, upload, task record, FTPS), which come before the print
|
|
28
|
+
// command, so the printer has no job. Any other result may have reached the printer and is never
|
|
29
|
+
// replayed. Of those, Studio also stops on a file missing or too large (should_stop_without_fallback).
|
|
30
|
+
const WITH_RECORD_PREPARATION_FAILURE = (value: number) => value <= -2010 && value >= -2140 && value !== -2040 && value !== -2100;
|
|
26
31
|
// bambu_networking.hpp print errors that name a recoverable cause.
|
|
27
32
|
const BAMBU_NETWORK_PRINT_ERRORS: Record<number, string> = {
|
|
33
|
+
[-2120]: "Bambu Cloud refused to create the print task",
|
|
34
|
+
[-2130]: "FTPS upload to the printer failed",
|
|
35
|
+
[-3120]: "Bambu Cloud refused to create the print task",
|
|
28
36
|
[-4010]: "the file exceeds the 1 GB upload limit",
|
|
29
37
|
[-4020]: "FTPS upload to the printer failed",
|
|
30
38
|
[LOCAL_PRINT_PUBLISH_FAILED]: "the networking plug-in could not send the print command to the printer over MQTT",
|
|
@@ -853,10 +861,24 @@ export class BambuNetworkBridge {
|
|
|
853
861
|
}
|
|
854
862
|
}
|
|
855
863
|
|
|
856
|
-
/**
|
|
864
|
+
/**
|
|
865
|
+
* A cloud print in a cloud session, so the plug-in has the printer's state. With a fallback this is
|
|
866
|
+
* Bambu Studio's order for a cloud-bound printer it can also reach on the LAN (PrintJob.cpp,
|
|
867
|
+
* SendAttemptRoute::LanWithRecord, then ::Cloud): start_local_print_with_record, then start_print
|
|
868
|
+
* when the first failed before its print command was sent. `firstFailure` is the first method's result.
|
|
869
|
+
*/
|
|
857
870
|
async startCloudPrint(devId: string, method: string, payload: JsonObject, options: BambuNetworkBridgeOptions = {},
|
|
858
|
-
assertActive: () => void = () => {}, timing: Readonly<CloudTiming> = CLOUD_PRINT_TIMING
|
|
859
|
-
|
|
871
|
+
assertActive: () => void = () => {}, timing: Readonly<CloudTiming> = CLOUD_PRINT_TIMING, fallbackMethod?: string
|
|
872
|
+
): Promise<{ result: unknown; jobUpdates: BridgeJobUpdate[]; firstFailure?: number }> {
|
|
873
|
+
return await this.withCloudSession(devId, options, assertActive, timing, async session => {
|
|
874
|
+
const result = await this.callWithAgent(method, payload, options);
|
|
875
|
+
const value = bridgeNumericResult(result);
|
|
876
|
+
if (!fallbackMethod || value === undefined || !WITH_RECORD_PREPARATION_FAILURE(value)) return { result, jobUpdates: session.jobUpdates };
|
|
877
|
+
options.trace?.({ step: "fallback", from: method, to: fallbackMethod, result: value });
|
|
878
|
+
const params = (payload as { params?: JsonObject }).params;
|
|
879
|
+
const fallbackPayload = params ? { ...payload, params: { ...params, comments: `failed(${value})` } } : payload;
|
|
880
|
+
return { result: await this.callWithAgent(fallbackMethod, fallbackPayload, options), jobUpdates: session.jobUpdates, firstFailure: value };
|
|
881
|
+
});
|
|
860
882
|
}
|
|
861
883
|
|
|
862
884
|
/**
|
package/src/index.ts
CHANGED
|
@@ -75,7 +75,7 @@ import { BambuNetworkBridge, describeBambuNetworkPrintError, type BridgeJobUpdat
|
|
|
75
75
|
import { PRINT_ROUTES, connectionForRoute, routeOf, routeOptions, writeEnvValues, type PrintRoute } from "./counterpart/route.js";
|
|
76
76
|
import { resolveStudioPrintOptions, usedNozzleCount, type CallerPrintOptions } from "./studio-print-options.js";
|
|
77
77
|
import { inspectPrintFile } from "./safety/print-file.js";
|
|
78
|
-
import { validatePrinterState } from "./safety/printer-state.js";
|
|
78
|
+
import { externalStorageState, validatePrinterState } from "./safety/printer-state.js";
|
|
79
79
|
import { uniquePrintName, withPrinterOperation, withPrintSnapshot } from "./safety/artifact.js";
|
|
80
80
|
import { checkedFile } from "./printers/bambu.js";
|
|
81
81
|
import { CONFIRMATION_UNAVAILABLE, confirmationTimedOut, confirmationTimeoutMs } from "./safety/confirmation.js";
|
|
@@ -1074,8 +1074,10 @@ class ThreeDPrinterMCPServer {
|
|
|
1074
1074
|
throw new Error("FULU BambuNetwork printing requires bambu_serial, the printer host and its LAN access code: fresh printer state is checked over MQTT before every bridge print, including cloud dispatch.");
|
|
1075
1075
|
}
|
|
1076
1076
|
const isLocalBridgePrint = connectionType === "lan";
|
|
1077
|
-
|
|
1078
|
-
|
|
1077
|
+
// Bambu Studio sends a cloud-bound printer it can reach on the LAN with record first, in the cloud
|
|
1078
|
+
// session, then start_print (PrintJob.cpp). This server always has the printer's IP and access code.
|
|
1079
|
+
let bridgeMethod = isLocalBridgePrint ? "net.start_local_print" : "net.start_local_print_with_record";
|
|
1080
|
+
let cloudFallbackMethod = isLocalBridgePrint ? undefined : "net.start_print";
|
|
1079
1081
|
const { plateIndex } = options;
|
|
1080
1082
|
const threeMfFilename = path.basename(options.threeMFPath);
|
|
1081
1083
|
const projectName = threeMfFilename.replace(/\.3mf$/i, "");
|
|
@@ -1116,6 +1118,11 @@ class ThreeDPrinterMCPServer {
|
|
|
1116
1118
|
validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
|
|
1117
1119
|
const confirmedStatus = await bambu.getSafetyStatus(devIp, devId, password);
|
|
1118
1120
|
validatePrinterState(confirmedStatus, requirements);
|
|
1121
|
+
if (cloudFallbackMethod && externalStorageState(confirmedStatus.raw) !== "normal") {
|
|
1122
|
+
// Studio sends straight through the cloud when the printer has no storage for the LAN upload.
|
|
1123
|
+
bridgeMethod = cloudFallbackMethod;
|
|
1124
|
+
cloudFallbackMethod = undefined;
|
|
1125
|
+
}
|
|
1119
1126
|
const wireMapping = mapping!.map((value) => bridgeAmsMappingValue(value, dualNozzle));
|
|
1120
1127
|
const remoteFileName = uniquePrintName(options.threeMFPath);
|
|
1121
1128
|
const checkedName = remoteFileName.replace(/(?:\.gcode)?\.3mf$/i, "");
|
|
@@ -1172,22 +1179,28 @@ class ThreeDPrinterMCPServer {
|
|
|
1172
1179
|
const bridgeOptions = {
|
|
1173
1180
|
// Trusted in-process preflight immediately before the print frame.
|
|
1174
1181
|
beforeDispatch: async (method: string) => {
|
|
1175
|
-
if (method !== bridgeMethod) return;
|
|
1182
|
+
if (method !== bridgeMethod && method !== cloudFallbackMethod) return;
|
|
1176
1183
|
assertActive();
|
|
1177
1184
|
validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
|
|
1178
1185
|
},
|
|
1179
|
-
assertDispatchAllowed: (method: string) => { if (method === bridgeMethod) assertActive(); },
|
|
1180
|
-
onDispatched: (method: string) => { if (method === bridgeMethod) retainForDispatchedUpload(); },
|
|
1186
|
+
assertDispatchAllowed: (method: string) => { if (method === bridgeMethod || method === cloudFallbackMethod) assertActive(); },
|
|
1187
|
+
onDispatched: (method: string) => { if (method === bridgeMethod || method === cloudFallbackMethod) retainForDispatchedUpload(); },
|
|
1181
1188
|
};
|
|
1182
1189
|
let jobUpdates: BridgeJobUpdate[] = [];
|
|
1183
1190
|
let bridgeResult: unknown;
|
|
1191
|
+
let firstAttempt = "";
|
|
1184
1192
|
if (isLocalBridgePrint) {
|
|
1185
1193
|
({ result: bridgeResult, jobUpdates } = await this.bambuNetwork.startLocalPrint(
|
|
1186
1194
|
{ devId, devIp, username: "bblp", password, useSsl: true },
|
|
1187
1195
|
bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive
|
|
1188
1196
|
));
|
|
1189
1197
|
} else {
|
|
1190
|
-
|
|
1198
|
+
let firstFailure: number | undefined;
|
|
1199
|
+
({ result: bridgeResult, jobUpdates, firstFailure } = await this.bambuNetwork.startCloudPrint(devId, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive, undefined, cloudFallbackMethod));
|
|
1200
|
+
if (firstFailure !== undefined) {
|
|
1201
|
+
bridgeMethod = cloudFallbackMethod!;
|
|
1202
|
+
firstAttempt = ` It first tried net.start_local_print_with_record, as Bambu Studio does, which returned ${firstFailure}${describeBambuNetworkPrintError(firstFailure)}.`;
|
|
1203
|
+
}
|
|
1191
1204
|
}
|
|
1192
1205
|
if (typeof bridgeResult === "object" && bridgeResult !== null && (bridgeResult as { ok?: unknown }).ok === false) {
|
|
1193
1206
|
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} failed: ${String((bridgeResult as { error?: unknown }).error || "unknown bridge error")}`);
|
|
@@ -1197,14 +1210,14 @@ class ThreeDPrinterMCPServer {
|
|
|
1197
1210
|
const reported = jobUpdates.filter((update) => update.code !== 0 || update.msg).slice(-3)
|
|
1198
1211
|
.map((update) => `status ${update.status}, code ${update.code}${update.msg ? `: ${update.msg}` : ""}`).join("; ");
|
|
1199
1212
|
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} returned non-zero result ${value}${describeBambuNetworkPrintError(value)}.` +
|
|
1200
|
-
(reported ? ` Plug-in reported: ${reported}.` : ""));
|
|
1213
|
+
(reported ? ` Plug-in reported: ${reported}.` : "") + firstAttempt);
|
|
1201
1214
|
}
|
|
1202
1215
|
assertActive();
|
|
1203
1216
|
// Bambu's plug-in placed the file, so a resume re-reads it if it can and otherwise asks a person.
|
|
1204
1217
|
bambu.recordCheckedJob(devIp, devId, remoteFileName, requirements, resumeFile, false);
|
|
1205
1218
|
return {
|
|
1206
1219
|
status: "success",
|
|
1207
|
-
message: `FULU BambuNetwork ${
|
|
1220
|
+
message: `FULU BambuNetwork ${bridgeMethod.replace(/^net\./, "")} command for ${threeMfFilename} sent successfully.${firstAttempt}`,
|
|
1208
1221
|
connectionMode: "bambu_network",
|
|
1209
1222
|
connectionType,
|
|
1210
1223
|
bridgeMethod,
|