mcp-3d-printer-server 1.2.12 → 1.2.14
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 +30 -0
- package/CONTRIBUTORS.md +4 -0
- package/README.md +2 -2
- package/dist/fulu-orca.d.ts +8 -0
- package/dist/fulu-orca.js +107 -10
- package/dist/index.js +3 -3
- package/dist/printers/bambu.js +11 -4
- package/dist/safety/confirmation.js +9 -2
- package/dist/safety/printer-state.d.ts +2 -0
- package/dist/safety/printer-state.js +42 -1
- package/dist/slicer/bambu-cli.d.ts +21 -1
- package/dist/slicer/bambu-cli.js +86 -11
- package/dist/slicer/run-slicer.d.ts +2 -0
- package/dist/slicer/run-slicer.js +3 -2
- package/dist/stl/stl-manipulator.d.ts +8 -0
- package/dist/stl/stl-manipulator.js +31 -2
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/fulu-orca.ts +102 -10
- package/src/index.ts +3 -3
- package/src/printers/bambu.ts +11 -3
- package/src/safety/confirmation.ts +8 -2
- package/src/safety/printer-state.ts +42 -1
- package/src/slicer/bambu-cli.ts +92 -7
- package/src/slicer/run-slicer.ts +5 -2
- package/src/stl/stl-manipulator.ts +29 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.14 — 2026-10-25
|
|
4
|
+
|
|
5
|
+
Ported from [bambu-printer-mcp](https://github.com/DMontgomery40/bambu-printer-mcp) 1.1.34. Its evidence came from real P1S runs and an x86-64 Linux FULU container; this repository's validation is noted in each entry.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Let H2D printers with stock nozzles print. Every print stopped with `Printer nozzle 0 information is unreliable, abnormal or unknown` because the HS01 nozzles report `stat: 1` permanently. Bambu Studio reads bit 0 of `stat` as "information unreliable" and bits 1-2 as abnormal or unknown. Nozzles that report no identity chip (`sn: "N/A"`) now pass bit 0, and their configured diameter and type are still compared with the job. The Bambu print prompt names them so a person confirms they match the installed hotends, and checks log them once per printer on stderr, so a headless setup still records them. Abnormal or unknown nozzles still block, and a chipped nozzle with unreliable information asks for a refresh on the printer. Reported by [MorganFreeeman](https://github.com/MorganFreeeman) in [bambu-printer-mcp #67](https://github.com/DMontgomery40/bambu-printer-mcp/issues/67) with idle and mid-print H2D firmware 01.04.00.00 reports. Validated here with that report in mocked-status regressions; no H2D hardware was available.
|
|
10
|
+
- Say "declined or cancelled" when a person or the MCP client declines a hardware confirmation, instead of advising a client with elicitation support; clients without elicitation still get that advice. Ordinary print and heating prompts name the opt-out that applies to the printer: `PRINT_REQUIRE_CONFIRMATION=0`, or also `BAMBU_REQUIRE_CONFIRMATION=0` for Bambu printers. Bed-clearance confirmations say a person must accept them. Also from [bambu-printer-mcp #67](https://github.com/DMontgomery40/bambu-printer-mcp/issues/67); validated with mocked Bambu and generic adapters.
|
|
11
|
+
- Stop `get_printer_status` on Bambu printers from reporting `UNKNOWN` when the printer's first MQTT push is a sparse hello (model and modules). The read returned as soon as any data had arrived; it now waits, within its existing 1.5 s bound, until the full status has merged. bambu-printer-mcp saw this twice on a P1S; validated here with a delayed-push regression.
|
|
12
|
+
- Stop `slice_stl` option combinations from crashing OrcaSlicer's CLI. OrcaSlicer 2.4.2 segfaults in `PartPlate::check_outside`, which needs the GUI, when `scale` or `rotate_x`/`rotate_y` moves an object below the bed while arranging, the default for STL input. For STL input with OrcaSlicer or FULU, the server applies scale and rotations to a copy of the mesh in Orca's own order (scale, then X, then Y, then Z) and omits those flags; for 3MF input with `arrange: true` it refuses that combination with an explanation. `repetitions` on an STL now uses Orca's `--clone-objects`, which cannot be combined with `clone_objects`. The `repetitions`, `arrange` and `clone_objects` descriptions now state the CLI's real limits. Bambu Studio keeps every flag. bambu-printer-mcp verified the crash and fix against OrcaSlicer 2.4.2 on Linux, including the 10 × 20 × 30 mm rotation-order check; this repository's regressions use a fake CLI that records the mesh it receives.
|
|
13
|
+
- Report how a Bambu-compatible slicer failed: Bambu Studio and OrcaSlicer failures now name the CLI's own reason, from the `result.json` it writes on Linux or from its exit code, next to the existing exit code or signal and output tails. Validated with a fake CLI.
|
|
14
|
+
- Make FULU BambuNetwork bridge probes work with FULU's v1.0.0 Linux host. The host exits when its input closes, dropping requests it has not answered, so every probe and RPC call returned no responses; the server now keeps the input open until each request is answered. On Linux it passes `PJARCZAK_BAMBU_NETWORK_SO` and `PJARCZAK_BAMBU_SOURCE_SO` inside `PJARCZAK_BAMBU_PLUGIN_DIR` unless set, because the host opens them from its working directory ([FULU #51](https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/51)). The default Linux bridge command pipes the host through `cat`, because the host exits 101 on the socket stdout Node.js provides ([FULU #50](https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/50)); probes and RPC results explain exit code 101, and the FULU guide documents a wrapper for custom commands. A handshake from a host that could not load the networking plug-in is no longer reported as ready, and `file open failed` names the missing setting. A host that stops answering times out with its stderr, how many requests went unanswered and any hints, and Linux setup checks report the library paths that reach the host, including ones you set. macOS bundle notes and wrapper commands appear only for macOS, and the wrapper command only when the wrapper exists. Linux host bugs were reported with diagnosis and upstream issues by [Aaron Roberts (aaroberts)](https://github.com/aaroberts) in [bambu-printer-mcp #68](https://github.com/DMontgomery40/bambu-printer-mcp/issues/68). Validated with a fake host that exits on end of input, and in the maintainer's x86-64 Linux container (QEMU emulation) against FULU's real v1.0.0 host and networking plug-in 02.05.02.58: probes were ready through the default command and the wrapper, a directly launched host returned exit 101 with its explanation, and a missing plug-in folder returned the `file open failed` explanation. `net.create_agent` through `fulu_bambu_network_rpc` created an agent; the host then aborted while exiting, after answering. No printer was contacted. bambu-printer-mcp's bridge LAN printing, certificate, `-4030` and job-name changes do not apply: this server refuses raw bridge print and printer-connection methods and prints over its direct MQTT/FTPS path.
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- Add the FULU guide's Linux host setup and an "Upstream bugs and workarounds" table naming FULU #50 and #51, the FULU runtime's missing `printer.cer`, the FULU v1.0.0 CLI crash, the OrcaSlicer 2.4.2 `check_outside` crash (related to the closed [OrcaSlicer #13328](https://github.com/OrcaSlicer/OrcaSlicer/issues/13328)), and the Bambu plug-in's `-4030` design, with what this server does about each.
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
- Move the direct `zod` dependency to 4.6.7 for [GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5), memory exhaustion when validating oversized constrained collections; every zod 3 release is affected. The MCP SDK and zod-to-json-schema both accept zod 4, and this server does not import zod itself. Update body-parser to 2.4.0 ([GHSA-27r3-jxmh-p828](https://github.com/advisories/GHSA-27r3-jxmh-p828)), fast-uri to 3.1.9, follow-redirects to 1.16.2, hono to 4.13.14 and ip-address to 10.7.5 for their new advisories. `npm audit` now reports no vulnerabilities. The documentation site's VitePress development server still pulls vite 6.4.3 and esbuild with advisories that have no VitePress 1.x fix; they affect only `vitepress dev`, not the published static site.
|
|
23
|
+
|
|
24
|
+
## 1.2.13 — 2026-10-20
|
|
25
|
+
|
|
26
|
+
### Dependencies
|
|
27
|
+
|
|
28
|
+
- Refresh the reproducible installation/test lock from MQTT.js 5.10.4 to 5.16.1, including its fix for reconnecting when the previous stream is already destroyed. Keep bambu-node pinned at 3.22.21 and preserve the server's checked MQTT wire format and rejection guard. Thank you to [Henk Kraal (hkraal)](https://github.com/hkraal) and the [MQTT.js maintainers](https://github.com/mqttjs/MQTT.js/releases/tag/v5.16.1).
|
|
29
|
+
- Update the documentation build's source-map-js to 1.2.2 for [GHSA-68fv-2mgg-jv7q](https://github.com/advisories/GHSA-68fv-2mgg-jv7q), which addresses indexed source-map offsets blocking the event loop. Thank you to [7rulnik](https://github.com/7rulnik/source-map-js/pull/79). Refresh compatible Node 18 typings and MQTT's required transitive packages.
|
|
30
|
+
|
|
31
|
+
The root lock controls `npm ci`, including CI and reproducible local installs; npm does not ship that lock to consumers. Existing parent projects can retain older transitive MQTT versions and must refresh their own locks. This is not a new published MQTT minimum or a claim that every advisory is resolved. Remaining nodemon/chokidar/braces and VitePress/Vite/esbuild development-server advisories have no compatible automatic fix; no major upgrade or downgrade is forced. Printer safety behavior is unchanged and no physical printer operation is performed.
|
|
32
|
+
|
|
3
33
|
## 1.2.12 — 2026-10-13
|
|
4
34
|
|
|
5
35
|
### Fixed
|
package/CONTRIBUTORS.md
CHANGED
|
@@ -21,6 +21,8 @@ Thank you to everyone who builds, tests, reports problems, and shares real print
|
|
|
21
21
|
|
|
22
22
|
| Contributor | Contribution |
|
|
23
23
|
| --- | --- |
|
|
24
|
+
| [MorganFreeeman](https://github.com/MorganFreeeman) | Reports in the Bambu fork's [#67](https://github.com/DMontgomery40/bambu-printer-mcp/issues/67) that every H2D print stopped on stock nozzles, with idle and mid-print H2D firmware 01.04.00.00 reports, and that a declined confirmation pointed to the wrong cause. The nozzle-information and confirmation-wording fixes are ported here with that report as a mocked-status regression; no H2D print is claimed. |
|
|
25
|
+
| [Aaron Roberts (aaroberts)](https://github.com/aaroberts) | Diagnoses FULU's Linux host failures under Node.js in the Bambu fork's [#68](https://github.com/DMontgomery40/bambu-printer-mcp/issues/68), with the upstream issues ([FULU #50](https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/50), [FULU #51](https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/51)) and a working configuration. The Linux library paths, exit-101 explanation and `cat` wrapper are ported here and checked against FULU's real Linux host without contacting a printer. |
|
|
24
26
|
| [Steavie](https://github.com/Steavie) | Reports reversed A1/A1 mini serial identity in the Bambu fork's [#39](https://github.com/DMontgomery40/bambu-printer-mcp/issues/39). This repository now uses the corrected prefixes for fresh safety identity, with mocked MQTT regressions. |
|
|
25
27
|
| [Travis Cashman (travismcashan)](https://github.com/travismcashan) | Reports MCP stdout pollution from model parsing and slicing in the Bambu fork's [#44](https://github.com/DMontgomery40/bambu-printer-mcp/issues/44). The stderr correction and subprocess regressions are also applied here. Their exact stock material-switch and runout-purge evidence informs the X2D file-inspection port; maintainer validation remains offline and mocked. |
|
|
26
28
|
| [Lickitysplitted](https://github.com/Lickitysplitted) | Diagnoses Bambu FTPS "Premature close" upload failures on an X1C as missing TLS session reuse in [#22](https://github.com/DMontgomery40/mcp-3D-printer-server/issues/22). Release 1.2.9 addresses it; confirmation on that X1C firmware is still outstanding. |
|
|
@@ -51,6 +53,8 @@ See the [full contribution history](https://github.com/DMontgomery40/mcp-3D-prin
|
|
|
51
53
|
|
|
52
54
|
Thanks to [Patrick Juchli](https://github.com/patrickjuchli) and the basic-ftp maintainers for [6.2.3](https://github.com/patrickjuchli/basic-ftp/releases/tag/v6.2.3), which fixes rejected-command crashes, client recovery, and password exposure in PASS errors. The compatible FTPS security update is validated in this release.
|
|
53
55
|
|
|
56
|
+
Thanks to [Henk Kraal (hkraal)](https://github.com/hkraal) and the [MQTT.js maintainers](https://github.com/mqttjs/MQTT.js/releases/tag/v5.16.1) for correcting reconnect behavior when the previous stream is destroyed, and to [7rulnik](https://github.com/7rulnik/source-map-js/pull/79) for the indexed source-map offset security fix in source-map-js 1.2.2. Their fixes are incorporated into the installation/test and documentation locks; published consumers still manage their own transitive locks. Thanks also to the [DefinitelyTyped contributors](https://github.com/DefinitelyTyped/DefinitelyTyped) for maintained Node 18 and stream typings.
|
|
57
|
+
|
|
54
58
|
## Open-source community and interoperability
|
|
55
59
|
|
|
56
60
|
Special thanks to the [FULU Foundation](https://www.fulu.org/), [Louis Rossmann](https://www.youtube.com/@rossmanngroup), and the [OrcaSlicer-bambulab contributors](https://github.com/FULU-Foundation/OrcaSlicer-bambulab) for their work supporting user control and interoperable tools. This is community recognition, separate from code authorship in this repository. The Bambu-only fork, [bambu-printer-mcp](https://github.com/DMontgomery40/bambu-printer-mcp), credits its own contributors in its [CONTRIBUTORS.md](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/CONTRIBUTORS.md).
|
package/README.md
CHANGED
|
@@ -590,8 +590,8 @@ Options for the Bambu-compatible path:
|
|
|
590
590
|
| `filament_colours` | One `#RRGGBB` per filament slot, `;`-separated. Defaults to the input 3MF's colours, then each profile's colour |
|
|
591
591
|
| `template_3mf_path`, `template_name`, `template_dir` | Reuse a 3MF's or profile's slicer settings as the process profile (defaults: `BAMBU_TEMPLATE_3MF_PATH`, `BAMBU_TEMPLATE_DIR`). An explicit `slicer_profile` takes precedence |
|
|
592
592
|
| `uptodate`, `min_save`, `skip_modified_gcodes` | Refresh 3MF presets to the installed slicer, write a smaller 3MF, or ignore custom G-code embedded in an input 3MF |
|
|
593
|
-
| `orient`, `arrange`, `ensure_on_bed`, `repetitions`, `clone_objects`, `skip_objects`, `slice_plate` | Placement: auto-orient, auto-arrange (set `false` to keep a layout), drop floating models onto the bed, copies
|
|
594
|
-
| `scale`, `rotate`, `rotate_x`, `rotate_y` | Transform before slicing (uniform scale; rotations in degrees) |
|
|
593
|
+
| `orient`, `arrange`, `ensure_on_bed`, `repetitions`, `clone_objects`, `skip_objects`, `slice_plate` | Placement: auto-orient, auto-arrange (STL input is arranged by default; set `false` to keep a 3MF layout), drop floating models onto the bed, copies (OrcaSlicer copies an STL with `--clone-objects`), a total clone count for the loaded model, objects to skip, and which plate to slice (0 = all) |
|
|
594
|
+
| `scale`, `rotate`, `rotate_x`, `rotate_y` | Transform before slicing (uniform scale; rotations in degrees). For OrcaSlicer STL input the server applies them to a mesh copy in Orca's order to avoid a CLI crash; see [OrcaSlicer CLI limits](https://github.com/DMontgomery40/mcp-3D-printer-server/blob/main/docs/SLICING.md#bambu-compatible-slicing) |
|
|
595
595
|
| `enable_timelapse`, `allow_mix_temp` | Insert timelapse parking moves; allow filaments with different temperature needs on one plate |
|
|
596
596
|
|
|
597
597
|
Generic slicers keep their own profile formats: PrusaSlicer and Slic3r load one exported config, and generic OrcaSlicer takes `machine.json;process.json`, optionally followed by `|filament.json`. `slicer_type`, `slicer_profile`, and `filament_profile` fall back to `SLICER_TYPE`, `SLICER_PROFILE`, and `FILAMENT_PROFILE`. The slicer executable comes from `SLICER_PATH`; a per-call `slicer_path` requires `MCP_ALLOW_EXECUTABLE_ARG=1`. See the [slicing guide](https://github.com/DMontgomery40/mcp-3D-printer-server/blob/main/docs/SLICING.md#bambu-compatible-slicing).
|
package/dist/fulu-orca.d.ts
CHANGED
|
@@ -16,5 +16,13 @@ export type FuluBridgeRpcOptions = {
|
|
|
16
16
|
bambuModel?: string;
|
|
17
17
|
};
|
|
18
18
|
export declare function isFuluPrintRpcMethod(method: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* FULU's Linux host opens its network and source libraries by bare name from
|
|
21
|
+
* its working directory instead of PJARCZAK_BAMBU_PLUGIN_DIR
|
|
22
|
+
* (FULU-Foundation/OrcaSlicer-bambulab#51), so name them in that directory
|
|
23
|
+
* unless they are already set. macOS wrappers run the host in a Linux guest
|
|
24
|
+
* that may not see host paths, so they are left alone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function linuxPluginLibraryEnv(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): Record<string, string>;
|
|
19
27
|
export declare function invokeFuluBridgeRpc(options: FuluBridgeRpcOptions): Promise<Record<string, unknown>>;
|
|
20
28
|
export declare function inspectFuluOrcaSetup(options: FuluOrcaSetupOptions): Promise<Record<string, unknown>>;
|
package/dist/fulu-orca.js
CHANGED
|
@@ -47,6 +47,43 @@ const SESSION_MUTATING_METHODS = new Set([
|
|
|
47
47
|
export function isFuluPrintRpcMethod(method) {
|
|
48
48
|
return FULU_PRINT_RPC_METHODS.has(method.trim());
|
|
49
49
|
}
|
|
50
|
+
const FULU_LINUX_DOCS = "https://github.com/DMontgomery40/mcp-3D-printer-server/blob/main/docs/FULU.md#linux";
|
|
51
|
+
/**
|
|
52
|
+
* FULU's Linux host opens its network and source libraries by bare name from
|
|
53
|
+
* its working directory instead of PJARCZAK_BAMBU_PLUGIN_DIR
|
|
54
|
+
* (FULU-Foundation/OrcaSlicer-bambulab#51), so name them in that directory
|
|
55
|
+
* unless they are already set. macOS wrappers run the host in a Linux guest
|
|
56
|
+
* that may not see host paths, so they are left alone.
|
|
57
|
+
*/
|
|
58
|
+
export function linuxPluginLibraryEnv(env, platform) {
|
|
59
|
+
const pluginDir = env.PJARCZAK_BAMBU_PLUGIN_DIR?.trim();
|
|
60
|
+
if (platform !== "linux" || !pluginDir)
|
|
61
|
+
return {};
|
|
62
|
+
const libraries = {};
|
|
63
|
+
if (!env.PJARCZAK_BAMBU_NETWORK_SO?.trim())
|
|
64
|
+
libraries.PJARCZAK_BAMBU_NETWORK_SO = path.resolve(pluginDir, "libbambu_networking.so");
|
|
65
|
+
if (!env.PJARCZAK_BAMBU_SOURCE_SO?.trim())
|
|
66
|
+
libraries.PJARCZAK_BAMBU_SOURCE_SO = path.resolve(pluginDir, "libBambuSource.so");
|
|
67
|
+
return libraries;
|
|
68
|
+
}
|
|
69
|
+
/** FULU's Linux host exits 101 when it cannot reopen a socket stdout (FULU-Foundation/OrcaSlicer-bambulab#50). */
|
|
70
|
+
const STDOUT_SOCKET_HINT = "Exit code 101 means FULU's Linux host could not reopen its stdout, which fails when stdout is a socket, as Node.js provides " +
|
|
71
|
+
"(https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/50). Launch the host through a wrapper that pipes its output through cat: " +
|
|
72
|
+
FULU_LINUX_DOCS;
|
|
73
|
+
/** Actionable hints for a bridge run: the exit-101 socket bug and a host that cannot open the plug-in libraries. */
|
|
74
|
+
function bridgeHints(exitCode, responses) {
|
|
75
|
+
const hints = [];
|
|
76
|
+
if (exitCode === 101)
|
|
77
|
+
hints.push(STDOUT_SOCKET_HINT);
|
|
78
|
+
const fileOpenFailure = Object.values(responses)
|
|
79
|
+
.find((value) => [value?.network_status, value?.error].some((text) => typeof text === "string" && /file open failed/i.test(text)));
|
|
80
|
+
if (fileOpenFailure) {
|
|
81
|
+
hints.push(`FULU BambuNetwork bridge could not open its networking library (${String(fileOpenFailure.network_status ?? fileOpenFailure.error)}). ` +
|
|
82
|
+
"Set PJARCZAK_BAMBU_PLUGIN_DIR to the directory containing libbambu_networking.so and libBambuSource.so, " +
|
|
83
|
+
`or set PJARCZAK_BAMBU_NETWORK_SO and PJARCZAK_BAMBU_SOURCE_SO to their absolute paths: ${FULU_LINUX_DOCS}`);
|
|
84
|
+
}
|
|
85
|
+
return hints;
|
|
86
|
+
}
|
|
50
87
|
function normalizePlatform(value) {
|
|
51
88
|
const normalized = (value || process.platform).trim().toLowerCase();
|
|
52
89
|
if (normalized === "mac" || normalized === "macos" || normalized === "darwin") {
|
|
@@ -200,7 +237,7 @@ function setupCommands(platform, pluginDir, runtimeDir) {
|
|
|
200
237
|
const verify = path.join(pluginDir, "verify_runtime_macos.sh");
|
|
201
238
|
const bridgeHost = path.join(runtimeDir || "", "pjarczak_bambu_linux_host");
|
|
202
239
|
const wrapper = path.join(pluginDir, "pjarczak-bambu-linux-host-wrapper");
|
|
203
|
-
|
|
240
|
+
const commands = [
|
|
204
241
|
{
|
|
205
242
|
label: "install macOS Lima runtime",
|
|
206
243
|
command: `bash ${quoteShell(install)} -PackageDir ${quoteShell(pluginDir)} -PluginDir ${quoteShell(pluginDir)}`,
|
|
@@ -213,11 +250,15 @@ function setupCommands(platform, pluginDir, runtimeDir) {
|
|
|
213
250
|
label: "verify macOS Lima runtime core before Linux plugin payload is present",
|
|
214
251
|
command: `bash ${quoteShell(verify)} -PackageDir ${quoteShell(pluginDir)} -PluginDir ${quoteShell(pluginDir)} -AllowMissingLinuxPlugin`,
|
|
215
252
|
},
|
|
216
|
-
|
|
253
|
+
];
|
|
254
|
+
// The wrapper ships in FULU's macOS bundle; suggest it only where it exists.
|
|
255
|
+
if (fs.existsSync(wrapper)) {
|
|
256
|
+
commands.push({
|
|
217
257
|
label: "bridge command for FULU BambuNetwork runtime",
|
|
218
258
|
command: `${quoteShell(wrapper)} ${quoteShell(bridgeHost)}`,
|
|
219
|
-
}
|
|
220
|
-
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return commands;
|
|
221
262
|
}
|
|
222
263
|
if (platform === "win32") {
|
|
223
264
|
return [
|
|
@@ -244,6 +285,11 @@ function setupCommands(platform, pluginDir, runtimeDir) {
|
|
|
244
285
|
label: "configure MCP slicer path",
|
|
245
286
|
command: "SLICER_TYPE=orcaslicer-bambulab SLICER_PATH=/path/to/orcaslicer",
|
|
246
287
|
},
|
|
288
|
+
{
|
|
289
|
+
label: "bridge command for FULU's Linux host, piped through cat (FULU #50)",
|
|
290
|
+
command: `FULU_BAMBU_BRIDGE_COMMAND="${quoteShell(path.join(pluginDir, "pjarczak_bambu_linux_host"))} | cat" ` +
|
|
291
|
+
"PJARCZAK_BAMBU_PLUGIN_DIR=\"$HOME/.config/OrcaSlicer/plugins\"",
|
|
292
|
+
},
|
|
247
293
|
];
|
|
248
294
|
}
|
|
249
295
|
function defaultBridgeCommand(platform, pluginDir, runtimeDir) {
|
|
@@ -255,12 +301,16 @@ function defaultBridgeCommand(platform, pluginDir, runtimeDir) {
|
|
|
255
301
|
}
|
|
256
302
|
if (platform === "darwin") {
|
|
257
303
|
const wrapper = path.join(pluginDir, "pjarczak-bambu-linux-host-wrapper");
|
|
304
|
+
if (!fs.existsSync(wrapper))
|
|
305
|
+
return undefined;
|
|
258
306
|
const runtimeHost = path.join(runtimeDir || defaultRuntimeDir(platform) || "", "pjarczak_bambu_linux_host");
|
|
259
307
|
return `${quoteShell(wrapper)} ${quoteShell(runtimeHost)}`;
|
|
260
308
|
}
|
|
261
309
|
if (platform === "linux") {
|
|
310
|
+
// The v1.0.0 host exits 101 on the socket stdout Node.js gives a child
|
|
311
|
+
// (FULU-Foundation/OrcaSlicer-bambulab#50); a shell pipe through cat avoids it.
|
|
262
312
|
const host = path.join(pluginDir, "pjarczak_bambu_linux_host");
|
|
263
|
-
return fs.existsSync(host) ? quoteShell(host) : undefined;
|
|
313
|
+
return fs.existsSync(host) ? `${quoteShell(host)} | cat` : undefined;
|
|
264
314
|
}
|
|
265
315
|
return undefined;
|
|
266
316
|
}
|
|
@@ -301,6 +351,7 @@ async function runBridgeRequests(command, requests, timeoutMs) {
|
|
|
301
351
|
return await new Promise((resolve, reject) => {
|
|
302
352
|
const child = spawn(command, {
|
|
303
353
|
shell: true,
|
|
354
|
+
env: { ...process.env, ...linuxPluginLibraryEnv(process.env, process.platform) },
|
|
304
355
|
stdio: ["pipe", "pipe", "pipe"],
|
|
305
356
|
});
|
|
306
357
|
const stdoutChunks = [];
|
|
@@ -314,12 +365,40 @@ async function runBridgeRequests(command, requests, timeoutMs) {
|
|
|
314
365
|
clearTimeout(timer);
|
|
315
366
|
callback();
|
|
316
367
|
};
|
|
368
|
+
const unanswered = new Set(requests.map((request) => request.id));
|
|
317
369
|
const timer = setTimeout(() => {
|
|
370
|
+
child.stdin?.end();
|
|
318
371
|
child.kill("SIGKILL");
|
|
319
|
-
|
|
372
|
+
// Keep what the host did say: a hung host is diagnosed from its stderr and partial answers.
|
|
373
|
+
const responses = Object.fromEntries(parseBridgeFrames(Buffer.concat(stdoutChunks))
|
|
374
|
+
.filter((frame) => frame.type === FRAME_TYPE_JSON_RESPONSE)
|
|
375
|
+
.map((frame) => [String(frame.id), (() => { try {
|
|
376
|
+
return JSON.parse(frame.payload.toString("utf8"));
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return null;
|
|
380
|
+
} })()]));
|
|
381
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim().slice(-1000);
|
|
382
|
+
const hints = bridgeHints(null, responses);
|
|
383
|
+
settle(() => reject(new Error(`FULU bridge request timed out after ${timeoutMs}ms with ${unanswered.size} of ${requests.length} request(s) unanswered.` +
|
|
384
|
+
(stderr ? ` Host stderr: ${stderr}` : "") + (hints.length ? ` ${hints.join(" ")}` : ""))));
|
|
320
385
|
}, timeoutMs);
|
|
321
|
-
|
|
386
|
+
// FULU's host exits on stdin EOF without answering requests still in
|
|
387
|
+
// flight, so stdin closes only once every request has a response.
|
|
388
|
+
child.stdout?.on("data", (chunk) => {
|
|
389
|
+
stdoutChunks.push(chunk);
|
|
390
|
+
if (!unanswered.size)
|
|
391
|
+
return;
|
|
392
|
+
for (const frame of parseBridgeFrames(Buffer.concat(stdoutChunks))) {
|
|
393
|
+
if (frame.type === FRAME_TYPE_JSON_RESPONSE)
|
|
394
|
+
unanswered.delete(frame.id);
|
|
395
|
+
}
|
|
396
|
+
if (!unanswered.size)
|
|
397
|
+
child.stdin?.end();
|
|
398
|
+
});
|
|
322
399
|
child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
400
|
+
// A host that exits early (exit code 101) closes the pipe; close reports the result.
|
|
401
|
+
child.stdin?.on("error", () => { });
|
|
323
402
|
child.on("error", (error) => {
|
|
324
403
|
settle(() => reject(error));
|
|
325
404
|
});
|
|
@@ -357,7 +436,8 @@ async function runBridgeRequests(command, requests, timeoutMs) {
|
|
|
357
436
|
for (const request of requests) {
|
|
358
437
|
child.stdin?.write(writeJsonRequestFrame(request.id, request.method, request.payload));
|
|
359
438
|
}
|
|
360
|
-
|
|
439
|
+
if (!unanswered.size)
|
|
440
|
+
child.stdin?.end();
|
|
361
441
|
});
|
|
362
442
|
}
|
|
363
443
|
async function probeFuluBridge(command, timeoutMs) {
|
|
@@ -366,9 +446,13 @@ async function probeFuluBridge(command, timeoutMs) {
|
|
|
366
446
|
{ id: 2, method: "bridge.capabilities", payload: {} },
|
|
367
447
|
{ id: 3, method: "bridge.runtime_info", payload: {} },
|
|
368
448
|
], timeoutMs);
|
|
449
|
+
const handshake = probe.responses["1"];
|
|
450
|
+
const hints = bridgeHints(probe.exitCode, probe.responses);
|
|
369
451
|
return {
|
|
370
452
|
exitCode: probe.exitCode,
|
|
371
|
-
|
|
453
|
+
// A handshake from a host that could not load the networking plug-in is not a ready bridge.
|
|
454
|
+
ok: probe.ok && Boolean(handshake) && handshake?.ok !== false && handshake?.network_loaded !== false,
|
|
455
|
+
...(hints.length ? { hints } : {}),
|
|
372
456
|
stderr: probe.stderr,
|
|
373
457
|
logs: probe.logs,
|
|
374
458
|
handshake: probe.responses["1"] || null,
|
|
@@ -400,6 +484,7 @@ export async function invokeFuluBridgeRpc(options) {
|
|
|
400
484
|
}
|
|
401
485
|
const command = options.bridgeCommand || process.env.FULU_BAMBU_BRIDGE_COMMAND || "";
|
|
402
486
|
const bridgeResult = await runBridgeRequests(command, [{ id: 1, method, payload: options.payload || {} }], options.timeoutMs || 5000);
|
|
487
|
+
const hints = bridgeHints(bridgeResult.exitCode, bridgeResult.responses);
|
|
403
488
|
return {
|
|
404
489
|
method,
|
|
405
490
|
readOnly,
|
|
@@ -408,6 +493,7 @@ export async function invokeFuluBridgeRpc(options) {
|
|
|
408
493
|
bambuModel: options.bambuModel || undefined,
|
|
409
494
|
exitCode: bridgeResult.exitCode,
|
|
410
495
|
ok: bridgeResult.ok,
|
|
496
|
+
...(hints.length ? { hints } : {}),
|
|
411
497
|
stderr: bridgeResult.stderr,
|
|
412
498
|
logs: bridgeResult.logs,
|
|
413
499
|
response: bridgeResult.responses["1"] || null,
|
|
@@ -446,6 +532,12 @@ export async function inspectFuluOrcaSetup(options) {
|
|
|
446
532
|
pluginDir: pluginDir || null,
|
|
447
533
|
runtimeDir: runtimeDir || null,
|
|
448
534
|
bridgeCommand: bridgeCommand || null,
|
|
535
|
+
// Linux only: absolute plug-in library paths passed to FULU's host (FULU #51).
|
|
536
|
+
...(platform === "linux" ? { pluginLibraries: {
|
|
537
|
+
...Object.fromEntries(["PJARCZAK_BAMBU_NETWORK_SO", "PJARCZAK_BAMBU_SOURCE_SO"]
|
|
538
|
+
.filter((name) => process.env[name]?.trim()).map((name) => [name, process.env[name].trim()])),
|
|
539
|
+
...linuxPluginLibraryEnv(process.env, process.platform),
|
|
540
|
+
} } : {}),
|
|
449
541
|
fileChecks,
|
|
450
542
|
missingRequiredFiles,
|
|
451
543
|
optionalMissingFiles,
|
|
@@ -471,7 +563,12 @@ export async function inspectFuluOrcaSetup(options) {
|
|
|
471
563
|
"Bambu print start still enforces BAMBU_MODEL before generating or sending project G-code.",
|
|
472
564
|
"Raw bridge print methods are disabled; print_3mf inspects the exact plate, checks the live printer and asks for human confirmation.",
|
|
473
565
|
"The FULU BambuNetwork bridge is a separate runtime from the LAN MQTT/FTPS path; use bridge probing to verify that runtime before relying on cloud behavior.",
|
|
474
|
-
|
|
566
|
+
...(platform === "darwin"
|
|
567
|
+
? ["FULU macOS release bundles may ship the bridge dylib and host shims before the Linux plugin .so payload is present; missing linux-plugin entries mean the slicer can still be tested, but the BambuNetwork bridge is not fully ready."]
|
|
568
|
+
: []),
|
|
569
|
+
...(platform === "linux"
|
|
570
|
+
? ["FULU's v1.0.0 Linux host exits 101 when its stdout is a Node.js socket (FULU #50) and opens the plug-in libraries from its working directory (FULU #51). Pipe the host through cat and set PJARCZAK_BAMBU_PLUGIN_DIR; the server then passes the absolute library paths."]
|
|
571
|
+
: []),
|
|
475
572
|
],
|
|
476
573
|
};
|
|
477
574
|
}
|
package/dist/index.js
CHANGED
|
@@ -220,11 +220,11 @@ const SLICE_TOOL_PROPERTIES = {
|
|
|
220
220
|
description: "Template directory override when resolving template_name (default: BAMBU_TEMPLATE_DIR or ~/Sync/bambu/templates).",
|
|
221
221
|
},
|
|
222
222
|
uptodate: { type: "boolean", description: "Bambu-compatible slicing: refresh 3MF preset configs to the installed slicer version (--uptodate)." },
|
|
223
|
-
repetitions: { type: "number", description: "Bambu-compatible slicing: print N identical copies (--repetitions)." },
|
|
223
|
+
repetitions: { type: "number", description: "Bambu-compatible slicing: print N identical copies (--repetitions). OrcaSlicer copies an STL with --clone-objects; do not combine with clone_objects." },
|
|
224
224
|
orient: { type: "boolean", description: "Bambu-compatible slicing: auto-orient for printability (--orient)." },
|
|
225
|
-
arrange: { type: "boolean", description: "Bambu-compatible slicing: auto-arrange objects on the plate (--arrange). Set false to keep an existing layout." },
|
|
225
|
+
arrange: { type: "boolean", description: "Bambu-compatible slicing: auto-arrange objects on the plate (--arrange); STL input is arranged by default. Set false to keep an existing layout: an STL that is not already on the plate then fails to slice." },
|
|
226
226
|
ensure_on_bed: { type: "boolean", description: "Bambu-compatible slicing: lower floating models onto the bed (--ensure-on-bed)." },
|
|
227
|
-
clone_objects: { type: "string", description: "Bambu-compatible slicing:
|
|
227
|
+
clone_objects: { type: "string", description: "Bambu-compatible slicing: clone the loaded model, total copies as a single integer, e.g. '3' (--clone-objects). The Bambu-style CLIs count copies per input file and this tool loads one file, so a comma-separated list is rejected by the slicer." },
|
|
228
228
|
skip_objects: { type: "string", description: "Bambu-compatible slicing: comma-separated object indices to skip, e.g. '3,5,10'." },
|
|
229
229
|
enable_timelapse: { type: "boolean", description: "Bambu-compatible slicing: insert timelapse parking moves (--enable-timelapse)." },
|
|
230
230
|
allow_mix_temp: { type: "boolean", description: "Bambu-compatible slicing: allow filaments with different temperature requirements on one plate." },
|
package/dist/printers/bambu.js
CHANGED
|
@@ -8,7 +8,7 @@ import { BambuClient, PushAllCommand } from "bambu-node";
|
|
|
8
8
|
import { MAX_PRINT_FILE_BYTES, readSafe3mfArchive } from "../safety/archive.js";
|
|
9
9
|
import { inspectPrintFile } from "../safety/print-file.js";
|
|
10
10
|
import { normalizeModel, validateTemperature } from "../safety/limits.js";
|
|
11
|
-
import { manualHeatingRequirements, publishBambuCommand, readFreshPrinterStatus, validatePrinterState, } from "../safety/printer-state.js";
|
|
11
|
+
import { manualHeatingRequirements, publishBambuCommand, readFreshPrinterStatus, unchippedNozzles, validatePrinterState, } from "../safety/printer-state.js";
|
|
12
12
|
import { cancelPendingPrinterOperations, normalizedRemotePath, uniquePrintName, withPrinterOperation, withPrintSnapshot, } from "../safety/artifact.js";
|
|
13
13
|
import { requireHumanConfirmation } from "../safety/confirmation.js";
|
|
14
14
|
import { installBambuNodeRejectionGuard } from "./bambu-node-guard.js";
|
|
@@ -118,10 +118,13 @@ export class BambuImplementation extends PrinterImplementation {
|
|
|
118
118
|
}
|
|
119
119
|
async confirmPrintPreflight(serial, status, inspection) {
|
|
120
120
|
const finishedJob = this.finishedJobIdentity(status);
|
|
121
|
+
const unchipped = unchippedNozzles(status?.raw);
|
|
121
122
|
await this.confirmHardwareAction(`Start a checked print on ${inspection.model.toUpperCase()} (${serial})? Nozzles: ${inspection.nozzleDiameters.join(", ")} mm. ` +
|
|
122
123
|
`Materials: ${inspection.materials.join(", ")}. Peak targets: nozzle ${inspection.maxNozzleTemperature}°C, bed ${inspection.maxBedTemperature}°C, chamber ${inspection.maxChamberTemperature}°C. ` +
|
|
123
124
|
`File SHA-256: ${inspection.sha256}. Confirm the physical spool labels and that the build plate is clear.` +
|
|
124
|
-
(finishedJob !== undefined ? " The printer reports FINISH: remove the previous part and debris before confirming." : "")
|
|
125
|
+
(finishedJob !== undefined ? " The printer reports FINISH: remove the previous part and debris before confirming." : "") +
|
|
126
|
+
(unchipped.length ? ` Printer ${unchipped.length === 1 ? `nozzle ${unchipped[0]} reports` : `nozzles ${unchipped.join(" and ")} report`} no identity chip, ` +
|
|
127
|
+
`so ${unchipped.length === 1 ? "its" : "their"} diameter and type are the printer's configured settings; confirm they match the installed ${unchipped.length === 1 ? "hotend" : "hotends"}.` : ""), finishedJob !== undefined);
|
|
125
128
|
return finishedJob;
|
|
126
129
|
}
|
|
127
130
|
assertBedClearance(status, confirmedFinishedJob) {
|
|
@@ -180,8 +183,12 @@ export class BambuImplementation extends PrinterImplementation {
|
|
|
180
183
|
catch (error) {
|
|
181
184
|
console.warn("PushAllCommand failed, continuing with cached status", error);
|
|
182
185
|
}
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
// The first push after connecting can be a sparse hello (model and
|
|
187
|
+
// modules); the full status follows. Returning before it merges reported
|
|
188
|
+
// status UNKNOWN, so wait for gcode_state within the same 1.5 s bound.
|
|
189
|
+
const deadline = Date.now() + 1500;
|
|
190
|
+
while (typeof printer.data?.gcode_state !== "string" && Date.now() < deadline) {
|
|
191
|
+
await sleep(50);
|
|
185
192
|
}
|
|
186
193
|
const data = printer.data;
|
|
187
194
|
return {
|
|
@@ -37,7 +37,14 @@ export const CONFIRMATION_UNAVAILABLE = "Human hardware confirmation requires an
|
|
|
37
37
|
export async function requireHumanConfirmation(confirm, message, scope, physicalCheck = false) {
|
|
38
38
|
if (!physicalCheck && confirmationOptOut(scope))
|
|
39
39
|
return;
|
|
40
|
-
if (!confirm
|
|
41
|
-
throw new Error(
|
|
40
|
+
if (!confirm)
|
|
41
|
+
throw new Error(CONFIRMATION_UNAVAILABLE);
|
|
42
|
+
if (!(await confirm(message))) {
|
|
43
|
+
throw new Error("Hardware safety confirmation was declined or cancelled in the MCP client. No command was sent. " + (physicalCheck
|
|
44
|
+
? "This confirmation covers a physical check at the printer and always needs a person to accept it."
|
|
45
|
+
: "Some clients decline prompts automatically, for example in a non-interactive permission mode. A deliberately headless " +
|
|
46
|
+
`setup that approves jobs another way can set ${scope === "bambu"
|
|
47
|
+
? "PRINT_REQUIRE_CONFIRMATION=0 (or BAMBU_REQUIRE_CONFIRMATION=0)"
|
|
48
|
+
: "PRINT_REQUIRE_CONFIRMATION=0"} to skip ordinary print and heating prompts.`));
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -44,6 +44,8 @@ export interface ValidatedPrinterState {
|
|
|
44
44
|
reportedNozzles: ReportedNozzle[];
|
|
45
45
|
filaments: ReportedFilament[];
|
|
46
46
|
}
|
|
47
|
+
/** Nozzles whose reported diameter and type are configured settings rather than chip readings. */
|
|
48
|
+
export declare function unchippedNozzles(raw: unknown): number[];
|
|
47
49
|
/** Verifies reported configuration, not a physical nozzle/spool inspection. */
|
|
48
50
|
export declare function validatePrinterState(status: any, requirements: PrinterStateRequirements): ValidatedPrinterState;
|
|
49
51
|
/** Manual M104 cannot remap filament slots. Validate the material already at the nozzle. */
|
|
@@ -138,6 +138,41 @@ function nozzleTypeAndFlow(value) {
|
|
|
138
138
|
const type = token(value);
|
|
139
139
|
return { type: ["stainlesssteel", "hardenedsteel", "tungstencarbide", "brass"].includes(type) ? type : undefined };
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* A nozzle without an identity chip reports serial "N/A" and keeps the
|
|
143
|
+
* unreliable bit set permanently (stock H2D HS01 nozzles), so its diameter
|
|
144
|
+
* and type are configured settings, as on single-nozzle printers.
|
|
145
|
+
*/
|
|
146
|
+
function hasNoIdentityChip(entry) {
|
|
147
|
+
return typeof entry?.sn === "string" && entry.sn.trim().toUpperCase() === "N/A";
|
|
148
|
+
}
|
|
149
|
+
/** Bit 0 (information unreliable) is accepted only from a nozzle without an identity chip. */
|
|
150
|
+
function unreliableWithoutChip(entry, stat) {
|
|
151
|
+
return (stat & 1) !== 0 && hasNoIdentityChip(entry);
|
|
152
|
+
}
|
|
153
|
+
/** Nozzles whose reported diameter and type are configured settings rather than chip readings. */
|
|
154
|
+
export function unchippedNozzles(raw) {
|
|
155
|
+
const info = raw?.device?.nozzle?.info;
|
|
156
|
+
if (!Array.isArray(info))
|
|
157
|
+
return [];
|
|
158
|
+
return info.flatMap(entry => {
|
|
159
|
+
const index = finiteNumber(entry?.id);
|
|
160
|
+
const stat = finiteNumber(entry?.stat);
|
|
161
|
+
return index !== undefined && Number.isInteger(index) && index >= 0 && index < 16 &&
|
|
162
|
+
stat !== undefined && Number.isInteger(stat) && stat >= 0 && unreliableWithoutChip(entry, stat) ? [index] : [];
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const loggedUnchippedNozzles = new Set();
|
|
166
|
+
/** Name each unchipped nozzle a check accepted once per printer on stderr, so headless runs still record it. */
|
|
167
|
+
function logUnchippedNozzles(raw, serial) {
|
|
168
|
+
for (const index of unchippedNozzles(raw)) {
|
|
169
|
+
const key = `${serial}:${index}`;
|
|
170
|
+
if (loggedUnchippedNozzles.has(key))
|
|
171
|
+
continue;
|
|
172
|
+
loggedUnchippedNozzles.add(key);
|
|
173
|
+
console.error(`Printer ${serial || "(unknown serial)"} nozzle ${index} reports no identity chip; its diameter and type are the printer's configured settings.`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
141
176
|
function reportedNozzles(raw) {
|
|
142
177
|
const info = raw.device?.nozzle?.info;
|
|
143
178
|
const output = [];
|
|
@@ -151,9 +186,13 @@ function reportedNozzles(raw) {
|
|
|
151
186
|
if (index === undefined || !Number.isInteger(index) || index < 0 || diameter === undefined || diameter <= 0)
|
|
152
187
|
throw new Error("Printer reported malformed nozzle identity or diameter.");
|
|
153
188
|
if (entry.stat !== undefined) {
|
|
189
|
+
// BambuStudio DevNozzle: bit 0 set means the information is unreliable;
|
|
190
|
+
// bits 1-2 are 0 normal, 1 abnormal, 2 unknown.
|
|
154
191
|
const stat = finiteNumber(entry.stat);
|
|
155
|
-
if (stat === undefined || !Number.isInteger(stat) || stat < 0 || (stat &
|
|
192
|
+
if (stat === undefined || !Number.isInteger(stat) || stat < 0 || (stat & 6) !== 0)
|
|
156
193
|
throw new Error(`Printer nozzle ${index} information is unreliable, abnormal or unknown.`);
|
|
194
|
+
if ((stat & 1) !== 0 && !unreliableWithoutChip(entry, stat))
|
|
195
|
+
throw new Error(`Printer nozzle ${index} information is unreliable; refresh the nozzle information on the printer.`);
|
|
157
196
|
}
|
|
158
197
|
if (output.some(nozzle => nozzle.index === index))
|
|
159
198
|
throw new Error("Printer reported ambiguous duplicate nozzle indices.");
|
|
@@ -338,6 +377,8 @@ export function validatePrinterState(status, requirements) {
|
|
|
338
377
|
filaments.push({ position, material, trayIndex, nozzleMin, nozzleMax });
|
|
339
378
|
}
|
|
340
379
|
}
|
|
380
|
+
if (nozzles.length)
|
|
381
|
+
logUnchippedNozzles(raw, identity.observedSerial ?? String(status.serial ?? ""));
|
|
341
382
|
return { model, reportedNozzles: nozzles, filaments };
|
|
342
383
|
}
|
|
343
384
|
/** Manual M104 cannot remap filament slots. Validate the material already at the nozzle. */
|
|
@@ -100,8 +100,28 @@ export declare class BambuCliProfilePreparer {
|
|
|
100
100
|
*/
|
|
101
101
|
private isolateMachineSettings;
|
|
102
102
|
}
|
|
103
|
+
/** How the CLI receives the model: Orca STL transforms are applied to a mesh copy first. */
|
|
104
|
+
export interface BambuCliInput {
|
|
105
|
+
slicerType: BambuCliSlicerType;
|
|
106
|
+
is3mf: boolean;
|
|
107
|
+
/** scale and rotations are already applied to the mesh passed to the CLI. */
|
|
108
|
+
meshTransformed: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Decide how the CLI receives the model. OrcaSlicer's CLI (upstream and FULU)
|
|
112
|
+
* crashes with SIGSEGV in PartPlate::check_outside, which needs the GUI's
|
|
113
|
+
* Plater, when --scale or --rotate-x/-y move an object below the bed while it
|
|
114
|
+
* arranges, the default for STL input. An Orca STL's transforms are therefore
|
|
115
|
+
* applied to a mesh copy; combinations the CLI cannot handle are refused.
|
|
116
|
+
*/
|
|
117
|
+
export declare function planBambuCliInput(slicerType: BambuCliSlicerType, is3mf: boolean, options: BambuSliceOptions): BambuCliInput;
|
|
118
|
+
/**
|
|
119
|
+
* The CLI's own reason for a failed run: result.json in the run's fresh output
|
|
120
|
+
* directory (written on Linux for handled errors), else the exit-code table.
|
|
121
|
+
*/
|
|
122
|
+
export declare function bambuCliFailureReason(exitCode: number | null, outputDir: string): string | undefined;
|
|
103
123
|
/** CLI flags for the prepared bundle and caller options (input path appended by the caller). */
|
|
104
|
-
export declare function buildBambuCliArgs(bundle: BambuSettingsBundle, outputDir: string, exportName: string, options: BambuSliceOptions): string[];
|
|
124
|
+
export declare function buildBambuCliArgs(bundle: BambuSettingsBundle, outputDir: string, exportName: string, options: BambuSliceOptions, input: BambuCliInput): string[];
|
|
105
125
|
/**
|
|
106
126
|
* A sliced Bambu project must contain at least one nonempty
|
|
107
127
|
* Metadata/plate_<n>.gcode entry. A .gcode.md5 checksum or the unsliced
|
package/dist/slicer/bambu-cli.js
CHANGED
|
@@ -469,8 +469,76 @@ const PROFILE_METADATA_KEYS = new Set([
|
|
|
469
469
|
"name", "type", "from", "inherits", "instantiation", "setting_id", "version", "is_custom_defined",
|
|
470
470
|
"compatible_printers", "compatible_printers_condition", "compatible_prints", "compatible_prints_condition",
|
|
471
471
|
]);
|
|
472
|
+
/**
|
|
473
|
+
* Decide how the CLI receives the model. OrcaSlicer's CLI (upstream and FULU)
|
|
474
|
+
* crashes with SIGSEGV in PartPlate::check_outside, which needs the GUI's
|
|
475
|
+
* Plater, when --scale or --rotate-x/-y move an object below the bed while it
|
|
476
|
+
* arranges, the default for STL input. An Orca STL's transforms are therefore
|
|
477
|
+
* applied to a mesh copy; combinations the CLI cannot handle are refused.
|
|
478
|
+
*/
|
|
479
|
+
export function planBambuCliInput(slicerType, is3mf, options) {
|
|
480
|
+
const input = { slicerType, is3mf, meshTransformed: false };
|
|
481
|
+
if (slicerType === "bambustudio")
|
|
482
|
+
return input;
|
|
483
|
+
const tilts = !!options.rotateX || !!options.rotateY;
|
|
484
|
+
if (is3mf) {
|
|
485
|
+
if (options.arrange === true && ((options.scale ?? 1) > 1 || tilts)) {
|
|
486
|
+
throw new Error("OrcaSlicer's CLI crashes when scale above 1, rotate_x or rotate_y moves a 3MF object below the bed while arranging. " +
|
|
487
|
+
"Set arrange to false, or apply the transform in the slicer and export the project.");
|
|
488
|
+
}
|
|
489
|
+
return input;
|
|
490
|
+
}
|
|
491
|
+
if (options.repetitions !== undefined && options.cloneObjects) {
|
|
492
|
+
throw new Error("Use either repetitions or clone_objects for an STL with OrcaSlicer, not both.");
|
|
493
|
+
}
|
|
494
|
+
return { ...input, meshTransformed: options.scale !== undefined || tilts };
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Bambu Studio and OrcaSlicer CLI errors (libslic3r/Utils.hpp). The process
|
|
498
|
+
* exits with the low byte of the code; on Linux result.json names the error.
|
|
499
|
+
*/
|
|
500
|
+
const BAMBU_CLI_ERRORS = {
|
|
501
|
+
[-2]: "invalid parameters to the slicer",
|
|
502
|
+
[-3]: "input files not found",
|
|
503
|
+
[-5]: "preset file cannot be parsed",
|
|
504
|
+
[-6]: "model file cannot be parsed",
|
|
505
|
+
[-9]: "copying objects failed",
|
|
506
|
+
[-17]: "printer is not compatible with the process preset",
|
|
507
|
+
[-21]: "auto-arranging objects failed",
|
|
508
|
+
[-22]: "auto-orienting objects failed",
|
|
509
|
+
[-50]: "a plate is empty or has no object fully inside it; arrange the model or place it on the plate",
|
|
510
|
+
[-51]: "incorrect slicing parameters",
|
|
511
|
+
[-52]: "objects extend over the bed boundary",
|
|
512
|
+
[-60]: "no printable objects remain after skipping",
|
|
513
|
+
[-61]: "filaments are not compatible with the plate type",
|
|
514
|
+
[-62]: "the filaments' temperatures differ too much",
|
|
515
|
+
[-63]: "objects collide in print-by-object mode",
|
|
516
|
+
[-64]: "objects collide",
|
|
517
|
+
[-100]: "slicing the model failed",
|
|
518
|
+
[-101]: "G-code conflicts after slicing",
|
|
519
|
+
[-102]: "G-code in an unprintable area",
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* The CLI's own reason for a failed run: result.json in the run's fresh output
|
|
523
|
+
* directory (written on Linux for handled errors), else the exit-code table.
|
|
524
|
+
*/
|
|
525
|
+
export function bambuCliFailureReason(exitCode, outputDir) {
|
|
526
|
+
try {
|
|
527
|
+
const result = JSON.parse(fs.readFileSync(path.join(outputDir, "result.json"), "utf8"));
|
|
528
|
+
if (typeof result?.error_string === "string" && result.error_string.trim()) {
|
|
529
|
+
return `${result.error_string.trim()}${result.return_code !== undefined ? ` [${result.return_code}]` : ""}`;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
// result.json is written only on Linux and only for handled errors.
|
|
534
|
+
}
|
|
535
|
+
if (exitCode === null)
|
|
536
|
+
return undefined;
|
|
537
|
+
const code = Object.keys(BAMBU_CLI_ERRORS).map(Number).find((value) => value === exitCode || (value & 0xff) === exitCode);
|
|
538
|
+
return code === undefined ? undefined : `${BAMBU_CLI_ERRORS[code]} [${code}]`;
|
|
539
|
+
}
|
|
472
540
|
/** CLI flags for the prepared bundle and caller options (input path appended by the caller). */
|
|
473
|
-
export function buildBambuCliArgs(bundle, outputDir, exportName, options) {
|
|
541
|
+
export function buildBambuCliArgs(bundle, outputDir, exportName, options, input) {
|
|
474
542
|
const args = [
|
|
475
543
|
"--slice", String(options.slicePlate ?? 0),
|
|
476
544
|
"--outputdir", outputDir,
|
|
@@ -496,16 +564,23 @@ export function buildBambuCliArgs(bundle, outputDir, exportName, options) {
|
|
|
496
564
|
args.push("--orient", options.orient ? "1" : "0");
|
|
497
565
|
if (options.arrange !== undefined)
|
|
498
566
|
args.push("--arrange", options.arrange ? "1" : "0");
|
|
499
|
-
if (options.repetitions !== undefined)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
567
|
+
if (options.repetitions !== undefined) {
|
|
568
|
+
// OrcaSlicer rejects --repetitions for one STL; it copies the loaded model with --clone-objects.
|
|
569
|
+
if (input.slicerType !== "bambustudio" && !input.is3mf)
|
|
570
|
+
args.push("--clone-objects", String(options.repetitions));
|
|
571
|
+
else
|
|
572
|
+
args.push("--repetitions", String(options.repetitions));
|
|
573
|
+
}
|
|
574
|
+
if (!input.meshTransformed) {
|
|
575
|
+
if (options.scale !== undefined)
|
|
576
|
+
args.push("--scale", String(options.scale));
|
|
577
|
+
if (options.rotate !== undefined)
|
|
578
|
+
args.push("--rotate", String(options.rotate));
|
|
579
|
+
if (options.rotateX !== undefined)
|
|
580
|
+
args.push("--rotate-x", String(options.rotateX));
|
|
581
|
+
if (options.rotateY !== undefined)
|
|
582
|
+
args.push("--rotate-y", String(options.rotateY));
|
|
583
|
+
}
|
|
509
584
|
if (options.cloneObjects)
|
|
510
585
|
args.push("--clone-objects", options.cloneObjects);
|
|
511
586
|
if (options.skipObjects)
|
|
@@ -5,6 +5,8 @@ export interface SlicerRunOptions {
|
|
|
5
5
|
slicerType: string;
|
|
6
6
|
/** Polled while the slicer runs; returning true kills it. */
|
|
7
7
|
isCancelled?: () => boolean;
|
|
8
|
+
/** The slicer's own reason for a nonzero exit, read before its output directory is removed. */
|
|
9
|
+
failureReason?: (exitCode: number | null) => string | undefined;
|
|
8
10
|
}
|
|
9
11
|
export interface SlicerRunResult {
|
|
10
12
|
stdoutTail: string;
|