Commit Graph
2272 Commits
Author SHA1 Message Date
maziggy e634550de7 Add "Combine to 3MF" for slicing several STLs on one plate (#3162) 2026-10-01 13:00:10 +02:00
adman234 b67c9b572c Add "Combine to 3MF" for slicing several STLs on one plate (#3162) 2026-10-01 08:11:19 +02:00
maziggy e7ca347de4 Start queued jobs in list order, whether pinned or "Any <model>" (#3200) 2026-09-30 16:29:07 +02:00
maziggy fdf062a64a Add layers, job id, HMS faults and serial to the API-key printer status (#2919) 2026-09-30 16:27:31 +02:00
maziggy 22436a3416 Choose what a spool label shows, preview it, and save it as PNG (#2981, #2980) 2026-09-30 16:26:58 +02:00
maziggy 3c164afa24 Match a Bambu roll to its own product line, not just its colour (issue #2907) (#2944) 2026-09-30 16:26:40 +02:00
maziggy 177f136ea3 Resolve LDAP groups on lldap and OpenLDAP (#3197)
lldap and OpenLDAP's memberof overlay omit memberOf from "*", so every
    lldap login fell through to the default group. Request memberOf by name
    when the schema defines it, and on non-AD directories also search the
    directory root for groupOfNames/groupOfUniqueNames entries listing the
    user, since groups often sit outside the user search base and the
    overlay tracks only one group class.

    Also: skip ldap3's anonymous schema read after StartTLS, which AD and
    Samba AD reject, so StartTLS works there; reword a server's StartTLS
    refusal with an LDAPS hint; stop the bundle sanitizer masking part of
    an OID as an IP; skip the sync right after auto-provisioning so the
    default-group warning logs once.
2026-09-30 16:26:10 +02:00
maziggy a26dd51e5a Use the Spoolman spool's own initial_weight as its label weight (#3194)
Spoolman keeps a full spool's net weight on the spool (initial_weight)
    and falls back to the filament's weight, but Bambuddy read only the
    filament, so a 250 g spool of a 1000 g filament showed and synced as
    1000 g. The list, weigh, AMS sync, SpoolBuddy scale, remain-% tracking,
    fill bar and cost now share one lookup. Create writes initial_weight,
    and a label-weight edit writes it instead of patching or duplicating the
    filament. The spool form's cost per kg is converted at the spool's size
    to and from Spoolman's per-spool price.
2026-09-30 16:25:56 +02:00
maziggy 5e5fcbba26 Use the vendor's empty spool weight as tare in Spoolman mode (#3195)
Spoolman resolves a spool's tare from the spool, then the filament, then
    the vendor's empty_spool_weight. Bambuddy skipped the vendor and fell
    back to 250 g, so weighing a spool whose tare was set only on its vendor
    gave the wrong remaining weight. The inventory weigh action, the
    SpoolBuddy scale and the displayed core weight now share one lookup, and
    "keep old weight" on a filament change stamps a vendor-inherited tare.
2026-09-30 16:25:40 +02:00
maziggy 835049dd43 Map the P-series model codes to the right printers
C11 is the P1P, C12 the P1S, C13 the X1E and N7 the P2S, as the virtual
    printer and a real P1P 3MF already say. The frontend map had them shifted,
    so discovery pre-filled a P1S as a P1P and an X1E as a P2S. The backend
    map read C11/C12 as X1C/X1 and lacked N7, the firmware check sent C13 to
    the P2S line, and the capability lists never matched BL-P001 because
    their lookup strips the dash.

    -----

    Post work PR #3134
2026-09-30 16:24:42 +02:00
maziggy 0f32b71827 fix: display friendly legacy printer model names in stream overlay (#3134) 2026-09-30 16:24:23 +02:00
maziggy d09f9d8cc1 Sync OIDC provider groups to BamBuddy groups on every login (issue #3107) (#3122) 2026-09-30 16:23:47 +02:00
maziggy a5965cac34 Post work PR #2994 2026-09-30 16:23:33 +02:00
maziggy d30e0e685b Material number as a first-class spool field (#2994) 2026-09-30 16:23:17 +02:00
maziggy 451be985fb Give the Low Filament notification something that fires it (issue #2913) (#2940) 2026-09-30 16:22:49 +02:00
maziggy df9802492c Count camera reconnects in a row, not for the life of the stream 2026-09-30 16:22:34 +02:00
maziggy b5441cd269 Hold an automatic slot unlink until the slot stays empty (issue #3186) 2026-09-30 16:22:22 +02:00
maziggy fbcc0b1e94 Resolve dispatch plate_id from the archive instead of assuming 1 (#2951) 2026-09-30 16:20:21 +02:00
maziggy 80840ad6c5 Suppliers as a managed list with per-spool assignments (#2996) 2026-09-30 16:19:54 +02:00
maziggy 86badf6993 Let a spool keep its own empty weight (issue #2908) (#3011) 2026-09-30 16:19:21 +02:00
maziggy 780d3e9b93 Paint SpoolBuddy spools with their extra colours and effect (issue #3033) 2026-09-30 16:19:09 +02:00
maziggy 0e0fad9568 Show Bambu's own HMS descriptions and the real alert level (issue #2728)
Document HMS levels, Bambu's descriptions and uncounted faults (issue #2728)
2026-09-30 16:18:53 +02:00
maziggy bfde96cce4 Give the AI's own halt a name in the archive (issue #2946) (#2954) 2026-09-30 16:18:22 +02:00
maziggy 3b4b7439c6 (Post work): parked AMS drying timers and translated drying failures (issue #2896) 2026-09-30 16:18:06 +02:00
maziggy dc12cda3b4 fix(ams): show a parked drying command as not started instead of an active cycle (#3096) 2026-09-30 16:17:51 +02:00
maziggy 8c31937a5f Correct the drying settings path and the print-drying comment (issue #2518)
The changelog named Settings -> Print Queue -> Auto-Drying; the toggle
    lives in the Queue Auto-Drying card under Settings -> Workflow. The
    scheduler comment and a test docstring called print_drying a permission
    overlay rather than a trigger, but since #1816 it starts mid-print
    cycles on its own regardless of queue state.
2026-09-30 16:17:31 +02:00
maziggy 9bb2a01f9e Refuse null for number and on/off settings, and read bad rows as the default (issue #2518)
A PUT /settings with null for a number setting stored the literal
    "None". From then on int()/float() raised inside the response builder,
    and every settings read returned 503 until the row was fixed by hand.

    Explicit null for any boolean or numeric setting is now refused with a
    422 that names the keys, and nothing in that request is saved. A numeric
    row that does not parse reads back as its default with a warning, so an
    install that already stored one recovers. The typed-key lists moved to
    module constants so the save check and the read path share them.
2026-09-30 16:17:14 +02:00
maziggy 86a0d0deb7 feat: Wait for sustained AMS humidity before an ambient auto-dry starts (issue #2518) 2026-09-30 16:16:51 +02:00
maziggy 5b59d35141 Post work PR #2956
-----

    Give each test worker its own scratch folder in the #3025 and #3029 tests (issue #3025)

    Both modules wrote into one shared folder under settings.base_dir and deleted
    it after every test. Under xdist, one worker's cleanup removed files another
    worker was still serving, so tests failed at random with a 404. The folder name
    now includes the process ID.
2026-09-30 16:16:32 +02:00
maziggy 534e337c35 Let the two stock alert toggles reach the database (issue #2945) (#2956) 2026-09-30 16:16:19 +02:00
maziggy 6d42632b67 Keep the consumed-counter reset out of Spoolman's own numbers (issue #2906) (#2939) 2026-09-30 16:16:05 +02:00
maziggy bc4084b2a9 Let other applications send messages through the notification channels
POST /notifications/app-message delivers an app's message to every channel
    with the new "Messages from connected apps" switch on (off by default),
    through quiet hours, the digest and the log. API keys need the new "Send
    notifications" permission, and their owner notifications:update; plain text,
    http(s) links, 20 messages a minute per key. The electricity-price door and
    this one now share one scoped-key check. /queue?batch=<id> opens and
    highlights one batch order.
2026-09-30 16:15:43 +02:00
maziggy 5f1e5f9aa9 feat: add optional printer model to stream overlay (#3099) 2026-09-30 16:15:19 +02:00
maziggy a6635f6783 Fix external-spool usage charged to an AMS spool (#3166)
Print commands carry the external spool as -1 in the flat ams_mapping
    (the firmware rejects 254/255 there) and the real target only in
    ams_mapping2. We captured only the flat list, so external-spool prints
    looked unmapped and the usage tracker's position-based fallback
    charged them to the first loaded AMS tray.

    - Resolve external spools from ams_mapping2 when capturing a
      project_file (dual-nozzle keeps 254/255, single-nozzle -> 254)
    - Tracker: an explicit -1 no longer falls back to a positional tray;
      a mapping naming no tray for any used slot defers to tray_now
    - Keep the #1822 H2S tray_now override working with resolved mappings
2026-09-30 16:14:15 +02:00
maziggy 50dd408383 fix(auth): let the connected-app consent page load inside Bambuddy's own frames
An app opened from the sidebar signs in inside Bambuddy's iframe, but every
    page sent frame-ancestors 'none', so the browser refused to show
    /connect/authorize there. That path now gets 'self', like the streaming
    overlay: every ancestor must be Bambuddy itself, so foreign pages still
    cannot frame it.
2026-09-30 16:13:37 +02:00
maziggy 9fb878cd3e feat(auth): connected apps - sign in to external applications with Bambuddy
Minimal OAuth 2.0 authorization-code flow with PKCE (S256): admins register
    an app with one exact callback URL (Settings > API Keys > Connected Apps);
    /connect/authorize asks for consent once and returns a single-use, 60 s code
    bound to app, callback and challenge; POST /api/v1/connect/token swaps it,
    with the client secret, for the user's identity and permissions. Codes and
    secrets stored hashed, exchanges rate-limited per client and IP, no redirect
    before the callback is validated, API keys cannot authorize, refused while
    auth is disabled. i18n for all 15 locales.

    -----

    fix(db): upgrading from 0.2.4.0 or older no longer crashes at startup

    The #2974 failure-reason conversion ran before the #1378 migration that adds
    print_log_entries.failure_reason, so older databases stopped with "no such
    column: failure_reason". It now skips a table without the column, only runs
    where a legacy label exists, and on SQLite rebuilds archive_fts first, since
    archives created before that index existed trip "database disk image is
    malformed" when updated.
2026-09-30 16:13:18 +02:00
maziggy e65852f7ac feat(queue): let a batch record the external order it fulfils
POST /queue/batches accepts external_source + external_ref; both are
    returned on every batch and filterable on GET /queue/batches. The pair is
    unique (index uq_print_batches_external), so a retried create answers 409
    instead of queueing the same order twice. Migration covers SQLite and
    PostgreSQL.
2026-09-30 16:12:54 +02:00
maziggy 029093ed4e fix(diagnostics): name the stalled step when a bundle's connection check times out (issue #3164)
The support bundle gives each printer's connection diagnostic 15 s and
    discarded the whole result on overrun, recording only "timed_out". A
    14-printer farm's bundle carried that marker for every printer and
    nothing else, so it could not say which check was slow.

    run_connection_diagnostic now keeps an optional progress dict current
    (finished checks + the step in flight). On timeout the snapshot records
    stalled_in, elapsed_s and the checks that completed.
2026-09-30 16:12:35 +02:00
maziggy 9bb597f37c fix(tests): assemble the upload batch instead of racing a sleep
The concurrent-dispatch tests went red on the Docker shard with
    "expected all 6 printers to be uploaded to concurrently, but the
    high-water mark was 4", on a scheduler that was dispatching all six
    correctly.

    peak == 6 is the claim that the sixth dispatch reaches its upload
    before the first one finishes, and the dispatches do not arrive
    together: each runs a preamble of database work first. So the
    assertion was a race between that spread and a fixed 0.15 s sleep.
    Measured here the spread is ~14 ms; on the runner it passed 150 ms.
    Padding the sleep would only move the threshold and slow every test
    that uses it.

    _UploadRecorder(assemble=True) now holds each call until every
    upload the pass launched has arrived, reading len(_inflight), which
    is filled synchronously at launch and is therefore the batch size.
    That states the property the peak assertions are about, directly and
    with no time in it, and it ends sooner than the sleep it replaces -
    the file runs 7.0s to 5.9s. _BATCH_DEADLINE_SECONDS bounds it so a
    dispatch that really has gone serial fails on its peak assertion
    instead of hanging to the pytest timeout.

    test_check_queue_returns_without_awaiting_the_uploads keeps the
    sleep, with the reason recorded next to it: it reads the rows while
    the uploads are open, and an assembled batch releases as soon as it
    is complete, which would let the dispatches flip those rows
    mid-assertion.

    The test engine also takes the application's own _set_sqlite_pragmas
    listener rather than a copy, so the file is opened the way the
    running system opens one - WAL, synchronous = NORMAL, a 15 s busy
    timeout. SQLite's defaults spend an fsync per commit and lock the
    whole file, which made a dispatch's preamble cost more than the
    upload it precedes. That is what turned the previous commit's move
    to a file-backed database into a visible failure.

    Verified in both directions rather than by a green run: with two
    printers' preambles delayed by a second, the old recorder reports
    exactly the "high-water mark was 4" the runner saw and the shared
    library test reports 3 == 4, while the assembling recorder passes
    the same scenario. Without the induced skew, ruff is clean, the full
    backend suite is green at 12224 passed, and ten consecutive runs of
    the file pinned to one core show no failures.
2026-09-30 16:11:33 +02:00
maziggy 1a80430c45 fix(tests): give each concurrent-dispatch session its own connection
The scheduler's concurrent-dispatch tests failed on CI and passed
    locally, on a commit that touched nothing but a text file. Six tests
    in tests/unit/test_scheduler_concurrent_dispatch.py went red with
    every queue item logged as "Status set to 'printing'" and four of
    them read back as "pending", alongside "cannot commit transaction -
    SQL statements in progress" and a PrintArchive that could not be
    refreshed.

    The fixtures built their farm on sqlite+aiosqlite:///:memory:, and
    SQLAlchemy backs an in-memory SQLite with a StaticPool: one DBAPI
    connection handed to every session, with nothing keeping them apart.
    That was harmless while check_queue awaited its uploads inline,
    because only one session was ever live at a time. Under the
    refillable upload pool the uploads run as concurrent background
    tasks with a session each, so their transactions interleave on that
    single connection - a sibling session's close() rolls back another's
    flushed-but-uncommitted UPDATE, and a commit() landing while another
    session still holds a cursor raises the commit error above. Whether
    the interleaving lands badly comes down to core count and
    interpreter version, which is why a 30-core box on 3.13 stayed green
    and a 4-vCPU runner on 3.11 did not.

    The four fixtures now put the database in the test's own tmp_path,
    which gets an AsyncAdaptedQueuePool and a connection per session -
    what the application itself runs with (_resolve_pool_kwargs in
    backend/app/core/database.py: pool_size 20, max_overflow 200). So
    the harness is being brought in line with production rather than
    having its assertions relaxed; nothing in the scheduler changes,
    and the concurrency under test was correct throughout.

    Checked against the failure mode rather than against a green run: an
    isolated repro of the same shape - six writer sessions and one
    reader session closing mid-transaction - yields all-pending on the
    in-memory engine and all-printing on a file. The new risk is real
    SQLite write contention on one file, so the file was run twelve
    times pinned to two cores with no failures and no lock errors.

    tests/unit/test_scheduler_busy_reasons_3018.py carries the same
    harness on an in-memory engine, but dispatches to a single printer,
    so it has no second session to race; left as is.
2026-09-30 16:11:10 +02:00
maziggy 5ac21d9202 feat(print-modal): show each printer slot's colour in the filament mapping (issue #3159)
The Print / Schedule dialog's filament mapping is where the colour a slice
    asked for is compared against the colour actually loaded, and only the
    left-hand side of that comparison had a swatch. The slot, and every slot in
    its dropdown, was text -- and the text cannot be trusted: a slot's colour name
    is resolved from the Color Catalog or, failing that, from hue, so a
    third-party beige is announced as "Orange". A "Color mismatch" warning then
    gives no way to tell a real mismatch from two names for the same hex without
    opening the printer card in another tab, which on a farm swapping twenty or
    thirty non-Bambu colours between machines is a check made many times a day.

    Each slot now carries its colour and its hex, and the slot whose colour is
    exactly the one the slice asked for is ticked. This works for a slot bound to
    an inventory spool and for one configured through Configure Slot or on the
    printer itself: the second kind has no inventory row behind it, and the
    printer's own tray colour is then what draws. A bound spool contributes what a
    tray record cannot -- SlotSpoolIdentity gains extra_colors and effect_type, so
    a two-tone or glittery spool draws as itself rather than as its base colour.

    The same treatment goes to the filament-override picker used for model-based
    assignment. It is the same choice on the other dispatch path, and leaving it
    text-only would have made one decision read two ways.

    Both controls stop being <select>s to do it, because an <option> renders text
    and nothing else. SlotPicker keeps what the select gave for free -- arrow,
    Home/End, Enter and Escape keys, listbox semantics, and the border colouring
    that encodes match, same-type-different-colour and not-loaded -- and is
    portaled with position:fixed so it is not clipped by the dialog's own scroll
    container, flipping above the row when there is no room below.
2026-09-30 16:10:24 +02:00
maziggy b60cee2c9f Bumped version 2026-09-30 16:08:49 +02:00
maziggy e9627e44a2 Start queued jobs in list order, whether pinned or "Any <model>" (#3200) 2026-09-30 15:55:05 +02:00
maziggy 8b5d8e3170 Add layers, job id, HMS faults and serial to the API-key printer status (#2919) 2026-09-30 11:45:31 +02:00
maziggy bf7fc67dc0 Choose what a spool label shows, preview it, and save it as PNG (#2981, #2980) 2026-09-30 11:08:05 +02:00
Kouki Ojima cb924e5c4a Match a Bambu roll to its own product line, not just its colour (issue #2907) (#2944) 2026-09-30 10:15:42 +02:00
maziggy 29e6d28205 Resolve LDAP groups on lldap and OpenLDAP (#3197)
lldap and OpenLDAP's memberof overlay omit memberOf from "*", so every
lldap login fell through to the default group. Request memberOf by name
when the schema defines it, and on non-AD directories also search the
directory root for groupOfNames/groupOfUniqueNames entries listing the
user, since groups often sit outside the user search base and the
overlay tracks only one group class.

Also: skip ldap3's anonymous schema read after StartTLS, which AD and
Samba AD reject, so StartTLS works there; reword a server's StartTLS
refusal with an LDAPS hint; stop the bundle sanitizer masking part of
an OID as an IP; skip the sync right after auto-provisioning so the
default-group warning logs once.
2026-09-30 09:46:54 +02:00
maziggy 2f7f17a891 Use the Spoolman spool's own initial_weight as its label weight (#3194)
Spoolman keeps a full spool's net weight on the spool (initial_weight)
and falls back to the filament's weight, but Bambuddy read only the
filament, so a 250 g spool of a 1000 g filament showed and synced as
1000 g. The list, weigh, AMS sync, SpoolBuddy scale, remain-% tracking,
fill bar and cost now share one lookup. Create writes initial_weight,
and a label-weight edit writes it instead of patching or duplicating the
filament. The spool form's cost per kg is converted at the spool's size
to and from Spoolman's per-spool price.
2026-09-30 09:12:45 +02:00
maziggy c145b3ebc1 Use the vendor's empty spool weight as tare in Spoolman mode (#3195)
Spoolman resolves a spool's tare from the spool, then the filament, then
the vendor's empty_spool_weight. Bambuddy skipped the vendor and fell
back to 250 g, so weighing a spool whose tare was set only on its vendor
gave the wrong remaining weight. The inventory weigh action, the
SpoolBuddy scale and the displayed core weight now share one lookup, and
"keep old weight" on a filament change stamps a vendor-inherited tare.
2026-09-30 08:43:21 +02:00
maziggy ad3b5f1982 Map the P-series model codes to the right printers
C11 is the P1P, C12 the P1S, C13 the X1E and N7 the P2S, as the virtual
printer and a real P1P 3MF already say. The frontend map had them shifted,
so discovery pre-filled a P1S as a P1P and an X1E as a P2S. The backend
map read C11/C12 as X1C/X1 and lacked N7, the firmware check sent C13 to
the P2S line, and the capability lists never matched BL-P001 because
their lookup strips the dash.

-----

Post work PR #3134
2026-09-29 16:43:04 +02:00