mcp-3d-printer-server 1.2.24 → 1.2.26
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 +71 -0
- package/CONTRIBUTORS.md +5 -1
- package/README.md +64 -29
- package/dist/ams-mapping.d.ts +38 -0
- package/dist/ams-mapping.js +125 -0
- package/dist/bambu-network-bridge.d.ts +200 -0
- package/dist/bambu-network-bridge.js +881 -0
- package/dist/bambu-network-certs.d.ts +13 -0
- package/dist/bambu-network-certs.js +152 -0
- package/dist/control-reply.d.ts +21 -0
- package/dist/control-reply.js +44 -0
- package/dist/counterpart/camera-sources.d.ts +16 -0
- package/dist/counterpart/camera-sources.js +138 -0
- package/dist/counterpart/camera-stream.d.ts +66 -0
- package/dist/counterpart/camera-stream.js +246 -0
- package/dist/counterpart/camera-tools.d.ts +247 -0
- package/dist/counterpart/camera-tools.js +35 -0
- package/dist/counterpart/contract.d.ts +21 -5
- package/dist/counterpart/contract.js +8 -2
- package/dist/counterpart/route.d.ts +31 -0
- package/dist/counterpart/route.js +70 -0
- package/dist/counterpart/service.d.ts +60 -4
- package/dist/counterpart/service.js +142 -16
- package/dist/design-guidance.d.ts +26 -0
- package/dist/design-guidance.js +177 -0
- package/dist/fulu-orca.d.ts +7 -0
- package/dist/fulu-orca.js +18 -2
- package/dist/index.js +528 -72
- package/dist/printers/bambu-filaments.d.ts +68 -0
- package/dist/printers/bambu-filaments.js +262 -0
- package/dist/printers/bambu.d.ts +99 -6
- package/dist/printers/bambu.js +478 -72
- package/dist/printers/creality.d.ts +2 -1
- package/dist/printers/creality.js +5 -1
- package/dist/printers/generic-base.d.ts +11 -6
- package/dist/printers/generic-base.js +21 -25
- package/dist/printers/klipper.d.ts +2 -1
- package/dist/printers/klipper.js +10 -7
- package/dist/printers/octoprint.d.ts +2 -1
- package/dist/printers/octoprint.js +15 -8
- package/dist/printers/printer-factory.d.ts +1 -1
- package/dist/printers/printer-factory.js +3 -3
- package/dist/printers/prusa.js +0 -1
- package/dist/safety/confirmation.d.ts +11 -16
- package/dist/safety/confirmation.js +8 -32
- package/dist/safety/hidden-hms.d.ts +32 -0
- package/dist/safety/hidden-hms.js +405 -0
- package/dist/safety/limits.d.ts +8 -2
- package/dist/safety/limits.js +14 -5
- package/dist/safety/print-file.d.ts +5 -0
- package/dist/safety/print-file.js +156 -19
- package/dist/safety/printer-state.d.ts +59 -8
- package/dist/safety/printer-state.js +174 -39
- package/dist/slicer/bambu-cli.d.ts +6 -2
- package/dist/slicer/bambu-cli.js +19 -12
- package/dist/slicer/layer-pauses.d.ts +47 -0
- package/dist/slicer/layer-pauses.js +130 -0
- package/dist/slicer/slicer-error.d.ts +11 -1
- package/dist/slicer/slicer-error.js +16 -2
- package/dist/stl/stl-manipulator.d.ts +27 -1
- package/dist/stl/stl-manipulator.js +216 -76
- package/dist/studio-print-options.d.ts +57 -0
- package/dist/studio-print-options.js +179 -0
- package/package.json +25 -4
- package/server.json +2 -2
- package/src/ams-mapping.ts +132 -0
- package/src/bambu-network-bridge.ts +1075 -0
- package/src/bambu-network-certs.ts +154 -0
- package/src/control-reply.ts +41 -0
- package/src/counterpart/camera-sources.ts +102 -0
- package/src/counterpart/camera-stream.ts +195 -0
- package/src/counterpart/camera-tools.ts +35 -0
- package/src/counterpart/contract.ts +12 -4
- package/src/counterpart/route.ts +82 -0
- package/src/counterpart/service.ts +150 -16
- package/src/design-guidance.ts +188 -0
- package/src/fulu-orca.ts +18 -2
- package/src/index.ts +546 -87
- package/src/printers/bambu-filaments.ts +346 -0
- package/src/printers/bambu.ts +506 -81
- package/src/printers/creality.ts +8 -3
- package/src/printers/generic-base.ts +33 -40
- package/src/printers/klipper.ts +12 -8
- package/src/printers/octoprint.ts +16 -9
- package/src/printers/printer-factory.ts +3 -3
- package/src/printers/prusa.ts +0 -1
- package/src/safety/confirmation.ts +15 -38
- package/src/safety/hidden-hms.ts +406 -0
- package/src/safety/limits.ts +15 -5
- package/src/safety/print-file.ts +139 -17
- package/src/safety/printer-state.ts +179 -40
- package/src/slicer/bambu-cli.ts +22 -13
- package/src/slicer/layer-pauses.ts +143 -0
- package/src/slicer/slicer-error.ts +21 -2
- package/src/stl/stl-manipulator.ts +254 -84
- package/src/studio-print-options.ts +201 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AxiosInstance } from "axios";
|
|
2
|
-
import { GenericPrinterImplementation, type GenericPrinterState, type SnapshotCandidate } from "./generic-base.js";
|
|
2
|
+
import { GenericPrinterImplementation, type GenericPrinterState, type SnapshotCandidate, type CameraCandidate } from "./generic-base.js";
|
|
3
3
|
import type { ConfirmHardware } from "../safety/confirmation.js";
|
|
4
4
|
import type { CounterpartProgress, FilamentSlot, PrinterIdentity } from "../counterpart/contract.js";
|
|
5
5
|
/**
|
|
@@ -27,6 +27,7 @@ export declare class CrealityImplementation extends GenericPrinterImplementation
|
|
|
27
27
|
reportsLayers(host: string): Promise<boolean>;
|
|
28
28
|
supportsPauseResume(host: string): Promise<boolean>;
|
|
29
29
|
snapshotCandidates(host: string): Promise<SnapshotCandidate[]>;
|
|
30
|
+
cameraCandidates(host: string): Promise<CameraCandidate[]>;
|
|
30
31
|
counterpartFilaments(host: string, port: string, apiKey: string): Promise<FilamentSlot[]>;
|
|
31
32
|
counterpartIdentity(host: string): Promise<PrinterIdentity>;
|
|
32
33
|
/** Pause and resume only through a detected Moonraker; the device API has no verified route. */
|
|
@@ -65,6 +65,10 @@ export class CrealityImplementation extends GenericPrinterImplementation {
|
|
|
65
65
|
const moonraker = await this.moonrakerPort(host);
|
|
66
66
|
return moonraker ? this.moonraker.snapshotCandidates(host, moonraker, "") : [];
|
|
67
67
|
}
|
|
68
|
+
async cameraCandidates(host) {
|
|
69
|
+
const moonraker = await this.moonrakerPort(host);
|
|
70
|
+
return moonraker ? this.moonraker.cameraCandidates(host, moonraker, "") : [];
|
|
71
|
+
}
|
|
68
72
|
async counterpartFilaments(host, port, apiKey) {
|
|
69
73
|
const moonraker = await this.moonrakerPort(host);
|
|
70
74
|
return moonraker ? this.moonraker.counterpartFilaments(host, moonraker, "") : super.counterpartFilaments(host, port, apiKey);
|
|
@@ -104,7 +108,7 @@ export class CrealityImplementation extends GenericPrinterImplementation {
|
|
|
104
108
|
if (IDLE_STATES.has(state))
|
|
105
109
|
return { state, ready: true };
|
|
106
110
|
if (FINISHED_STATES.has(state))
|
|
107
|
-
return { state, ready: true
|
|
111
|
+
return { state, ready: true };
|
|
108
112
|
return { state, ready: false, reason: `state is ${state}` };
|
|
109
113
|
}
|
|
110
114
|
async getStatus(host, port, apiKey) {
|
|
@@ -11,6 +11,12 @@ export interface SnapshotCandidate {
|
|
|
11
11
|
/** True when the firmware's own settings name this URL; false for a conventional fallback. */
|
|
12
12
|
configured: boolean;
|
|
13
13
|
}
|
|
14
|
+
export interface CameraCandidate extends SnapshotCandidate {
|
|
15
|
+
transport: "http-mjpeg" | "http-snapshot";
|
|
16
|
+
/** Snapshot belonging to this same camera, never an unrelated later candidate. */
|
|
17
|
+
snapshotUrl?: string;
|
|
18
|
+
unsupportedStream?: boolean;
|
|
19
|
+
}
|
|
14
20
|
/** Normalized adapter state used by the print and heating preflight. */
|
|
15
21
|
export interface GenericPrinterState {
|
|
16
22
|
/** Adapter-reported state text. */
|
|
@@ -19,8 +25,6 @@ export interface GenericPrinterState {
|
|
|
19
25
|
ready: boolean;
|
|
20
26
|
/** Why the printer is not ready. */
|
|
21
27
|
reason?: string;
|
|
22
|
-
/** Set when the printer explicitly reports a finished or cancelled job that may still be on the bed. */
|
|
23
|
-
finishedJob?: string;
|
|
24
28
|
}
|
|
25
29
|
export interface HeaterComponent {
|
|
26
30
|
heater: Heater;
|
|
@@ -49,6 +53,8 @@ export declare abstract class GenericPrinterImplementation extends PrinterImplem
|
|
|
49
53
|
counterpartIdentity(_host: string, _port: string, _apiKey: string): Promise<PrinterIdentity>;
|
|
50
54
|
/** Snapshot URLs from the firmware's own settings, in preference order. */
|
|
51
55
|
snapshotCandidates(_host: string, _port: string, _apiKey: string): Promise<SnapshotCandidate[]>;
|
|
56
|
+
/** Existing snapshot discovery is the fallback for cameras without an MJPEG URL. */
|
|
57
|
+
cameraCandidates(host: string, port: string, apiKey: string): Promise<CameraCandidate[]>;
|
|
52
58
|
/** The single extruder slot; adapters with a spool manager override this. */
|
|
53
59
|
counterpartFilaments(_host: string, _port: string, _apiKey: string): Promise<FilamentSlot[]>;
|
|
54
60
|
/** Whether progress reports carry layer numbers on this firmware. */
|
|
@@ -66,9 +72,9 @@ export declare abstract class GenericPrinterImplementation extends PrinterImplem
|
|
|
66
72
|
* Resume reheats and moves the printer, so it is gated like a print start:
|
|
67
73
|
* only the job this server instance inspected and started, only while the
|
|
68
74
|
* printer freshly reports that job paused, with its bytes re-read and
|
|
69
|
-
* compared where the firmware can download them
|
|
70
|
-
*
|
|
71
|
-
* in the meantime cancels it.
|
|
75
|
+
* compared where the firmware can download them. Only when the bytes cannot
|
|
76
|
+
* be re-read does a person confirm (MCP elicitation). A stop or heater-off
|
|
77
|
+
* request in the meantime cancels it.
|
|
72
78
|
*/
|
|
73
79
|
resumeJob(host: string, port: string, apiKey: string): Promise<{
|
|
74
80
|
status: string;
|
|
@@ -110,5 +116,4 @@ export declare abstract class GenericPrinterImplementation extends PrinterImplem
|
|
|
110
116
|
private withNamedSnapshot;
|
|
111
117
|
private assertReady;
|
|
112
118
|
private printChecked;
|
|
113
|
-
private printPrompt;
|
|
114
119
|
}
|
|
@@ -47,6 +47,10 @@ export class GenericPrinterImplementation extends PrinterImplementation {
|
|
|
47
47
|
async snapshotCandidates(_host, _port, _apiKey) {
|
|
48
48
|
return [];
|
|
49
49
|
}
|
|
50
|
+
/** Existing snapshot discovery is the fallback for cameras without an MJPEG URL. */
|
|
51
|
+
async cameraCandidates(host, port, apiKey) {
|
|
52
|
+
return (await this.snapshotCandidates(host, port, apiKey)).map(candidate => ({ ...candidate, transport: "http-snapshot" }));
|
|
53
|
+
}
|
|
50
54
|
/** The single extruder slot; adapters with a spool manager override this. */
|
|
51
55
|
async counterpartFilaments(_host, _port, _apiKey) {
|
|
52
56
|
return [{ id: "ext", source: "unknown" }];
|
|
@@ -70,9 +74,9 @@ export class GenericPrinterImplementation extends PrinterImplementation {
|
|
|
70
74
|
* Resume reheats and moves the printer, so it is gated like a print start:
|
|
71
75
|
* only the job this server instance inspected and started, only while the
|
|
72
76
|
* printer freshly reports that job paused, with its bytes re-read and
|
|
73
|
-
* compared where the firmware can download them
|
|
74
|
-
*
|
|
75
|
-
* in the meantime cancels it.
|
|
77
|
+
* compared where the firmware can download them. Only when the bytes cannot
|
|
78
|
+
* be re-read does a person confirm (MCP elicitation). A stop or heater-off
|
|
79
|
+
* request in the meantime cancels it.
|
|
76
80
|
*/
|
|
77
81
|
async resumeJob(host, port, apiKey) {
|
|
78
82
|
if (!(await this.supportsPauseResume(host, port, apiKey)) || !this.rawResumeJob) {
|
|
@@ -98,17 +102,20 @@ export class GenericPrinterImplementation extends PrinterImplementation {
|
|
|
98
102
|
bytesVerified = true;
|
|
99
103
|
}
|
|
100
104
|
assertActive();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if (!bytesVerified) {
|
|
106
|
+
// This firmware cannot hand the file back, so only a person can tell it is still the inspected job.
|
|
107
|
+
await requireHumanConfirmation(this.confirm, `Resume ${checked.name} on ${this.printerLabel} (${host}:${port})? This firmware cannot re-read the file, so it is matched by name only. ` +
|
|
108
|
+
`It reheats to its targets (peak nozzle ${checked.peaks.nozzle}°C, bed ${checked.peaks.bed}°C, chamber ${checked.peaks.chamber}°C) and moves. ` +
|
|
109
|
+
`Materials: ${checked.materials.length ? checked.materials.join(", ") : "none declared"}. Inspected file SHA-256: ${checked.sha256}. ` +
|
|
110
|
+
"Confirm the paused job is that file, the nozzle path is clear and the filament is still loaded.");
|
|
111
|
+
assertActive();
|
|
112
|
+
}
|
|
113
|
+
// Someone may have resumed or stopped it at the printer meanwhile (for example while a prompt was open).
|
|
107
114
|
await this.assertPausedJob(host, port, apiKey, checked.name);
|
|
108
|
-
// A progress read
|
|
109
|
-
// could now be paused under the same name); only the record that was
|
|
115
|
+
// A progress read meanwhile may have retired this record (the job ended, and a new one
|
|
116
|
+
// could now be paused under the same name); only the record that was checked may be resumed.
|
|
110
117
|
if (this.checkedJobs.get(key) !== checked) {
|
|
111
|
-
throw new Error(`Print safety: ${checked.name} ended on ${this.printerLabel}
|
|
118
|
+
throw new Error(`Print safety: ${checked.name} ended on ${this.printerLabel} during the resume checks; nothing was sent.`);
|
|
112
119
|
}
|
|
113
120
|
assertActive();
|
|
114
121
|
await this.rawResumeJob(host, port, apiKey);
|
|
@@ -186,8 +193,6 @@ export class GenericPrinterImplementation extends PrinterImplementation {
|
|
|
186
193
|
validateGenericTemperature(target.heater, value, material ? [material] : undefined);
|
|
187
194
|
return withPrinterOperation(host, key, async (assertActive) => {
|
|
188
195
|
this.assertReady(await this.readPrinterState(host, port, apiKey), "heat");
|
|
189
|
-
await requireHumanConfirmation(this.confirm, `Heat ${target.heater} on ${this.printerLabel} (${host}:${port}) to ${value}°C?` +
|
|
190
|
-
(material && target.heater === "nozzle" ? ` Declared material: ${material}. Confirm the loaded filament matches.` : ""), "generic");
|
|
191
196
|
assertActive();
|
|
192
197
|
this.assertReady(await this.readPrinterState(host, port, apiKey), "heat");
|
|
193
198
|
assertActive();
|
|
@@ -275,10 +280,9 @@ export class GenericPrinterImplementation extends PrinterImplementation {
|
|
|
275
280
|
assertExpectedPeaks(options.expectedPeaks, { nozzle: inspection.peaks.nozzle, bed: inspection.peaks.bed });
|
|
276
281
|
const initial = await this.readPrinterState(host, port, apiKey);
|
|
277
282
|
this.assertReady(initial, "print");
|
|
278
|
-
await requireHumanConfirmation(this.confirm, this.printPrompt(host, port, remoteName, inspection, initial), "generic");
|
|
279
283
|
assertActive();
|
|
280
|
-
const
|
|
281
|
-
this.assertReady(
|
|
284
|
+
const rechecked = await this.readPrinterState(host, port, apiKey);
|
|
285
|
+
this.assertReady(rechecked, "print");
|
|
282
286
|
assertActive();
|
|
283
287
|
const summary = {
|
|
284
288
|
status: "success",
|
|
@@ -318,12 +322,4 @@ export class GenericPrinterImplementation extends PrinterImplementation {
|
|
|
318
322
|
this.checkedJobs.set(this.operationKey(host, port), checkedJob(remoteName, inspection));
|
|
319
323
|
return { ...summary, upload, response };
|
|
320
324
|
}
|
|
321
|
-
printPrompt(host, port, remoteName, inspection, state) {
|
|
322
|
-
const materials = inspection.materials.length ? inspection.materials.join(", ") : "none declared (no positive nozzle target)";
|
|
323
|
-
return (`Start a checked print of ${remoteName} on ${this.printerLabel} (${host}:${port})? ` +
|
|
324
|
-
`Materials: ${materials} (${inspection.materialSource}). Peak targets: nozzle ${inspection.peaks.nozzle}°C, bed ${inspection.peaks.bed}°C, chamber ${inspection.peaks.chamber}°C ` +
|
|
325
|
-
`(server ceilings ${inspection.ceilings.nozzle}/${inspection.ceilings.bed}/${inspection.ceilings.chamber}°C). File SHA-256: ${inspection.sha256}. ` +
|
|
326
|
-
"Confirm the loaded filament matches." +
|
|
327
|
-
"");
|
|
328
|
-
}
|
|
329
325
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GenericPrinterImplementation, type GenericPrinterState, type SnapshotCandidate } from "./generic-base.js";
|
|
1
|
+
import { GenericPrinterImplementation, type GenericPrinterState, type SnapshotCandidate, type CameraCandidate } from "./generic-base.js";
|
|
2
2
|
import type { CounterpartProgress, FilamentSlot, PrinterIdentity } from "../counterpart/contract.js";
|
|
3
3
|
export declare const MACRO_TIMEOUT_MS = 60000;
|
|
4
4
|
export declare class KlipperImplementation extends GenericPrinterImplementation {
|
|
@@ -18,6 +18,7 @@ export declare class KlipperImplementation extends GenericPrinterImplementation
|
|
|
18
18
|
* path relative to the host. https://moonraker.readthedocs.io/en/latest/external_api/webcams/
|
|
19
19
|
*/
|
|
20
20
|
snapshotCandidates(host: string, port: string, apiKey: string): Promise<SnapshotCandidate[]>;
|
|
21
|
+
cameraCandidates(host: string, port: string, apiKey: string): Promise<CameraCandidate[]>;
|
|
21
22
|
/**
|
|
22
23
|
* Spoolman through Moonraker: GET /server/spoolman/spool_id, then the proxy
|
|
23
24
|
* for /v1/spool/<id>. https://moonraker.readthedocs.io/en/latest/external_api/integrations/
|
package/dist/printers/klipper.js
CHANGED
|
@@ -62,11 +62,18 @@ export class KlipperImplementation extends GenericPrinterImplementation {
|
|
|
62
62
|
* path relative to the host. https://moonraker.readthedocs.io/en/latest/external_api/webcams/
|
|
63
63
|
*/
|
|
64
64
|
async snapshotCandidates(host, port, apiKey) {
|
|
65
|
+
return (await this.cameraCandidates(host, port, apiKey)).filter(candidate => candidate.transport === "http-snapshot");
|
|
66
|
+
}
|
|
67
|
+
async cameraCandidates(host, port, apiKey) {
|
|
65
68
|
const data = (await this.apiClient.get(`http://${host}:${port}/server/webcams/list`, this.authHeaders(apiKey))).data;
|
|
66
69
|
const webcams = Array.isArray(data?.result?.webcams) ? data.result.webcams : [];
|
|
67
70
|
return webcams
|
|
68
|
-
.filter((webcam) => webcam?.enabled !== false
|
|
69
|
-
.
|
|
71
|
+
.filter((webcam) => webcam?.enabled !== false)
|
|
72
|
+
.flatMap((webcam) => [
|
|
73
|
+
...(nonEmptyText(webcam?.stream_url)
|
|
74
|
+
? [{ url: String(webcam.stream_url).trim(), snapshotUrl: nonEmptyText(webcam?.snapshot_url), unsupportedStream: /webrtc|hls/i.test(String(webcam?.service ?? "")), transport: "http-mjpeg" }] : []),
|
|
75
|
+
...(nonEmptyText(webcam?.snapshot_url) ? [{ url: String(webcam.snapshot_url).trim(), transport: "http-snapshot" }] : []),
|
|
76
|
+
].map(candidate => ({ ...candidate, source: "moonraker-webcam", configured: true })));
|
|
70
77
|
}
|
|
71
78
|
/**
|
|
72
79
|
* Spoolman through Moonraker: GET /server/spoolman/spool_id, then the proxy
|
|
@@ -159,11 +166,7 @@ export class KlipperImplementation extends GenericPrinterImplementation {
|
|
|
159
166
|
if (!["standby", "complete", "cancelled"].includes(job)) {
|
|
160
167
|
return { state, ready: false, reason: `print_stats.state is ${job}` };
|
|
161
168
|
}
|
|
162
|
-
return {
|
|
163
|
-
state,
|
|
164
|
-
ready: true,
|
|
165
|
-
finishedJob: job === "standby" ? undefined : JSON.stringify([job, status?.print_stats?.filename ?? null]),
|
|
166
|
-
};
|
|
169
|
+
return { state, ready: true };
|
|
167
170
|
}
|
|
168
171
|
async downloadRemoteFile(host, port, apiKey, filename, destination) {
|
|
169
172
|
await this.downloadToFile(`http://${host}:${port}/server/files/gcodes/${encodePath(filename)}`, apiKey ? { "X-Api-Key": apiKey } : {}, destination);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GenericPrinterImplementation, type GenericPrinterState, type HeaterComponent, type SnapshotCandidate } from "./generic-base.js";
|
|
1
|
+
import { GenericPrinterImplementation, type GenericPrinterState, type HeaterComponent, type SnapshotCandidate, type CameraCandidate } from "./generic-base.js";
|
|
2
2
|
import type { CounterpartProgress, PrinterIdentity } from "../counterpart/contract.js";
|
|
3
3
|
export declare class OctoPrintImplementation extends GenericPrinterImplementation {
|
|
4
4
|
protected readonly printerLabel = "OctoPrint";
|
|
@@ -19,6 +19,7 @@ export declare class OctoPrintImplementation extends GenericPrinterImplementatio
|
|
|
19
19
|
* src/octoprint/plugins/classicwebcam/__init__.py; checked on 1.8.7 and 1.11.2.
|
|
20
20
|
*/
|
|
21
21
|
snapshotCandidates(host: string, port: string, apiKey: string): Promise<SnapshotCandidate[]>;
|
|
22
|
+
cameraCandidates(host: string, port: string, apiKey: string): Promise<CameraCandidate[]>;
|
|
22
23
|
counterpartIdentity(host: string, port: string, apiKey: string): Promise<PrinterIdentity>;
|
|
23
24
|
/** POST /api/job {command:"pause", action:"pause"|"resume"}; 204 on success, 409 without an active job. */
|
|
24
25
|
protected rawPauseJob(host: string, port: string, apiKey: string): Promise<any>;
|
|
@@ -54,21 +54,28 @@ export class OctoPrintImplementation extends GenericPrinterImplementation {
|
|
|
54
54
|
* src/octoprint/plugins/classicwebcam/__init__.py; checked on 1.8.7 and 1.11.2.
|
|
55
55
|
*/
|
|
56
56
|
async snapshotCandidates(host, port, apiKey) {
|
|
57
|
+
return (await this.cameraCandidates(host, port, apiKey)).filter(candidate => candidate.transport === "http-snapshot");
|
|
58
|
+
}
|
|
59
|
+
async cameraCandidates(host, port, apiKey) {
|
|
57
60
|
const settings = (await this.apiClient.get(`http://${host}:${port}/api/settings`, { headers: { "X-Api-Key": apiKey } })).data;
|
|
58
61
|
const candidates = [];
|
|
59
62
|
if (settings?.webcam?.webcamEnabled !== false) {
|
|
63
|
+
// The settings API maps webcam.stream to streamUrl. Classic Webcam on
|
|
64
|
+
// 1.9+ exposes its own stream field; only configured URLs are used.
|
|
60
65
|
const configured = [
|
|
61
|
-
settings?.plugins?.classicwebcam
|
|
62
|
-
settings?.webcam?.snapshotUrl,
|
|
63
|
-
...(Array.isArray(settings?.webcam?.webcams) ? settings.webcam.webcams.map((webcam) => webcam?.compat
|
|
66
|
+
settings?.plugins?.classicwebcam,
|
|
67
|
+
{ stream: settings?.webcam?.streamUrl, snapshot: settings?.webcam?.snapshotUrl },
|
|
68
|
+
...(Array.isArray(settings?.webcam?.webcams) ? settings.webcam.webcams.map((webcam) => webcam?.compat) : []),
|
|
64
69
|
];
|
|
65
|
-
for (const
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
candidates.push({ url:
|
|
70
|
+
for (const camera of configured) {
|
|
71
|
+
const stream = nonEmptyText(camera?.stream), snapshot = nonEmptyText(camera?.snapshot);
|
|
72
|
+
if (stream)
|
|
73
|
+
candidates.push({ url: stream, snapshotUrl: snapshot, source: "octoprint-settings", configured: true, transport: "http-mjpeg" });
|
|
74
|
+
if (snapshot)
|
|
75
|
+
candidates.push({ url: snapshot, source: "octoprint-settings", configured: true, transport: "http-snapshot" });
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
|
-
candidates.push({ url: `http://${host}/webcam/?action=snapshot`, source: "octoprint-fallback", configured: false });
|
|
78
|
+
candidates.push({ url: `http://${host}/webcam/?action=snapshot`, source: "octoprint-fallback", configured: false, transport: "http-snapshot" });
|
|
72
79
|
return candidates;
|
|
73
80
|
}
|
|
74
81
|
async counterpartIdentity(host, port, apiKey) {
|
|
@@ -10,7 +10,7 @@ export type GuardedPrinterImplementation = PrinterImplementation & {
|
|
|
10
10
|
export declare class PrinterFactory {
|
|
11
11
|
private implementations;
|
|
12
12
|
private apiClient;
|
|
13
|
-
/** confirm is the human (MCP elicitation) confirmation
|
|
13
|
+
/** confirm is the human (MCP elicitation) confirmation for physical checks software cannot make, such as resuming a job that cannot be re-read. */
|
|
14
14
|
constructor(confirm?: ConfirmHardware);
|
|
15
15
|
getImplementation(type: string): GuardedPrinterImplementation;
|
|
16
16
|
disconnectAll(): Promise<void>;
|
|
@@ -2,12 +2,12 @@ import { OctoPrintImplementation } from "./octoprint.js";
|
|
|
2
2
|
import { KlipperImplementation } from "./klipper.js";
|
|
3
3
|
import { DuetImplementation } from "./duet.js";
|
|
4
4
|
import { RepetierImplementation } from "./repetier.js";
|
|
5
|
-
import { BambuImplementation } from "./bambu.js";
|
|
5
|
+
import { BambuImplementation, defaultResumeStateDir } from "./bambu.js";
|
|
6
6
|
import { PrusaImplementation } from "./prusa.js";
|
|
7
7
|
import { CrealityImplementation } from "./creality.js";
|
|
8
8
|
import axios from "axios";
|
|
9
9
|
export class PrinterFactory {
|
|
10
|
-
/** confirm is the human (MCP elicitation) confirmation
|
|
10
|
+
/** confirm is the human (MCP elicitation) confirmation for physical checks software cannot make, such as resuming a job that cannot be re-read. */
|
|
11
11
|
constructor(confirm) {
|
|
12
12
|
this.implementations = new Map();
|
|
13
13
|
this.apiClient = axios.create({ timeout: 10000 });
|
|
@@ -15,7 +15,7 @@ export class PrinterFactory {
|
|
|
15
15
|
this.implementations.set("klipper", new KlipperImplementation(this.apiClient, confirm));
|
|
16
16
|
this.implementations.set("duet", new DuetImplementation(this.apiClient, confirm));
|
|
17
17
|
this.implementations.set("repetier", new RepetierImplementation(this.apiClient, confirm));
|
|
18
|
-
this.implementations.set("bambu", new BambuImplementation(this.apiClient, confirm));
|
|
18
|
+
this.implementations.set("bambu", new BambuImplementation(this.apiClient, confirm, { resumeStateDir: defaultResumeStateDir() }));
|
|
19
19
|
this.implementations.set("prusa", new PrusaImplementation(this.apiClient, confirm));
|
|
20
20
|
this.implementations.set("creality", new CrealityImplementation(this.apiClient, confirm));
|
|
21
21
|
}
|
package/dist/printers/prusa.js
CHANGED
|
@@ -201,7 +201,6 @@ export class PrusaImplementation extends GenericPrinterImplementation {
|
|
|
201
201
|
state,
|
|
202
202
|
ready,
|
|
203
203
|
reason: ready ? undefined : `printer.state is ${state}`,
|
|
204
|
-
finishedJob: ready && (state === "FINISHED" || state === "STOPPED") ? JSON.stringify([state, data?.job?.id ?? null]) : undefined,
|
|
205
204
|
};
|
|
206
205
|
}
|
|
207
206
|
const flags = data?.state?.flags;
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
/** Human confirmation for physical printer mutations (print start, positive heating). */
|
|
2
|
-
export type ConfirmHardware = (message: string) => Promise<boolean>;
|
|
3
|
-
export type ConfirmationScope = "bambu" | "generic";
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Human confirmation through MCP elicitation. Ordinary print starts, positive
|
|
3
|
+
* heating and resumes of a re-read job do not ask: the client's own action is
|
|
4
|
+
* the approval. Only a check that software cannot make asks a person, such as
|
|
5
|
+
* resuming a job whose file the printer cannot hand back for re-reading.
|
|
9
6
|
*/
|
|
10
|
-
export
|
|
7
|
+
export type ConfirmHardware = (message: string) => Promise<boolean>;
|
|
11
8
|
/**
|
|
12
|
-
* How long a
|
|
13
|
-
*
|
|
9
|
+
* How long a person has to answer a prompt from the server (a resume of a job
|
|
10
|
+
* that could not be re-read, or the Bambu printer-model prompt). The MCP SDK's
|
|
11
|
+
* default request timeout (60 s) is too short for someone walking to the printer.
|
|
14
12
|
*/
|
|
15
13
|
export declare function confirmationTimeoutMs(): number;
|
|
16
14
|
export declare function confirmationTimedOut(timeoutMs: number): string;
|
|
17
|
-
export declare const CONFIRMATION_UNAVAILABLE
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
* clearing hardware errors). Those are required even when prompts are opted out.
|
|
21
|
-
*/
|
|
22
|
-
export declare function requireHumanConfirmation(confirm: ConfirmHardware | undefined, message: string, scope: ConfirmationScope, physicalCheck?: boolean): Promise<void>;
|
|
15
|
+
export declare const CONFIRMATION_UNAVAILABLE = "This step needs a person to confirm a check at the printer, which requires an MCP client with elicitation support. No command was sent.";
|
|
16
|
+
/** Ask a person to accept a physical check that software cannot replace. */
|
|
17
|
+
export declare function requireHumanConfirmation(confirm: ConfirmHardware | undefined, message: string): Promise<void>;
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* relevant to the scope is set to another value, confirmation stays required.
|
|
6
|
-
*/
|
|
7
|
-
export function confirmationOptOut(scope) {
|
|
8
|
-
const names = scope === "bambu"
|
|
9
|
-
? ["BAMBU_REQUIRE_CONFIRMATION", "PRINT_REQUIRE_CONFIRMATION"]
|
|
10
|
-
: ["PRINT_REQUIRE_CONFIRMATION"];
|
|
11
|
-
const values = names
|
|
12
|
-
.map((name) => process.env[name]?.trim())
|
|
13
|
-
.filter((value) => value !== undefined && value !== "");
|
|
14
|
-
return values.length > 0 && values.every((value) => value === "0");
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* How long a human has to answer a confirmation prompt. The MCP SDK's default
|
|
18
|
-
* request timeout (60 s) is too short for someone walking to the printer.
|
|
2
|
+
* How long a person has to answer a prompt from the server (a resume of a job
|
|
3
|
+
* that could not be re-read, or the Bambu printer-model prompt). The MCP SDK's
|
|
4
|
+
* default request timeout (60 s) is too short for someone walking to the printer.
|
|
19
5
|
*/
|
|
20
6
|
export function confirmationTimeoutMs() {
|
|
21
7
|
const raw = process.env.PRINT_CONFIRMATION_TIMEOUT_MS?.trim();
|
|
@@ -27,23 +13,13 @@ export function confirmationTimedOut(timeoutMs) {
|
|
|
27
13
|
return `No confirmation was received within ${minutes >= 1 ? `${Number(minutes.toFixed(1))} minute(s)` : `${timeoutMs / 1000} seconds`}; ` +
|
|
28
14
|
"nothing was sent to the printer. Ask again when you are at the printer, or raise PRINT_CONFIRMATION_TIMEOUT_MS.";
|
|
29
15
|
}
|
|
30
|
-
export const CONFIRMATION_UNAVAILABLE = "
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* physicalCheck marks confirmations that software cannot replace (for example
|
|
34
|
-
* clearing hardware errors). Those are required even when prompts are opted out.
|
|
35
|
-
*/
|
|
36
|
-
export async function requireHumanConfirmation(confirm, message, scope, physicalCheck = false) {
|
|
37
|
-
if (!physicalCheck && confirmationOptOut(scope))
|
|
38
|
-
return;
|
|
16
|
+
export const CONFIRMATION_UNAVAILABLE = "This step needs a person to confirm a check at the printer, which requires an MCP client with elicitation support. No command was sent.";
|
|
17
|
+
/** Ask a person to accept a physical check that software cannot replace. */
|
|
18
|
+
export async function requireHumanConfirmation(confirm, message) {
|
|
39
19
|
if (!confirm)
|
|
40
20
|
throw new Error(CONFIRMATION_UNAVAILABLE);
|
|
41
21
|
if (!(await confirm(message))) {
|
|
42
|
-
throw new Error("Hardware safety confirmation was declined or cancelled in the MCP client. No command was sent. " +
|
|
43
|
-
|
|
44
|
-
: "Some clients decline prompts automatically, for example in a non-interactive permission mode. A deliberately headless " +
|
|
45
|
-
`setup that approves jobs another way can set ${scope === "bambu"
|
|
46
|
-
? "PRINT_REQUIRE_CONFIRMATION=0 (or BAMBU_REQUIRE_CONFIRMATION=0)"
|
|
47
|
-
: "PRINT_REQUIRE_CONFIRMATION=0"} to skip ordinary print and heating prompts.`));
|
|
22
|
+
throw new Error("Hardware safety confirmation was declined or cancelled in the MCP client. No command was sent. " +
|
|
23
|
+
"This confirmation covers a physical check at the printer and always needs a person to accept it.");
|
|
48
24
|
}
|
|
49
25
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const HIDDEN_HMS_SOURCE: {
|
|
2
|
+
readonly studioTag: "v02.08.02.61";
|
|
3
|
+
readonly studioCommit: "926a7192574bcb9b3a732e1ec59a46d79cb45466";
|
|
4
|
+
/** Per table: Bambu's device_hms version and the SHA-256 of the English file used. */
|
|
5
|
+
readonly tables: {
|
|
6
|
+
readonly "093": {
|
|
7
|
+
readonly version: 202508191204;
|
|
8
|
+
readonly sha256: "7801fd6c355854b62ecb62ee32727b4e5dcca908841dbcfd5e5de45b3dfd1c25";
|
|
9
|
+
};
|
|
10
|
+
readonly "094": {
|
|
11
|
+
readonly version: 202503231214;
|
|
12
|
+
readonly sha256: "047c15e2140738ec08258b74536e6cb996cddf0c586c5c39e2318e54125a9e76";
|
|
13
|
+
};
|
|
14
|
+
readonly "20P": {
|
|
15
|
+
readonly version: 202511162200;
|
|
16
|
+
readonly sha256: "e4f067d027b9bcbb9bbf6cb90f41f11130cf4d83dffd3dee101db7826dc808c3";
|
|
17
|
+
};
|
|
18
|
+
readonly "22E": {
|
|
19
|
+
readonly version: 202510142200;
|
|
20
|
+
readonly sha256: "87426021aea2bd4e8d721e46e1c86808fd7c6fc06f52baf6d952ead9e8d14d1e";
|
|
21
|
+
};
|
|
22
|
+
readonly "239": {
|
|
23
|
+
readonly version: 202506171403;
|
|
24
|
+
readonly sha256: "c1d23d5302756d57df5cb9bc795d95fc54ef1db7565dca1f04b69a024886426a";
|
|
25
|
+
};
|
|
26
|
+
readonly "31B": {
|
|
27
|
+
readonly version: 202511162200;
|
|
28
|
+
readonly sha256: "dcf63657bff5451f0715ea3c032efe482279bc8cbc28cb9b8ae51c7354826574";
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export declare const HIDDEN_HMS: Readonly<Record<string, readonly string[]>>;
|