7 Commits
Author SHA1 Message Date
maziggy 02e39b18b9 Configure Orca Cloud filaments with their own filament ID (#3216)
OrcaSlicer's Sync filaments finds a slot's preset by the slot's filament
ID alone. The Configure dialog looked up an Orca profile's ID in the
browser and quietly sent the generic for the material when that came back
empty, so Orca custom filaments reached the slicer as Generic and the log
showed nothing. configure now resolves the ID on the server from
orca_profile_id, follows inherits to the parent profile or the Bambu
filament it was copied from, logs the outcome and reports a fallback,
which the dialog shows as a warning.

Slot presets also store the filament ID they were written with, so the
slot card and the Configure dialog stop showing a preset once the slot is
changed from OrcaSlicer's Device tab or the printer.

Re-configuring a slot for another filament no longer carries over the old
filament's active K-profile, which switched the slot back to the old
filament.
2026-10-02 08:32:57 +02:00
maziggy f3b1c59169 fix(orca-cloud): close the HTTP client when an authenticated build fails
OrcaCloudService owns an httpx client from construction, and every path in
_build_authenticated_service after that point can raise: no stored refresh
token, a rejected refresh, an unreachable Orca, and the token-rotation write.
On success the caller closes the client. On failure nobody is ever handed it,
so all four paths leaked one into the connection pool.

That went unnoticed while the only callers were routes, where the trigger is a
person retrying a broken sign-in a handful of times. It stopped being harmless
in 9434875f, which added a caller in spool assignment -- one build per
Orca-referenced spool, failing on every assignment for as long as the stored
credentials cannot be refreshed.

The unwind guard catches BaseException rather than Exception: a cancelled
request leaks the client just as surely as a failed refresh, and cancellation
during shutdown is exactly when dangling sockets are least welcome. The close
inside it is guarded in turn, so a failing cleanup cannot replace the error the
caller needs to see -- least of all a CancelledError, which has to keep
propagating for cancellation to work at all.

Six tests. Four fail against the unguarded builder, verified by reverting the
guard and re-running; the other two pin the surrounding contract (a failing
close must not mask the real error, and a successful build must leave the
client open for its caller) and pass either way. The shared _expired_service
helper now gives the mock an awaitable close(), so the four pre-existing
refresh tests exercise the same path.

Also corrects two comments and the changelog entry from 9434875f, which
overstated what the captures support. They claimed Bambu Cloud returns a
preset's filament_id in either of two places and only one was read. The
responses recorded in #1053 show something narrower: a Studio-created preset
carries it on the envelope, and an Orca-created one has none at all -- the
envelope says null and `setting` is a delta from the base. The `setting` lookup
stays as belt-and-braces for a shape no captured response has needed yet, but
it is not why a custom profile reached the slicer as its base. That is the
OrcaSlicer preset format having no filament_id field, filed upstream as
OrcaSlicer PR #13315.

The eight-character truncation is now evidenced across three models rather than
one -- an A1 storing PFUS9DDC of PFUS9DDC938FE3AB8F, a P1S storing PFUS7A65 of
PFUS7A65290D3DADC4, and an H2D storing 8219C45D of an Orca profile UUID.
2026-09-07 10:39:10 +02:00
maziggy 9434875fa1 fix(ams): resolve a custom filament's own id from every preset source (issue #3003)
A custom filament profile reaches an AMS slot as itself through exactly one
field, tray_info_idx, and every source we can read that id from was reading it
from the wrong place or not reading it at all.

Bambu Cloud returns a preset's own filament_id either on the response envelope
or inside the preset JSON under `setting`, and only the envelope was read.
Presets of the second shape fell through to the base_id branch and reached the
slicer as the Bambu filament they inherit from. filament_type next door already
handled both spreads; filament_id now does too.

Orca Cloud was absent from the resolver entirely. A spool stores the bare
profile UUID, which matched no branch and fell through normalize_slicer_filament
-- a function that passes anything it does not recognise straight through -- so
a 36-character UUID went into the field. Orca profiles carry their own
filament_id in the slicer JSON that OrcaProfileDetail already exposes under
`setting`, so the lookup is the same one the Bambu branch does. It is
best-effort: no pairing, a dead token or a missing orca_cloud:auth permission
degrades to the fallback rather than failing the assignment, and it passes
clear_on_auth_failure=False because a background caller cannot tell a real
revocation from a lost refresh-rotation race.

configure_ams_slot sent the cloud setting_id as tray_info_idx when it found no
real filament id. That field is 8 characters on the printer -- exactly the width
of a local preset id, less than half a cloud one. Measured on the reporter's A1:
sent PFUS9ddc938fe3ab8f, the tray read back PFUS9DDC, acknowledged as a success.
The slot then resolved to nothing, so the slicer showed Generic anyway and the
calibration table, keyed by the same field, lost the slot. It now falls back to
the slot's existing filament id or the generic for the material, and the route's
guard was aligned with the resolver's so both refuse the same four shapes from
one shared definition.

This reverses the contract #1053 pinned. Six tests asserted that the PFUS
belonged in tray_info_idx; the A1 capture shows it never worked, so they were
rewritten with the measurement in their docstrings.

Verified against 874 AMS trays across twelve models in the support archive: 92
already carry a custom "P" + 7 hex filament id, which is what confirms the
mechanism works and this is a lookup failure rather than a platform limit. No
tray on any model carries a setting_id, so a profile with no filament_id of its
own still cannot be told apart from its base.
2026-09-07 10:15:06 +02:00
maziggy 7b181b84f0 Let a filled or foamed filament keep its own name (issue #2902)
The reduction that gave an AMS slot a material type read PLA-AERO,
PLA-GF, ASA-GF and PPS-GF as their base material, so a slot loaded with
foaming or glass-filled filament went out saying plain PLA or ASA. That
is worse than the bug it replaced. "PLA-AERO" matched nothing before,
which was useless but honest; "PLA" matches every PLA plate in the
queue, so the dispatcher would have sent one to filament that will not
print it -- and the contract the first fix claimed, that it could only
ever repair a slot, no longer held. @doncaruana caught PLA Aero on the
issue.

All four are values Bambuddy itself offers: filament_fields.json is the
material list the Profiles editor puts in a dropdown, and the reduction
table was assembled from the cloud filament names and the frontend
preset parser without ever being checked against it. It is checked now,
so the next type added to one and not the other fails a test rather than
a print. ASA-AERO joins them from the cloud catalogue (GFB02).

The table hyphenates because the slicers do, while a spool says "PLA
Aero" and every Bambu preset name says "Bambu PLA Aero". Adjacent words
are joined and taken when the join is a type exactly -- exactly, because
letting the prefix and suffix rules reach across a space would make
"Support for PLA" a type by its tail.

Also from @doncaruana, and the better half of his point: a preset is
chosen from a list the slicer defines, so it already knows its own type
and nothing has to be read out of a product name. The resolver now hands
that answer back and both assign routes prefer it. It cannot be the only
source -- material is required on a spool and slicer_filament is not, and
the spool this issue was reported for had no preset at all -- so the
reduction stays as the fallback for spools without one.

Two things had to move with it. The auto-unlink guard compared the
slot's reported type against the reduced material, so a spool whose
preset outranked its material column would have been unlinked from the
slot it had just been assigned to; it now accepts any type the assign
path could have written. And two lookups keyed by material took the
catch-all for a type they had no row for, which sent an ASA-GF spool out
at 200/240 -- too cold to extrude -- and preheated its chamber to
nothing. Both fall back to the base material last, so PLA-CF, PETG-CF
and PA-CF keep the rows they are listed with, and ASA-CF and ABS-GF pick
up ranges they had been missing all along.

What counts as a material name is decided by the base for the same
reason: saying yes throws the value away and rescues the slot from the
generic-material fallback, so the answer has to be no when that fallback
has nothing to offer. ABS-GF reduces to a generic ABS the printer can
resolve; PPS-CF reduces to nothing and is left as it stands. Adding a
type to the table therefore cannot quietly change that answer, which is
how these five slipped through in the first place.

------

Hand the bundled chamber-preheat table back the way it is read

Every lookup of the per-filament chamber map happens after the keys are
upper-cased, and the parser documents exactly that: keys uppercased,
DEFAULT always present so the resolution loop can index it
unconditionally. The three fallback paths returned the bundled constant
as declared, with the lowercase "default" row the Settings editor writes
and displays, so an install that had never opened the setting got a dict
the loop could not read its fallback out of and used a hardcoded 0 for
any filament without a row of its own.

It reported the right number only because that bundled default is 0.
Raising it would have changed nothing for everyone who had not
customised the map, with the map in Settings still showing the value
that was not being used.

The test that should have caught this asserted the fallback under either
spelling, and its docstring contradicted itself between title and
comment. It pins the contract now, over all four ways the parser can
fall back.
2026-08-23 10:36:50 +02:00
maziggy 88e8ca81c3 Give an AMS slot a material type, not a product name (issue #2902)
Assigning a spool wrote its material straight into the slot's tray_type.
A slot that says "PLA+" satisfies nothing that asks for PLA: not
OrcaSlicer, not Bambu Studio, and not Bambuddy's own dispatch matcher,
which compares the type the printer reports to the one the 3MF declares
as plain equality. The reporter's slot was unusable for every PLA plate
he had.

Not only a label, either. The same string went into the generic-filament
lookup, which missed, so the slot went out with no tray_info_idx at all
-- the half-configured state #2604 documents the printer as reverting
from -- and took the 200/240 catch-all nozzle range instead of PLA's
190/230.

PLA+ is not a special case. Bambuddy's own colour catalogue supplies the
material dropdown, and around forty of its values are vendor product
lines rather than filament types: HTPLA, PolyTerra PLA, PLA Matte, ASA
Extrafill, Flexfill TPU 98A.

So the four routes that configure a slot reduce the material to a name
the printer knows before sending it, and the product name moves to
tray_sub_brands -- which is where Bambu Lab puts it too: their catalogue
carries a preset named "eSUN PLA+" whose type is PLA. A name the
reduction cannot place is sent exactly as before rather than guessed at,
so this can only repair a slot, never break a working one. The spool's
own wording still leads the id and temperature lookups with the reduced
type appended behind it, so "PETG HF" keeps its own generic preset
(GFG96) rather than being traded down to plain PETG's.

Two guards decide whether a candidate filament id is really a material
name -- the resolver's, which discards one, and slot reuse, which will
not carry one forward. Both saw only bare types, so "PLA+" passed as a
filament id. They share one answer now, which also refuses to read an
id-shaped value: "GFPLA" ends in a material name, and reducing it would
throw away the calibrated preset in the slot.

One thing had to move with it. on_ams_change auto-unlinks an assignment
whose slot stopped looking the way it did when the spool was assigned,
and the check that spares a slot Bambuddy itself reconfigured compared
the printer's reported type against the spool's raw material. With the
slot now carrying the reduced type, every spool this issue is about
would have been unlinked from the slot it had just been assigned to.
Both sides are reduced there -- the printer's too, so slots configured
by an older version, still reporting "PLA+", keep matching.

Something starts working as a result: a slot holding a calibrated preset
is reused when a same-material spool is assigned to it, which could not
happen for these spools while "PLA" and "PLA+" compared unequal.

Reverting any one of the behaviours above fails a distinct test -- the
reduction's four matching rules and its pass-through contract included,
since that contract is what makes the rest of it safe.
2026-08-22 11:41:28 +02:00
maziggy 261c376d1f fix(spoolbuddy): close #1815 — preserve PFUS/PFCN setting_id in resolver
SpoolBuddy "Assign to AMS" with a Bambu Cloud user preset (PFUS) left
  Bambu Studio showing "Generic <Material>" instead of the user's
  custom preset. Root cause: the defensive filter that catches
  PFUS/PFCN leaks into tray_info_idx also cleared setting_id —
  but PFUS/PFCN are VALID setting_id values, just not valid
  tray_info_idx values. When the cloud detail lookup didn't return
  a filament_id (cloud unauth on the on_ams_change replay path,
  transient failure, or older custom presets), both fields got
  cleared and the caller's generic-material fallback overwrote
  setting_id with GFSG99 — slicer resolved to Generic PETG.

  Fix: the filter still clears tray_info_idx for PFUS/PFCN/material-
  name leaks, but preserves setting_id when it's a valid slicer
  reference (PFUS / PFCN / GFS). Material-name leaks still clear
  both. Post-fix MQTT carries tray_info_idx=GFG99 (firmware-acceptable
  for HMS/drying/colour) AND setting_id=PFUS<hash> (slicer uses this
  to load the actual user preset).

  What stays the same: Bambuddy's own AMS card still displays
  the generic material on cloud-unauth paths — same fundamental
  limitation as today. Fixing that needs a deeper layered fallback
  (LocalPreset name match, printer kprofile query, cloud-detail
  cache) and is out of scope for this drop. Slicer-side fix is
  the reporter's explicit ask.
2026-06-25 09:26:33 +02:00
maziggy 282aefc564 Sync and housekeeping 2026-06-11 15:39:02 +02:00