From d120a804ff4514a3b304eaf31ab843c426cf30e9 Mon Sep 17 00:00:00 2001 From: maziggy Date: Tue, 11 Aug 2026 08:35:23 +0200 Subject: [PATCH] fix(slicer): name the sidecar service in the update command (#2802) The "update your sidecar image" advice told users to run a bare `docker compose pull`. bambu-studio-api is declared with `profiles: [bambu]`, and compose skips profile-gated services silently, so the pull was a no-op for exactly the users the message was written for -- and `restart: unless-stopped` kept the old container serving. The reporter pulled, restarted, set MAX_MODEL_UPLOAD_MB and got the same 100 MB rejection, because the image never changed. Name the service in both commands instead. Naming enables the profile implicitly, for pull and up alike. `--profile bambu` would also work but downloads the 220 MB Bambu image on an OrcaSlicer-only host and then starts a sidecar the user never asked for. Same correction in the sidecar README, the compose header and the changelog entry, which all carried the bare form. --- CHANGELOG.md | 2 +- backend/app/services/slicer_api.py | 18 ++++++++++--- .../unit/test_slicer_upload_size_rejection.py | 27 +++++++++++++++++++ slicer-api/README.md | 22 +++++++++++++-- slicer-api/docker-compose.yml | 5 ++++ 5 files changed, 68 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5aefef64..d240daa45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ All notable changes to Bambuddy will be documented in this file. - **The slice dialog showed a guessed filament list for projects saved by a newer Bambu Studio than the slicer sidecar** — Opening the slice dialog on an unsliced project runs a quick preview slice, purely to ask the slicer which AMS slots the chosen plate actually consumes. Bambu Studio 2.8 writes a machine G-code template containing `{if timelapse_inline_photo}` but does not export a definition for that variable, so the template is unresolvable the moment it leaves Studio: a sidecar running an older build stops with a placeholder parse error before producing any slice data. The preview then returned nothing and the dialog quietly fell back to guessing from the file's painted faces, with no indication the numbers were an estimate. On the H2D project this was found with, the guess dropped a whole slot -- the support material -- from a four-filament plate. Bambuddy now retries the preview once with just that one unparsable template emptied, leaving every other setting in the file untouched, which is what keeps the answer honest: the process settings, support configuration and per-slot filament assignments are all still the project's own, so the filament list and its gram figures match what the file would really print. Only templates that cannot extrude are ever emptied -- a start or filament-change template lays a prime line or purges, so silencing one would change the very grams the preview reports, and Bambuddy would rather return nothing than a confident wrong number. Verified against a real H2D slice: the retry reproduces the full four-slot list, gram for gram. Covered by backend tests. - **Cancelling or deleting a queued item did not stop the preheat already running for it (#2727, contributed by @ticfinack)** — The cancel and delete routes write the item's new status to the database, and nothing else. A dispatch that had already begun preheating was parked in a sleep waiting for the chamber to reach temperature, where it could not see that write — so the heaters kept running out the rest of the wait and soak for a print that was not going to happen, twenty minutes at the default settings and longer if those have been raised, and the printer stayed marked busy the whole time, holding up every other job queued behind it. Those routes now tell the scheduler directly and the waits are taken in slices, so a cancelled preheat is abandoned within seconds and the heaters are switched off on the way out. The same unwinding now covers every other way a dispatch can end without starting a print — a failed upload, an error mid-dispatch, a plate whose file has gone missing — each of which used to leave the bed and chamber heating with nothing to turn them off. What preheat set is recorded and reversed, and a bed the printer reports at some other target is left alone rather than switched off, on the grounds that it belongs to whoever set it. A cancellation that lands after the upload has begun still starts the print, as it always has. Covered by backend tests. - **STEP files were offered for server-side slicing, which cannot work** — The **Slice** action appeared on `.step` / `.stp` files and the backend accepted the job, but neither slicer can load one from its command line: OrcaSlicer 2.4.2 and Bambu Studio 02.07.01.62 both answer `Unknown file format. Input file must have .stl, .obj, .amf(.xml) extension.` So the file was read, converted and uploaded, and the failure came back as "The input model file to the slicer can not be parsed" — which reads as a corrupt model rather than an unsupported format. The Slice button and the pipeline action no longer appear on STEP files, and the endpoint refuses one up front with a message that says to export it as STL or 3MF first. **Open in Slicer** is unchanged and still hands STEP to the desktop application, which opens it perfectly well — that was always the working path for these files. -- **A large model was refused with "Slicer CLI failed (500): File too large" and no way to find out what was too large (#2802, reported by @zevulos)** — Server-side slicing of a big multi-colour project failed on every attempt, and the message pointed at nothing. The slicer sidecar caps the size of the model it will accept; that cap was fixed at 100 MB, which real MakerWorld projects exceed. Worse than the limit was how it arrived: the sidecar's upload layer reports a size rejection as a kind of error its own handler does not recognise, so it fell through to a generic **HTTP 500** carrying the bare words "File too large". A 500 reads as a crash inside the slicer, and Bambuddy's one good explanation about request size was written for the HTTP 413 that a reverse proxy sends, so it never appeared. The reporter did the only reasonable thing with what they were shown: set `MAX_FILE_SIZE`, `BODY_PARSER_LIMIT` and `EXPRESS_PAYLOAD_LIMIT`, restart everything, stop nginx in case it was interfering, and move the whole installation from Windows to Docker — none of which the sidecar reads, on a proxy that was never in the path. The cap is now **512 MB** by default and settable with `MAX_MODEL_UPLOAD_MB` on the slicer-api service, and the sidecar answers an oversized upload with a 413 that names the limit and where it lives. Bambuddy recognises the rejection by what it says rather than by its status code, so an installation still running an older sidecar image gets the same explanation — including that the fix there is to update the image, since those have no setting to change. Two things followed from the same misreading: the failure was classed as a slicer crash, so every attempt retried the identical oversized upload "with embedded settings", spending a second 25-second conversion on a guaranteed-identical answer; and nothing anywhere recorded the size of what was being sent, so the support package from a slice that died on an upload cap looked exactly like one that died on a bad profile. Both are fixed — the retry is skipped, and each slice logs the model's size. Raising the cap also changed how the sidecar handles the upload: the model is streamed to disk instead of being held whole in memory, so a 512 MB project no longer costs half a gigabyte of RAM per concurrent slice on the small machines most likely to be running it. **This needs a sidecar update to take effect** — `cd slicer-api/ && docker compose pull && docker compose up -d`. +- **A large model was refused with "Slicer CLI failed (500): File too large" and no way to find out what was too large (#2802, reported by @zevulos)** — Server-side slicing of a big multi-colour project failed on every attempt, and the message pointed at nothing. The slicer sidecar caps the size of the model it will accept; that cap was fixed at 100 MB, which real MakerWorld projects exceed. Worse than the limit was how it arrived: the sidecar's upload layer reports a size rejection as a kind of error its own handler does not recognise, so it fell through to a generic **HTTP 500** carrying the bare words "File too large". A 500 reads as a crash inside the slicer, and Bambuddy's one good explanation about request size was written for the HTTP 413 that a reverse proxy sends, so it never appeared. The reporter did the only reasonable thing with what they were shown: set `MAX_FILE_SIZE`, `BODY_PARSER_LIMIT` and `EXPRESS_PAYLOAD_LIMIT`, restart everything, stop nginx in case it was interfering, and move the whole installation from Windows to Docker — none of which the sidecar reads, on a proxy that was never in the path. The cap is now **512 MB** by default and settable with `MAX_MODEL_UPLOAD_MB` on the slicer-api service, and the sidecar answers an oversized upload with a 413 that names the limit and where it lives. Bambuddy recognises the rejection by what it says rather than by its status code, so an installation still running an older sidecar image gets the same explanation — including that the fix there is to update the image, since those have no setting to change. Two things followed from the same misreading: the failure was classed as a slicer crash, so every attempt retried the identical oversized upload "with embedded settings", spending a second 25-second conversion on a guaranteed-identical answer; and nothing anywhere recorded the size of what was being sent, so the support package from a slice that died on an upload cap looked exactly like one that died on a bad profile. Both are fixed — the retry is skipped, and each slice logs the model's size. Raising the cap also changed how the sidecar handles the upload: the model is streamed to disk instead of being held whole in memory, so a 512 MB project no longer costs half a gigabyte of RAM per concurrent slice on the small machines most likely to be running it. **This needs a sidecar update to take effect**, and the command has to name the sidecar: `cd slicer-api/ && docker compose pull orca-slicer-api && docker compose up -d orca-slicer-api`, substituting `bambu-studio-api` if that is the one you slice with. A bare `docker compose pull` looks like it works and does not: the Bambu Studio sidecar is declared behind a Compose profile, and Compose skips profile-gated services without saying so, leaving the old container running under `restart: unless-stopped`. The reporter hit exactly that on the first attempt at this fix — pulled, restarted, set `MAX_MODEL_UPLOAD_MB=5000`, and got the same 100 MB rejection, because the image never changed. Bambuddy's message, this changelog, the wiki and the sidecar README all gave the bare command; all four now name the service. - **`/auth/me` described API keys as administrators they were never allowed to be (#1894, reported by @MorganMLGman)** — Asked to identify an API key, Bambuddy answered with a synthetic administrator: user id `0`, role `admin`, `is_admin: true`, and every permission in the system. None of that was true. An API key cannot reach an administrative route at all, whatever its scopes and whoever owns it, so a client that built its interface from this answer — which is exactly what a native app does — offered buttons that failed with a permission error the moment anyone pressed one, and still had no way to learn which user id its own prints were filed under. The endpoint now reports the key's **owner** as its identity, `is_admin: false`, and a permission list containing precisely what the key's scopes admit, so what a client is told matches what it will be allowed to do. Keys created before keys had owners have no identity to report and keep the old `id: 0` placeholder, but they no longer claim to be administrators either. Clients that branched on `is_admin` or `role` should branch on `permissions` instead. - **An unacknowledged plate no longer stops AMS drying once a minute, for ever (#2801, reported by @superflyer11)** — With "require plate clear" on, a finished print left unacknowledged and something pending in that printer's queue put the scheduler into a loop: it stopped drying, auto-drying re-armed on the next tick, and it stopped it again — around 2000 state changes over ten days on the reporter's P2S, with no cycle ever running long enough to remove any moisture. Cycles the user had started by hand on other AMS units of the same printer were torn down with it. Two ideas had become tangled. Plate-clear answers "is the bed ready for the next job", which says nothing about whether the AMS may heat — and the gap between a finished print and the acknowledgment is exactly when drying is most useful, since the printer is free and nobody is waiting on it. Leaving the plate unacknowledged is also how people hold the queue by hand, so the hold was costing them the drying it should have enabled. On top of that, the "print takes priority" stop was reached only on the passes where the print was *not* going to start: drying is not one of the things the idle check looks at, so stopping a cycle could never make a blocked printer dispatchable, and the cycle was spent for nothing. Auto-drying no longer consults plate-clear at all; the stop now happens on dispatches that are actually going to proceed, and only where the model cannot dry through a print — hardware that can, and has been allowed to, keeps drying as #2758 established it should. A stop is also confined to cycles Bambuddy itself started, matching a contract the code already documented but did not honour, so a manual dry on another unit is left alone. Two smaller faults went with it: a printer merely waiting on the plate was being classed as mid-print, which silently applied the mid-print spool-protection cap to a printer that was not printing and logged the cycle as `(mid-print)` in `FINISH`; and a humidity reading that dipped to the threshold as the AMS cooled discarded the unit's whole history, including the 30-minute re-arm cooldown added in #2770 — so a reading oscillating a point either side of the threshold reset the very guard meant to ride it out. **One behaviour change to be aware of:** "Block queue while drying" previously had no effect on dispatch at all, and now does what it says — with it on, a queued print waits for a running cycle to finish. It is off by default. - **An H2C could clean and level with one hotend and then print with another, several millimetres above the plate (#2800, reported by @tru3l3gend)** — The reporter's H2C ran its startup clean and bed levelling on the wrong nozzle, switched hotends, and then printed in mid-air; the same job sent from Bambu Studio was fine. The H2C is the only printer that mounts its nozzle from a rack of six, and a print command names that nozzle by its *physical* rack position — the firmware reports those as IDs 16 to 21 — rather than by the extruder index, 0 or 1, that every other dual-nozzle printer uses. Bambuddy only ever had a rack position when a job arrived through the Virtual Printer, which captures Bambu Studio's own pick and replays it untouched (#1780). Anything queued from the library, from an archive, through the webhook or from a slicer pipeline carried none, so the field was left off the command entirely and the firmware chose a nozzle for itself — and its choice does not have to agree with the one the file was sliced for. Bambuddy now reads the per-slot extruder assignment out of the file it is about to dispatch and resolves it against the rack position the printer is reporting at that moment, which is the only place it can be known: the mounted hotend can be swapped from the touchscreen between queueing a job and printing it. Nothing about this is guessed. When the rack position cannot be established — mid-swap, or a connection that has not yet reported one — the field is left off and the firmware picks exactly as it did before, because a wrong physical ID is what puts a print in the air and is far worse than no ID at all. For the same reason a job that prints only from the fixed hotend is still left to the firmware: that nozzle's own physical ID has not yet been confirmed against a known-good Bambu Studio capture, and it will not be invented. Confined to the H2C throughout — the dispatch for every other printer, including the H2D and X2D, is unchanged. Diagnosed on real hardware by the reporter, who compared Bambuddy's dispatch against a working Bambu Studio one, established the rack ID range, and supplied a patch. diff --git a/backend/app/services/slicer_api.py b/backend/app/services/slicer_api.py index 4317f0d0a..de8fabd52 100644 --- a/backend/app/services/slicer_api.py +++ b/backend/app/services/slicer_api.py @@ -199,10 +199,22 @@ def _upload_size_rejection(response: httpx.Response, model_size_bytes: int | Non f"{common} Raise it by setting MAX_MODEL_UPLOAD_MB on the slicer-api service and " f"restarting it. Sidecar said: {detail}" ) + # Naming the service in the compose commands is not a style choice. The + # Bambu Studio sidecar sits behind `profiles: [bambu]`, and a bare + # `docker compose pull` silently skips every profile-gated service — so the + # update this message asks for was a no-op for exactly the users who need + # it, and `restart: unless-stopped` kept the old container serving (#2802, + # second round). Naming a service enables its profile implicitly, for both + # pull and up. `--profile bambu` would also work, but on an OrcaSlicer-only + # host it downloads the 220 MB Bambu image and then *starts* a sidecar the + # user never asked for. return ( - f"{common} This sidecar image predates the configurable cap and is fixed at 100 MB — " - "update it with 'cd slicer-api/ && docker compose pull && docker compose up -d', which " - "raises the default and adds MAX_MODEL_UPLOAD_MB for going higher still. " + f"{common} This sidecar image predates the configurable cap and is fixed at 100 MB. " + "Update it with 'cd slicer-api/ && docker compose pull orca-slicer-api && " + "docker compose up -d orca-slicer-api', substituting 'bambu-studio-api' if that is the " + "sidecar you slice with. Name the service in both commands — a bare 'docker compose pull' " + "skips the Bambu Studio sidecar, because it sits behind a compose profile. The new image " + "defaults to 512 MB and adds MAX_MODEL_UPLOAD_MB for going higher still. " f"Sidecar said: {detail}" ) diff --git a/backend/tests/unit/test_slicer_upload_size_rejection.py b/backend/tests/unit/test_slicer_upload_size_rejection.py index 005f31806..46ff5c3d7 100644 --- a/backend/tests/unit/test_slicer_upload_size_rejection.py +++ b/backend/tests/unit/test_slicer_upload_size_rejection.py @@ -140,6 +140,33 @@ class TestTheMessageIsActionable: assert "docker compose pull" in message assert "100 MB" in message + @pytest.mark.asyncio + async def test_the_update_command_names_the_service(self): + """A bare ``docker compose pull`` does not update the Bambu sidecar. + + ``bambu-studio-api`` is declared with ``profiles: [bambu]``, and compose + skips profile-gated services unless the profile is enabled or the + service is named. The advice this message used to give was therefore a + no-op for Bambu Studio users -- they pulled, saw "up to date", restarted + into the same 100 MB image and came back to the issue (#2802). + + Both commands are checked: pulling the right image is useless if the + ``up -d`` that follows leaves the old container running. + """ + svc = _service(_responder(500, {"message": "File too large"})) + + with pytest.raises(SlicerInputError) as excinfo: + await svc.slice_with_profiles(**SLICE_ARGS) + + message = str(excinfo.value) + assert "docker compose pull orca-slicer-api" in message + assert "docker compose up -d orca-slicer-api" in message + assert "bambu-studio-api" in message + # The bare forms must not appear at all -- a reader who copies the first + # command they see must not get the one that silently does nothing. + assert "docker compose pull &&" not in message + assert "docker compose up -d'" not in message + @pytest.mark.asyncio async def test_a_current_sidecar_is_told_which_variable_to_set(self): """Once the image is current, the fix is one env var, not another pull.""" diff --git a/slicer-api/README.md b/slicer-api/README.md index 605013067..1f203a74d 100644 --- a/slicer-api/README.md +++ b/slicer-api/README.md @@ -84,13 +84,31 @@ flipped back to `ghcr.io/afkfelix/orca-slicer-api`. ## Updating +OrcaSlicer only (the default): + ```bash docker compose pull +docker compose up -d +``` + +With the Bambu Studio sidecar — the profile flag belongs on **both** +commands: + +```bash +docker compose --profile bambu pull docker compose --profile bambu up -d ``` -That's it — Compose pulls the current `:latest` (or whatever -`SIDECAR_TAG` you've pinned to) and recreates the containers. +`bambu-studio-api` sits behind `profiles: [bambu]`, and a bare +`docker compose pull` skips profile-gated services silently: it reports +success, `restart: unless-stopped` keeps the old container serving, and +you stay on the old image no matter how often you repeat it. To update +one sidecar only, name it — `docker compose pull bambu-studio-api && +docker compose up -d bambu-studio-api` — which enables its profile +implicitly. + +Compose pulls the current `:latest` (or whatever `SIDECAR_TAG` you've +pinned to) and recreates the containers. To roll back to the sidecar that shipped with a previous Bambuddy release, set `SIDECAR_TAG=bambuddy-X.Y.Z` in `.env` and re-run the two diff --git a/slicer-api/docker-compose.yml b/slicer-api/docker-compose.yml index 35bdbd005..a814f540c 100644 --- a/slicer-api/docker-compose.yml +++ b/slicer-api/docker-compose.yml @@ -17,6 +17,11 @@ # docker compose up -d # starts OrcaSlicer only # docker compose --profile bambu up -d # starts both # +# Updating: `docker compose pull` skips profile-gated services, so a bare +# pull silently leaves bambu-studio-api on its old image (and restart: +# unless-stopped keeps it serving). Use `docker compose --profile bambu pull`, +# or name the service: `docker compose pull bambu-studio-api`. +# # First start pulls pre-built images from GHCR (~110 MB OrcaSlicer, # ~220 MB BambuStudio). No local build, no git in the BuildKit worker, # works on QNAP / Synology / Container Station out of the box.