Commit Graph
27 Commits
Author SHA1 Message Date
maziggy c1f69ee0cc fix(slicer): wrong-printer slicing + sliced-archive filament list + per-instance MakerWorld compat
Five stacked slice-pipeline bugs that each made the modal's profile picker
  theatrical for 3MF inputs:

  (1) `_strip_3mf_embedded_settings` removed `model_settings.config` /
      `slice_info.config` / `cut_information.xml` along with
      `project_settings.config`. The CLI silently exited after
      "Initializing StaticPrintConfigs" — exit 0, no result.json — and
      Bambuddy masked the failure by re-running with embedded settings
      and the source's bound printer. Strip removed from the dispatch
      path entirely.

  (2) Standard-tier preset stubs lacked the `type` field, so the CLI
      rejected `--load-settings` with rc=-5 ("input preset file is
      invalid") and the same masking fallback fired. Added
      `_SLOT_TO_PROFILE_TYPE` so each stub carries the right
      machine/process/filament discriminator.

  (3) Sliced-archive cards listed every project-wide AMS slot (16+
      swatches for a 2-color print). `slice_and_persist_as_archive` now
      reads `filament_type` / `filament_color` from the sliced output's
      `slice_info.config` (which `ThreeMFParser` already gates on
      `used_g > 0`) instead of inheriting from the source archive.

  (4) SliceModal had no warning when the picked printer profile didn't
      match the source 3MF — the CLI rejects cross-printer slices
      (rc=-16) and fell back to embedded settings, producing wrong-printer
      g-code that errored at print dispatch. Plates response now exposes
      `source_printer_model`; the modal compares against the picked
      profile name and disables Slice + shows an inline warning on
      mismatch.

  (5) MakerWorld URL-paste resolver listed plate instances without
      showing which printer each was sliced for (`/instances/hits`
      omits compatibility info that lives on `design.instances[]
      .extention.modelInfo`). The resolve route now joins both payloads
      by instance ID and forwards `compatibility` + `otherCompatibility`
      onto each hit; the MakerWorld page renders "Sliced for {primary}"
      + "Also marked compatible: ..." per row.

  Tests: 6 unit tests for `extract_source_printer_model_from_3mf`, 1 for
  filtered filament metadata via ThreeMFParser, 2 for makerworld resolve
  compat-merge (happy path + missing modelInfo), 3 frontend SliceModal
  tests for the printer-mismatch warning + Slice-disabled gate. New i18n
  keys `slice.printerMismatch`, `makerworld.slicedFor`,
  `makerworld.alsoCompatible` across all 8 locales.
2026-04-28 13:58:56 +02:00
maziggy 988c00554e feat(slicer): multi-color slicing + per-plate filament discovery
The slice modal previously rendered exactly one filament dropdown and
  silently truncated multi-color 3MFs to a single profile, producing wrong
  colours on every multi-filament print. End-to-end fix across sidecar,
  backend, and frontend.

  Sidecar (orca-slicer-api / bambuddy/profile-resolver, separate commit):
    - /slice accepts up to 16 repeated filamentProfile parts; slicing
      service materializes each and joins paths with `;` for
      --load-filaments.
    - /profiles/bundled emits filament_type and filament_colour per leaf
      so the bundled tier carries metadata into the modal.

  Bambuddy backend:
    - SliceRequest gains filament_presets: list[PresetRef]. Validator
      accepts three shapes (multi-color array, source-aware singular,
      legacy bare-int id) and lands them all on a populated array before
      the route handler runs — fully backwards-compatible.
    - SlicerApiService.slice_with_profiles takes filament_profile_jsons:
      list[str] and sends one filamentProfile multipart part per profile
      (in submission order) so the sidecar receives N profiles cleanly.
    - New service slice_preview runs the sidecar's slice_without_profiles
      against an unsliced project file's embedded settings, parses the
      result's slice_info.config, and returns the canonical per-plate
      filament list. Cached by (kind, source_id, plate_id, content_hash)
      with LRU eviction at 256 entries, per-key asyncio.Lock prevents
      thundering-herd; transient sidecar failures are NOT cached so they
      retry naturally; parse failures ARE cached (deterministic property
      of the input, no point re-running).
    - /filament-requirements endpoint chain: slice_info.config (existing,
      sliced files) → preview-slice (new, unsliced project files) →
      project_settings.config + painted-face heuristic with 5% noise
      threshold (sidecar-down fallback).
    - threemf_tools gains extract_project_filaments_from_3mf and
      extract_plate_extruder_set_from_3mf — the latter unions object
      top-level extruder, per-part overrides, and painted-face quadtree
      leaves (1-E nibbles in paint_color attrs of <triangle> elements
      inside per-object .model files).
    - Cloud preset listing no longer fetches per-preset detail (Bambu's
      rate limit at ~10/sec returns 429 on every request for users with
      50+ presets). Unified-listing dedup pass instead backfills metadata
      cross-tier so a cloud entry that wins dedup over a same-named local
      entry inherits the local's filament_type / filament_colour.

  Frontend:
    - SliceModal multi-step: plate-picker first when the source is a
      multi-plate 3MF, then preset dropdowns. One filament dropdown per
      AMS slot the plate actually uses, each pre-picked by metadata
      match against user's local + standard presets via existing
      colorsAreSimilar / normalizeColorForCompare utils.
    - SliceModal-only tier priority is now local → cloud → standard
      (was cloud → local → standard). Other consumers of /slicer/presets
      keep the existing cloud-first order.
    - Submits filament_presets array; backfills the legacy singular
      filament_preset from the array's first entry for stale-tab
      compatibility.
    - i18n keys added across all 8 locales: slice.filamentSlot,
      slice.tier.{local,cloud,standard}, slice.cloud.{notAuthenticated,
      expired,unreachable}, slice.noPresetsForSlot,
      slice.allPresetsRequired (en + de fully translated; six others
      seeded with English copies pending native translation, matching
      the project's existing flow).

  Permissions: no new endpoint paths added. Preview-slice runs inside
  /filament-requirements (LIBRARY_READ / ARCHIVES_READ) and multi-filament
  dispatch runs inside POST /slice (LIBRARY_UPLOAD). No auth surface
  widened.

  Tests: 6 SliceRequest schema tests for multi-filament + legacy-new
  precedence; 9 unit tests for slice_preview cache behaviour (LRU
  eviction with lock cleanup, content-hash invalidation, concurrent
  thundering-herd guard, no-cache-poison on transient sidecar failure);
  15 unit tests for the two new threemf_tools helpers (5 + 10 cases
  including the 60/40 painted-threshold regression pin); a multi-filament
  wire-format test pinning the multipart part count + order; 22 frontend
  SliceModal tests covering plate picker, multi-color render,
  metadata-aware pre-pick, manual override, and the new tier order.
2026-04-28 12:20:59 +02:00
maziggy 02eb5f57dc fix(#422): start g-code anchor + slicer placeholder substitution
Auto-Print G-code Injection had two reviewer-reported bugs from the initial
  ship:

  1. Start snippets were prepended to the entire plate_X.gcode, landing
     before the printer's own bed-heat / homing / nozzle-prime sequence —
     so a Swapmod start snippet that assumed nozzle-at-temp ran on a cold
     printer (pleite). Anchor injection at "; MACHINE_START_GCODE_END" so
     snippets land where a slicer-side custom-start-gcode would. Files
     without the marker keep prepend behaviour as a fallback with a
     warning log.

  2. Placeholders like "G1 Z{max_layer_z} F600" were written verbatim;
     firmware parsed them as Z1 and crashed the head into the print on
     tall models — real safety bug (DevScarabyte). Added a header parser
     for the 3MF "; HEADER_BLOCK_START..END" block (lowercased keys,
     [units] suffix stripped, spaces -> underscores) and a Prusa-style
     {name} substitution pass over both start and end snippets before
     injection. Supported placeholders: {max_layer_z} / {max_print_height},
     {total_layer_number} / {total_layers}, {total_filament_weight},
     {total_filament_length}, plus any other normalised header key.
     Unknown placeholders are left verbatim with a warning — a typo never
     silently expands to an empty string.

  16 new regression tests across 4 new classes in test_gcode_injection.py
  (anchored injection + missing-marker fallback, placeholder substitution
  including alias resolution + unknown-pass-through, direct unit tests
  for each new helper). All 2195 backend unit tests pass.

  Wiki print-queue page updated with the supported placeholder list and a
  {max_layer_z} safety callout for park moves.
2026-04-27 16:02:40 +02:00
maziggy 6fb814c5ea feat(printer): add X2D support — camera, dual-nozzle, K-profile, maintenance (#988)
The Bambu Lab X2D (launched April 2026, dual-nozzle, enclosed, hardened
  steel rod gantry, AMS 2 Pro compatible) identifies itself as internal
  model code N6 via SSDP/MQTT, and real serials begin with 20P9. None of
  these identifiers existed in Bambuddy's registries, so the camera
  service fell back to the chamber-image protocol on port 6000 (X2D
  doesn't speak it), firmware-check logged "Unknown printer model: N6",
  and the dual-nozzle K-profile paths — gated on the H2D serial prefix
  "094" — would have treated X2D as single-nozzle.

  Backend:
  - Register N6 → X2D across every registry (PRINTER_MODEL_ID_MAP,
    PRINTER_MODEL_MAP, STEEL_ROD_MODELS, ETHERNET_MODELS,
    CHAMBER_TEMP_SUPPORTED_MODELS, firmware-check API keys + wiki path,
    virtual-printer SSDP/product/serial tables, DB vp_model_fixes).
  - supports_rtsp(): match the X2 display-name prefix and the N6 internal
    code; camera now routes to RTSP on port 322.
  - Dual-nozzle serial prefix check in bambu_mqtt.delete_kprofile and
    kprofiles.set_kprofile broadened to ("094", "20P9") — X2D now takes
    the H2D-style cali_idx in-place edit path.
  - is_h2d model gate in bambu_mqtt.start_print extended with "X2D" so
    timelapse / bed_leveling / flow_cali / vibration_cali / layer_inspect
    are sent as integers and external-spool ams_id 254/255 routing is
    preserved (H2D-style deputy-nozzle addressing).

  X2D uses hardened steel rods like P2S — it is intentionally placed in
  STEEL_ROD_MODELS, not CARBON_ROD_MODELS. A regression-guard test pins
  the classification.

  Frontend:
  - mapModelCode in PrintersPage and SpoolBuddyAmsPage handle N6 and X2D.
  - Enclosure-door badge and airduct-mode whitelists include X2D.
  - MaintenancePage.getMaintenanceWikiUrl routes X2D to P2S wiki URLs for
    steel-rod lubrication, belt tension, cold-pull, and PTFE tube
    (exported to enable direct unit testing).

  Tests:
  - test_printer_models.py: TestX2DModel (10 assertions).
  - test_bambu_mqtt.py: X2D in start_print ams_mapping and is_h2d gate;
    TestDeleteKProfileDualNozzleDetection across H2D, X2D, P2S, X1C.
  - MaintenancePageWikiUrls.test.tsx: 15 assertions covering X2D, P2S
    regression, X1C/H2D/A1Mini regression, and model-name normalisation.

  Docs:
  - README: added X2 series to the supported printers table.
  - CHANGELOG: new entry under 0.2.3b4 Fixed.

  Credit to @krautech for the report and debug bundle, and to @legend813
  for PR #989 which seeded most of the registry changes — rod-type
  classification was corrected (steel, not carbon) and the dual-nozzle /
  K-profile / is_h2d gaps were added on top.
2026-04-16 10:40:32 +02:00
maziggy f006472f79 Add auto-print G-code injection for queue items (#422)
Per-model start/end G-code snippets configurable in Settings (Workflow
  tab). Queue items get "Inject G-code" toggle — scheduler injects
  snippets into a temp 3MF copy before FTP upload. Supports Farmloop,
  SwapMod, AutoClear, Printflow 3D and similar bed-clearing systems.
  Original files are never modified.
2026-04-05 11:14:26 +02:00
maziggy fd9ece00d4 Post work PR #851 2026-03-31 09:11:45 +02:00
Michael Behringer 50c976d7ec Fix/wrong filament mapping (#851)
Fix/wrong filament mapping (#851)
2026-03-31 09:07:51 +02:00
Keybored 6e648804fc [Feature] Spoolbuddy Fixes and Improvements (#787)
[Feature] Spoolbuddy Fixes and Improvements (#787)
2026-03-24 11:56:33 +01:00
maziggy 26a3b2172d Fix ethernet badge shown on WiFi-only printers (#585)
home_flag bit 18 is set on all printers regardless of hardware,
    causing the ethernet badge to appear on WiFi-only models (A1, P1P,
    etc.). The previous fix removed the feature entirely, so ethernet
    printers lost their badge too.

    Now only trusts bit 18 on models with an ethernet port (X1C, X1E,
    P1S, P2S, H2D, H2D Pro, H2C, H2S). WiFi-only models always show
    the WiFi signal badge.
2026-03-11 12:56:23 +01:00
maziggy d55585a73f Fix P2S showing carbon rod maintenance tasks (#640)
P2S uses hardened steel rods, not carbon fiber. Move P2S from
  carbon rod classification to new steel_rod category with its own
  "Lubricate Steel Rods" / "Clean Steel Rods" maintenance tasks.
2026-03-07 09:45:14 +01:00
maziggy 1cf40a5c35 Redesign SpoolBuddy dashboard: inline spool cards, full-screen AMS assign modal, filament ID normalization
- Replace TagDetectedModal with inline SpoolInfoCard/UnknownTagCard
    in dashboard right panel (known spools show assign/sync/close,
    unknown tags show add-to-inventory/link/close)
  - Rewrite AssignToAmsModal as full-screen overlay reusing AmsUnitCard,
    with AMS-HT and external slot support, single assignSpool API call
  - Remove printer selector from assign modal (uses top bar selection)
  - Extract filament_id <-> setting_id conversion to shared utility
    (backend/app/utils/filament_ids.py), used by inventory + cloud routes
  - Normalize slicer_filament in assign_spool to derive proper
    tray_info_idx and setting_id for MQTT (was sending setting_id="")
  - Rename SpoolBuddy top bar status label "Online" -> "Backend"
  - Remove weightStable guard from sync weight button
2026-03-01 11:56:52 +01:00
maziggy ce97a47627 Add H2C dual nozzle variant O1C2 model support (#489)
The H2C dual nozzle variant reports model code O1C2 via MQTT, but only
  O1C was recognized. This caused the camera to use the wrong protocol
  (chamber image on port 6000 instead of RTSP on port 322), producing a
  reconnect loop. Added O1C2 to all model ID maps across 8 files.
2026-02-28 11:53:38 +01:00
maziggy 29e9593f6c AMS mapping fixes (7 bugs):
- backend/app/services/print_scheduler.py — hard nozzle filter + external spool extruder
- backend/app/api/routes/printers.py — ams_extruder_map race condition
- backend/app/utils/threemf_tools.py — group_id priority + dual-nozzle detection
- frontend/src/hooks/useFilamentMapping.ts — external spool extruder
- frontend/src/pages/PrintersPage.tsx — AMS-HT globalTrayId
- frontend/src/components/PrintModal/FilamentMapping.tsx — dropdown nozzle filter

Usage tracking fix:
- backend/app/services/bambu_mqtt.py — capture last valid progress/layer
- backend/app/services/usage_tracker.py — use captured values on cancel

UI fix:
- frontend/src/components/spool-form/ColorSection.tsx — color tooltip z-index

Tests:
- backend/tests/unit/test_scheduler_ams_mapping.py — 63 tests
- frontend/src/__tests__/hooks/useFilamentMapping.test.ts — 43 tests

Static assets:
- static/ — rebuilt frontend bundles
2026-02-18 09:17:50 +01:00
maziggy cf7c459099 fix(inventory): fix usage tracking for remapped AMS slots and slicer prints
Three fixes for inventory spool usage not updating after prints:

1. Store ams_mapping from print command (reprint, library print, queue)
   so the usage tracker maps 3MF slots to the actual physical trays
   the user selected, not the default slicer mapping.

2. Track last_loaded_tray on printer state — the last valid tray_now
   (0-253) seen during printing. On H2D printers, tray_now is always
   255 in AMS data; the real tray resolves via snow field ~44s after
   print start but reverts to "unloaded" at completion. The fallback
   chain is: tray_now_at_start > current tray_now > last_loaded_tray.

3. Use color similarity (Euclidean RGB distance, threshold 50) instead
   of exact hex match for auto-unlink fingerprint checks. RFID sensors
   report slightly different shades across reads (e.g. distance ~43.6
   for the same spool), causing false assignment unlinks after prints.
2026-02-16 15:46:32 +01:00
AneoPsy aa684b4b36 feat(queue): add filament weight display
- Show total filament weight on Queue page
- Display weight per queue item
- Add translations for queue weight labels
2026-02-14 14:32:49 -10:00
maziggy cffe7e62bc Fix maintenance tasks showing wrong rod type for printer models (#351)
H2/A1 series have linear rails, not carbon rods. X1/P1/P2S have carbon
rods, not linear rails. Split rod-specific maintenance into model-aware
pairs: "Lubricate/Clean Carbon Rods" for X1/P1/P2S and "Lubricate/Clean
Linear Rails" for A1/H2. Added rod type classification to printer_models
and startup cleanup for stale/duplicate system maintenance types.
2026-02-13 12:23:34 +01:00
maziggy f9b47282a1 Nozzle-aware AMS mapping for dual-nozzle printers, BL spool detection fix, AMS startup fix, SQLite WAL (#318)
Dual-nozzle H2D/H2D Pro: filament matching now respects nozzle assignments
from the 3MF file. Each AMS unit feeds a specific nozzle (L/R), and the
scheduler/frontend constrain matching to only trays on the correct nozzle.
Falls back to unfiltered matching when no trays exist on the target nozzle.
L/R badges shown in the filament mapping UI. Translated in en/de/ja/it.

Fix AMS slot config overwritten on startup: on_ams_change unconditionally
unlinked BL spool assignments on every MQTT pushall, then re-assigned them
sending ams_filament_setting without setting_id — clearing the printer's
filament preset. Now compares spool RFID identifiers before unlinking.

Fix BL spool detection false positives: removed tray_info_idx from detection
logic in both backend is_bambu_lab_spool() and frontend isBambuLabSpool().
Third-party spools using Bambu generic presets had GF-prefixed tray_info_idx
values, causing misidentification. Now uses only tray_uuid and tag_uid.

SQLite WAL mode with 5s busy timeout reduces "database is locked" errors.
2026-02-13 11:48:32 +01:00
maziggy ae63ec8a91 Fix H2C firmware update downloading wrong (H2D) firmware (#311)
H2C was mapped to the "h2d" firmware API key, causing it to check
the H2D firmware page and offer H2D firmware for download. H2C has
its own separate firmware track (01.01.x.x vs H2D's 01.02.x.x).

Changed H2C to use its own "h2c" API key and added the reverse
mapping. Also added missing H2C (O1C) and H2S (O1S) entries to
the printer model ID and 3MF model name maps.
2026-02-10 08:51:44 +01:00
maziggy 4b46e443dc Fix critical FTP upload failure and revert dangerous exception narrowing
The CodeQL cleanup in "Housekeeping" (2b11efd) bulk-narrowed except
clauses across 50+ files, breaking FTP uploads on ALL printer models.
ftplib.error_perm (550 errors) is not a subclass of ftplib.error_reply,
so diagnose_storage() CWD failures escaped the handler and prevented
STOR from ever executing — causing 100% upload failure and HTTP 500s
on /api/v1/archives/{id}/reprint and /api/v1/library/files/{id}/print.

FTP fixes:
- Remove diagnose_storage() from upload hot path
- Change all except (OSError, ftplib.error_reply) to
  except (OSError, ftplib.Error) across bambu_ftp.py

Exception handling reverts (9 files):
- Revert narrowed except clauses back to except Exception in route
  handlers and service code where broad catches are intentional
  defensive programming (archive parsing, HTTP clients, 3MF/ZIP
  processing, Home Assistant, firmware checks)
- Keep narrow exceptions only where safe (single-op blocks like
  int(), file.unlink(), socket.close())
- Remove unused XMLParseError imports from archive.py, threemf_tools.py

Closes #287
2026-02-07 09:20:06 +01:00
maziggy 5b0a985da2 Add explanatory comments to 265 empty except blocks
CodeQL flags except blocks where `pass` has no comment explaining
why the exception is silently ignored (py/empty-except rule).

Added context-specific comments to all 265 instances across 31 files:
- database.py (~112): ALTER TABLE migrations — "Already applied"
- archive/library/3MF parsing (~64): "Skip unparseable metadata"
- virtual_printer network cleanup (~32): "Best-effort socket cleanup"
- discovery/SSDP (~13): "SO_REUSEPORT not available" / socket cleanup
- bambu_ftp/mqtt (~13): FTP cleanup, JSON decode, signal parsing
- remaining routes/services (~31): context-specific comments
2026-02-06 11:58:38 +01:00
maziggy 53bd4fadb3 Fix safe security findings: hashlib, log injection, broad excepts
- Add usedforsecurity=False to MD5 (AMS fingerprint) and SHA1 (git blob
  hash) calls to silence Bandit B303 / CodeQL weak-crypto findings
- Convert ~996 f-string logging calls to parameterized %s-style across
  55 files to prevent log injection (Bandit G201 / CodeQL log-injection)
- Narrow ~199 broad except Exception blocks to specific types:
  OperationalError for DB migrations, OSError for network/file cleanup,
  (OSError, ftplib.error_reply) for FTP, and targeted tuples for
  ZIP/XML/JSON parsing — 36 intentionally left broad (mixed async,
  re-raise patterns)
2026-02-06 11:37:59 +01:00
maziggy dc82b5ff2a Refactor Spoolman per-filament tracking (PR #277 follow-up)
Extract Spoolman tracking from main.py into dedicated service module,
DRY up repeated helpers, add i18n for new settings UI, and add tests.

- Move ~460 lines from main.py to services/spoolman_tracking.py
- Extract _resolve_spool_tag, _resolve_global_tray_id, build_ams_tray_lookup helpers
- Replace fragile tuple return in get_spoolman_settings() with dict
- Wire 4 new UI strings through i18n (en/de/ja)
- Add 42 backend unit tests (spoolman tracking helpers + 3MF parsing)
- Add 6 frontend tests for weight sync and partial usage toggles
- Update CHANGELOG, wiki, and website docs

Closes PR #277
2026-02-06 10:03:27 +01:00
bambuman ce4683ad3f Use defusedxml in test files for Bandit compliance 2026-02-05 19:55:18 +02:00
bambuman 5703cea3f2 fix linting isssues 2026-02-05 18:37:55 +02:00
bambuman 6e82cc611e Add per-filament Spoolman usage tracking with G-code parsing
Implement accurate per-filament usage tracking for Spoolman integration,
similar to OpenSpoolman v0.3.0. This replaces the previous single-spool
reporting with multi-material aware tracking.

Features:
- Parse G-code from 3MF files at print start to build per-layer,
  per-filament cumulative extrusion maps
- Store tracking data in new `active_print_spoolman` database table
  (survives server restarts for long prints)
- Report accurate partial usage when prints fail/cancel based on
  actual layer progress and G-code data
- Add "Disable AMS Weight Sync" setting to prevent AMS percentage-based
  weight estimates from overwriting Spoolman's granular tracking
- Add "Report Partial Usage for Failed Prints" toggle (only shown when
  weight sync is disabled)
- Use Spoolman's filament density instead of defaults for mm-to-grams
  conversion
- Prefer tray_uuid over tag_uid for spool identification
2026-02-05 17:16:02 +02:00
maziggy e87fe7ad87 Add H2D Pro printer model support
- Add H2D Pro option to printer model dropdowns (add/edit modals)
- Support both O1E and O2D internal codes for H2D Pro compatibility
- Add O2D to RTSP-capable and chamber temp supported models
- Add H2DPRO variant to firmware check mapping
- Add H2D Pro and X1E to bug report issue template

Closes #192
2026-01-30 15:38:52 +01:00
maziggy 2eb21e8b42 Issue #162 2026-01-28 09:14:22 +01:00