Commit Graph
35 Commits
Author SHA1 Message Date
maziggy fdcc063d9f fix(k-profile): match by filament_id, surface active profile in Configure Slot (#1688 + #1689)
Two related bugs in K-profile matching, same root cause.

  #1688 — spool form's PA-profile suggester (PAProfileSection via
  isMatchingCalibration in spool-form/utils.ts) matched K-profiles by
  parsing the profile NAME for material/brand/variant. Spools already
  store slicer_filament (the slicer preset id) and K-profiles already
  carry filament_id, but both were ignored — so a user's custom
  K-profile whose name doesn't agree with the slicer preset got silently
  dropped from suggestions even when the underlying filament_id was
  identical.

  #1689 — ConfigureAmsSlotModal's matchingKProfiles ran the same
  name-only logic on the slot's selected preset. A spool assigned under
  "Generic PLA" with a custom K-profile actively bound on the printer
  landed in the modal as "K profile not assigned, default 0.020 will
  be used", while the printer-card hover-card correctly showed the
  active profile. Two paths, only one was filtering by name.

  Shared root: spool preset ids and K-profile filament_ids look
  different but are equivalent after normalising. Spools store
  slicer_filament as the cloud setting_id form ("GFSG98_09" — _09 is
  the variant suffix, the S infix marks setting_id form); K-profiles
  store filament_id as the bare form ("GFG98"). Plain === doesn't
  work; both need normalising. This conversion already existed in the
  other direction at buildFilamentOptions (filament_id → "GFS" +
  filament_id.slice(2)), so the inverse toFilamentId helper is just
  the matching reverse, not new ground.

  Fix — one shared helper, two surfaces:

  - spool-form/utils.ts: new exports toFilamentId(id) (drops "_NN"
    variant suffix and strips the "S" in "GFS", so GFSG98_09 → GFG98)
    and isGenericFilamentId(id) (flags Bambu's generic GFx99 ids
    which are shared across many filaments and must NOT id-match —
    the name fallback handles those correctly).

  - isMatchingCalibration: gains slicer_filament?: string in formData,
    tries id-match (with generic exclusion) before the existing name
    parse. PAProfileSection already passes the full formData so no
    caller edit needed. Strictly additive precedence.

  - ConfigureAmsSlotModal.selectedPresetInfo: resolves a filamentId
    field (toFilamentId(cp.setting_id) for cloud presets,
    toFilamentId(builtinFilamentId) for builtin; empty for local /
    orca paths which fall through to name match).

  - ConfigureAmsSlotModal.matchingKProfiles: id-match check at the top
    of the per-profile predicate (preferred when both sides agree
    after normalisation), then the existing name-parse logic, then
    ALWAYS unshifts the slot's currently-active K-profile by
    slot_id === slotInfo.caliIdx — gated on activeIdx > 0 (so caliIdx
    0/null doesn't leak unrelated profiles in), extruder-matched when
    slotInfo.extruderId is known. This is Spionkiller01's #1689 patch
    verbatim with the activeIdx > 0 guard added.

  SpoolBuddy: both kiosk K-profile surfaces reuse the shared
  components. SpoolBuddyWriteTagPage renders PAProfileSection;
  SpoolBuddyAmsPage renders ConfigureAmsSlotModal. Verified — fixes
  propagate automatically, no kiosk-specific edits.

  What this does NOT change: spools without slicer_filament, K-profiles
  without filament_id, and generic GFx99 ids all fall through to the
  existing name-based matching path. Strictly additive precedence; no
  input shape that matched under the old logic fails to match under the
  new. The #1053 cloud-preset PFUS* path is preserved because the
  toFilamentId regex /^GFS/ doesn't match a "PFU" prefix.
2026-06-09 09:53:28 +02:00
maziggy 632334953c fix(inventory): support transparent / clear filament end-to-end (#1545)
Reporter wanted to select a transparent filament colour in the spool
  editor; CMW-ISS confirmed on v0.2.5b1 that AMS-detected transparent
  spools were silently labelled "Black" in the filament-mapping dropdown
  because the colour name resolver dropped the alpha byte and the underlying
  RGB 000000 HSL-bucketed to "Black". Spoolman already supported 8-digit
  hex; the built-in inventory didn't.

  Eight collapsing sites fixed together so transparent reaches the user
  intact:

  - frontend/src/utils/colors.ts: hexToColorName / getColorName /
    resolveSpoolColorName / isLightColor short-circuit to "Clear" for
    alpha=00 before HSL bucketing or catalog lookup
  - frontend/src/utils/amsHelpers.ts::normalizeColor preserves the alpha
    byte when alpha < FF (normalizeColorForCompare unchanged so type/colour
    matching is unaffected)
  - frontend/src/components/spool-form/constants.ts: new
    { name: 'Clear', hex: '00000000' } preset in QUICK_COLORS
  - frontend/src/components/spool-form/ColorSection.tsx: hex draft accepts
    0-8 chars, commits at 6 (+FF) or 8 verbatim; blur pads 7-char to 8;
    selectColor passes 6-char as +FF / 8-char verbatim; isSelected matches
    on full rgba; swatch buttons paint a checkerboard for alpha=00
  - backend/app/api/routes/printers.py::get_available_filaments preserves
    the full rgba on both AMS and vt_tray branches (6-char dedup key
    unchanged)
  - backend/app/services/spoolman.py::parse_ams_tray drops the silent
    00000000 -> F5E6D3FF cream rewrite — the swatch renderer paints a
    checkerboard underlay for alpha < FF already (added in #1154), so the
    rewrite was hidden technical debt that made every AMS-detected
    transparent spool land in inventory as cream
  - backend/app/services/spool_tag_matcher.py::create_spool_from_tray
    short-circuits the colour-catalog lookup for alpha=00 and stores
    color_name="Clear" directly — otherwise an RFID-tagged transparent
    Bambu spool would resolve against the #000000 catalog row (or "Black"
    via the HSL fallback) before the frontend's resolver ever saw it
  - Two shared helpers in utils/colors.ts — getSwatchStyle(rgba) (style
    object: checkerboard for alpha=00) and spoolColorString(rgba)
    (8-char hex string for SVG fill) — applied to every simple-swatch
    site that would otherwise have rendered Clear spools as solid black:
    LabelTemplatePickerModal, SpoolBuddyInventoryPage (SpoolCircle + dot),
    SpoolBuddyAmsPage (both branches), SpoolBuddyWriteTagPage (4 sites),
    ForecastPanel, AssignToAmsModal, AssignSpoolModal (both branches),
    InventorySpoolInfoCard, TagDetectedModal, SpoolInfoCard, LinkSpoolModal,
    and the FilamentSwatch tooltip title fallback

  Intentionally NOT changed: native <input type="color"> keeps 6-char hex
  (can't pick alpha; onChange still emits +FF, correct); Spoolman's
  _find_or_create_filament strips alpha (Spoolman catalog is 6-char only);
  print_scheduler colour matching strips alpha (auto-mapping treats Clear
  as Black for slot compatibility); label_renderer prints "#RRGGBB" on the
  physical label (printers can't print transparency, swatch fill via
  _color_from_hex still honours alpha).
2026-05-29 10:15:53 +02:00
maziggy 8d52c713ef fix(inventory): hex colour field accepts character-by-character typing (#1407)
Reporter typed into the Add Spool modal's hex colour input and only
  the first character stuck - everything after that defaulted to "0"
  with no way to override except by pasting the full hex.

  Pre-fix, the #1055 fix aggressively normalized the input to a valid
  8-char rgba on every keystroke. After typing the first char the
  controlled input value snapped to e.g. "A00000", the browser placed
  the cursor at the end, and the user's next keystroke landed at
  position 7. The #1055 fix's 7-char branch then truncated that byte
  away, leaving the form state unchanged - so the user appeared to
  type nothing.

  Fix splits the typing-state from the backend-state:

  - The hex input gets its own `hexDraft` useState holding 0-6 chars
    freely. Typing one char at a time works naturally because the
    controlled value matches what the user typed.
  - `updateField('rgba', ...)` fires only when the draft reaches a
    complete 6-char RGB (commits as `<6chars>FF`). Below that, the
    form state stays untouched - no mid-keystroke snap.
  - On blur, a partial 1-5 char draft is right-padded with `0` and
    committed. Keeps the #1055 invariant: anything reaching the
    backend is exactly 8 hex chars matching /^[0-9A-F]{8}$/.
  - A `useEffect` resyncs the draft when an external action (color
    picker, swatch click, edit-mode load) changes the canonical hex.
  - Paste of 7-/8-char strings truncates to the leading RGB. Bambu
    filaments are opaque; the UI never exposed an alpha affordance,
    so dropping the (undocumented) paste-with-alpha case is fine.
2026-05-18 09:57:07 +02:00
maziggy f7c94cbc7b fix(inventory): add CF/GF to subtype dropdown on the Add/Edit Spool form (#1345)
Adding a third-party PETG-CF spool via the Material=PETG + Subtype=CF
  flow (same shape as the existing PETG HF) hit a missing option: KNOWN_VARIANTS
  in spool-form/constants.ts didn't list CF or GF. Users had to type it
  freehand into the "create new" tail of the dropdown.

  Added both: CF (matches PETG-CF / PLA-CF / ASA-CF / PA-CF) and GF (the
  natural pair for ABS-GF / PA6-GF). parsePresetName is unaffected — its
  materials list is iterated longest-first, so cloud presets like
  "Bambu PETG-CF Black" still resolve to material=PETG-CF with empty
  afterMaterial.
2026-05-15 09:47:21 +02:00
maziggy dd3e3f8039 fix(inventory): make AMS slot config land cleanly for spools with no k-profile
The assign flow was sending slicer-invalid values for tray_info_idx and an
  empty setting_id, which the slicer rejected — slot detail modal showed
  empty fields. With a stored k-profile the realignment path masked the
  issue; without one, garbage hit MQTT.

  Backend (apply_spool_to_slot_via_mqtt):
  - Discard tray_info_idx values that aren't real preset IDs: literal
    material names ("PLA", "PETG-CF") AND PFUS-prefix cloud setting_ids
    (valid as setting_id but rejected as tray_info_idx). Same check applied
    to current_tray_info_idx so stale slot values don't get reused as
    garbage.
  - Local-preset path now reads the printer-recognized filament_id from
    the preset's setting JSON (e.g. P4d64437) instead of falling through
    to a generic material ID.
  - Derive setting_id from filament_id_to_setting_id when empty so
    ams_filament_setting always carries a matched pair.
  - No stored k-profile: always send cali_idx=-1 (Default K), regardless
    of the live cali_idx on the slot. The live value belongs to whatever
    filament was there before, so reusing it would apply the wrong K to
    the new spool.

  Frontend (spool-form/utils.ts):
  - Local preset options use String(preset.id) as the unique code instead
    of preset.filament_type — every PLA local preset was collapsing onto
    the same "PLA" code, so picking any of them saved slicer_filament=
    "PLA" and lost the specific preset identity.

  Spoolman counterpart in spoolman_inventory.py mirrors the cali_idx=-1
  reset.
2026-05-14 19:20:58 +02:00
maziggy b51ef33472 fix(inventory): apply catalog color's gradient + effect, not just hex (#1340)
Picking a color preset from the catalog only copied color_name and
  rgba onto the spool — extra_colors (gradient stops) and effect_type
  (sparkle / wood / etc.) were silently dropped at three layers above
  the API: the SpoolFormModal state shape, the CatalogDisplayColor
  mapping in ColorSection, and the selectColor handler itself. All
  three widened to carry both fields through.

  Picking a catalog swatch now writes both fields from the entry, so
  solid presets cleanly replace previous gradients. Recent-colors and
  the hardcoded-fallback palette stay as plain hex pickers — they
  don't touch extras/effect since they aren't full presets.

  Also fixed the en-US `colour` → `color` drift in 8 locale files
  that the reporter flagged.
2026-05-14 14:52:18 +02:00
maziggy 30fe88a334 fix(inventory): show all per-printer/per-nozzle variants in spool form's Slicer Preset dropdown (issue #1248)
Two defects in buildFilamentOptions, surfaced together:

  1. The function was precedence-based — cloud presets short-circuited
     the local-presets branch, silently hiding any imported Local Profile
     while the user was logged into Bambu Cloud. The wiki documents the
     dropdown as "merged and deduplicated" across cloud + local + built-in.

  2. Cloud default presets and local presets were being collapsed by base
     name (everything after "@" stripped), so all P1S/X1C/A1 variants of
     "Bambu PLA Basic" rendered as a single row. The spool form is
     printer-agnostic by design, so the right semantic is to show every
     variant individually — the union across all printers — not collapse
     them. AMS Slot is per-printer (it filters), the spool form is
     union-of-all (it doesn't).

  Rewrote the merge to push each cloud setting_id and each LocalPreset row
  as its own FilamentOption with the full @printer suffix preserved in
  displayName. Built-in dedup against cloud setting_id is kept (mirrors
  ConfigureAmsSlotModal.tsx). Wired api.getBuiltinFilaments() into both
  callers. slicer_filament persistence is unchanged so existing spools
  keep slicing correctly.
2026-05-09 08:43:51 +02:00
MartinNYHC b30a283184 Feature/spoolman inventory UI (#1241)
feat(spoolman-inventory): squashed feature work for rebase onto dev

Squashed all commits from feature/spoolman-inventory-ui onto a single commit
to enable a clean rebase onto dev. Original per-commit history preserved at
backup tag backup/spoolman-inventory-ui-prerebase-20260507-105721.
2026-05-08 11:52:42 +02:00
MartinNYHC dac2a31192 Revert "feat(inventory): unified Spoolman inventory UI + AMS slot assignments…" (#1232)
This reverts commit 55d71498e9.
2026-05-07 11:30:31 +02:00
Sn0rrii 55d71498e9 feat(inventory): unified Spoolman inventory UI + AMS slot assignments + Storage Location + NFC write support + Spoolman Filament Catalog Picker (#1114)
feat(spoolman-inventory): squashed feature work for rebase onto dev

Squashed all commits from feature/spoolman-inventory-ui onto a single commit
to enable a clean rebase onto dev. Original per-commit history preserved at
backup tag backup/spoolman-inventory-ui-prerebase-20260507-105721.
2026-05-07 11:15:24 +02:00
Martha Augsburger 7baf1caf75 UI/multi color handling (#1205) 2026-05-05 09:34:16 +02:00
maziggy 86b54026f6 fix(inventory): hydrate Extra Colours; render Dual Color as bars; punch up Sparkle + checkerboard (#1154)
@maugsburger surfaced four bugs against the original #1154 multi-colour
  swatch work:

  1. Editing an existing spool always opened with the Extra Colours field
     blank, even when the COLOR preview banner above it was rendering
     correctly from the saved data. ColorSection seeded its local
     ``extraColorsDraft`` via ``useState(formData.extra_colors)`` at
     mount time, but SpoolFormModal opens *before* its own useEffect
     populates ``formData`` from the spool record — so by the time the
     saved value landed, the input had already locked onto ''. The user
     then had to retype the value before saving anything else.

  2. Dual Color and Gradient produced the same diagonal blend
     (``linear-gradient(135deg, A, B)``), so the two variants were
     visually indistinguishable. The whole point of the Dual Color variant
     is that the spool has two distinct bars on the reel — a smooth blend
     defeats it.

  3. Sparkle was almost invisible on card-sized swatches. The original
     4-dot pattern (each ~1px) read fine on the inline 20×20 swatch but
     disappeared on the 60-pixel inventory card banners — exactly where
     the user actually identifies a spool.

  4. Checkerboard cell density scaled with the swatch — the same 4-cell
     pattern was either tiny squares on a small swatch or four huge
     squares on a card-sized banner. The user couldn't tell a translucent
     filament from a multi-colour one because the indicator changed shape.

  Fix:

  - ``ColorSection.tsx``: ref-guarded ``useEffect`` resyncs the draft
    whenever the parent's ``formData.extra_colors`` changes via an
    external update. ``commitExtraColors`` updates the ref before
    calling ``updateField`` so live user typing is round-tripped without
    the resync useEffect clobbering it.

  - ``filamentSwatchHelpers.ts: buildColorLayer``: branch on
    ``effect_type``. ``dual-color`` and ``tri-color`` produce
    ``linear-gradient(to right, c1 0 X%, c2 X% Y%, ...)`` with CSS
    double-position stops (hard line, not blend) and equal-width
    segments. ``gradient`` keeps the original 135° smooth blend. The
    ``multicolor`` conic-gradient path is untouched.

  - ``filamentSwatchHelpers.ts: EFFECT_OVERLAYS.sparkle``: bumped from 4
    dots to 13 flecks in mixed sizes (1 / 1.5 / 2 px) and varying
    opacity (0.65 → 1.0) for a depth-of-field "metal flake" feel.

  - ``filamentSwatchHelpers.ts: buildFilamentBackground``: now returns
    ``{ backgroundImage, backgroundSize }`` so per-layer sizes can be
    applied — painted layers stay ``cover``, the checkerboard gets a
    fixed 12px tile so cell density is constant regardless of element
    size. Updated the three existing call sites (``InventoryPage`` group
    banner + spool card, ``ColorSection`` preview) to spread the style
    object directly. ``FilamentSwatch.tsx`` composes the same per-layer
    sizing inline so its output stays in lockstep.

  Tests: 8 new frontend cases pinning the four fixes — Dual/Tri Color
  hard-split (3 tests + 1 regression guard that Dual ≠ Gradient for the
  same stops), Sparkle prominence (≥ 10 distinct radial-gradient layers
  in the rendered background), checkerboard density (last backgroundSize
  layer is a fixed pixel value, not ``cover``), 4 hydration cases (fills
  when formData arrives via parent update, resyncs when the spool
  changes mid-form, doesn't clobber live user typing, clears when the
  new spool has no extra_colors). Existing buildFilamentBackground tests
  updated for the new return-object shape. Full frontend suite: 1610
  passed; full backend suite: 3598 passed; no regressions.
2026-05-01 13:04:10 +02:00
maziggy 6a1e3454db fix(inventory): hydrate Extra Colours on edit + render Dual Color as hard-split bars (#1154)
@maugsburger surfaced two bugs against the original #1154 multi-colour
  swatch work:

  1. Editing an existing spool always opened with the Extra Colours field
     blank, even when the COLOR preview banner above it was rendering
     correctly from the saved data. ColorSection seeded its local
     ``extraColorsDraft`` via ``useState(formData.extra_colors)`` at
     mount time, but SpoolFormModal opens *before* its own useEffect
     populates ``formData`` from the spool record — so by the time the
     saved value landed, the input had already locked onto ''. The user
     then had to retype the value before saving anything else.

  2. Dual Color and Gradient produced the same diagonal blend
     (``linear-gradient(135deg, A, B)``), so the two variants were
     visually indistinguishable. The whole point of the Dual Color variant
     is that the spool has two distinct bars on the reel — a smooth blend
     defeats it.

  Fix:

  - ``ColorSection.tsx``: ref-guarded ``useEffect`` resyncs the draft
    whenever the parent's ``formData.extra_colors`` changes via an
    external update (modal opening with a spool, or switching to a
    different spool mid-form). ``commitExtraColors`` updates the ref
    before calling ``updateField`` so the user's own typing is round-
    tripped without the resync useEffect clobbering it.

  - ``filamentSwatchHelpers.ts``: ``buildColorLayer`` now branches on
    ``effect_type``. ``dual-color`` and ``tri-color`` produce
    ``linear-gradient(to right, c1 0 X%, c2 X% Y%, ...)`` with CSS
    double-position stops — the colour change is a hard vertical line
    rather than a blend region — and equal-width segments across N stops.
    ``gradient`` keeps the original 135° smooth blend. The
    ``multicolor`` conic-gradient path is untouched.

  Tests: 4 new ``FilamentSwatch.test.tsx`` cases pinning the hard-split
  contract (Dual Color uses ``to right`` not ``135deg``; Tri Color
  renders 3 equal hard-split bars; ``gradient`` keeps the smooth
  diagonal; explicit regression guard that Dual Color and Gradient never
  produce the same CSS string for the same stops). 4 new
  ``ColorSectionExtraColorsHydration.test.tsx`` cases pinning the input
  hydration (fills when formData arrives via parent update, resyncs when
  the spool changes mid-form, doesn't clobber live user typing, clears
  when the new spool has no extra_colors). Full frontend suite: 1608
  passed; full backend suite: 3598 passed; no regressions.

  The minor "Sparkle could be more prominent / checkerboard denser"
  feedback in the same comment is deferred to a separate cosmetic pass —
  the reporter flagged it as finetuning.
2026-05-01 12:53:03 +02:00
maziggy a34beaa599 feat(inventory): multi-colour gradients, transparency, visual effects (#1154)
Spool and color_catalog rows carry extra_colors (comma-separated hex
  stops) and effect_type (14 visual variants: surface effects, sheen,
  structural). The shared FilamentSwatch component renders gradient,
  conic, effect overlay, and alpha-checkerboard consistently across the
  inventory grid, table, group banner, card, ColorSection preview, and
  catalog editor. Catalog hex_color accepts #RRGGBBAA so catalog entries
  can carry transparency too.

  The paste field accepts the exact format 3dfilamentprofiles.com puts on
  its filament details pages, so users can copy a multi-colour combo
  directly. The effect dropdown spans the full filament-variant
  vocabulary -- surface effects (sparkle/wood/marble/glow/matte), sheen
  variants (silk/galaxy/rainbow/metal/translucent), and structural
  variants (gradient/dual-color/tri-color/multicolor). None of these
  fields touch MQTT/firmware -- pure visual hint.

  Spool group-key extended to include extra_colors + effect_type so
  "Group similar" no longer collapses visually distinct spools.

  Migrations: 4 idempotent ALTER TABLE ADD COLUMN (Postgres-safe), plus
  ALTER COLUMN hex_color TYPE VARCHAR(9) on Postgres only (SQLite ignores
  VARCHAR length).

  Tests: 42 new backend (35 unit + 7 integration), 20 new frontend (14
  FilamentSwatch + 3 ColorCatalogSettings + 3 InventoryPageGrouping
  regression). 3522 backend + 1582 frontend tests pass; ruff clean.
  Localised across all 8 UI locales.
2026-04-29 09:13:33 +02:00
maziggy 4304a42542 feat(#729): per-spool category + low-stock threshold override
Two new optional fields on Spool: free-text `category` (max 50) and
  `low_stock_threshold_pct` (1-99). Powers the "differentiate critical
  spools from prototype spools and alert at different thresholds" use
  case from #729 without taking on the full multi-tag taxonomy + auto-
  apply rules + per-tag alert system the ticket originally proposed.

  Form gains:
  - Category input with datalist autocomplete sourced from categories
    already in use, so casing/spelling stays consistent.
  - Per-spool low-stock threshold input. Empty = global default; the
    global value renders as the placeholder.

  Inventory page:
  - New category filter chip (hidden until at least one spool carries
    a category — keeps the chip row uncluttered).
  - Stat-card "Low Stock" count and the "Low Stock" filter both honour
    the per-spool override.

  Plus: rename "Delete Tag" button to "Clear RFID Tag" (the original
  ticket reporter mistook it for a taxonomy-tag delete; the button
  actually clears the RFID UID/UUID off the spool record). Toast key
  renamed from `tagDeleted` to `rfidCleared`.

  i18n: full translations across all 8 locales.

  Tests: 9 new backend schema tests (defaults, partial-update, range
  rejection, max-length); 2 new frontend tests (per-spool threshold
  pulls extra spools into low-stock count, filter chip hidden when no
  categories exist).
2026-04-25 13:24:49 +02:00
maziggy 9e938cbc8c Revert "feat(inventory): unified Spoolman inventory UI + Storage Location + AMS deep-link + SpoolBuddy NFC write support (#1063)"
This reverts commit 89f14c57ad.
2026-04-24 14:33:33 +02:00
Sn0rrii 89f14c57ad feat(inventory): unified Spoolman inventory UI + Storage Location + AMS deep-link + SpoolBuddy NFC write support (#1063)
feat(inventory): replace Spoolman iframe with internal inventory UI

When Spoolman is enabled, the Inventory page now uses the same internal
UI (spool list, create/edit modal, archive, delete, weight sync) backed
by a new proxy layer instead of opening an iframe.
2026-04-24 14:00:45 +02:00
maziggy bf5135cb12 fix(inventory): malformed rgba no longer bricks the Filaments page (#1055)
A single legacy spool with a 7-char rgba ('FFFFFFF', missing one F)
  caused GET /api/v1/inventory/spools to 500 with a pydantic
  ResponseValidationError, leaving the reporter with a blank Filaments
  page and "Add Spool" silently failing. Root cause spans three layers:

  1. Write path: SpoolUpdate.rgba had no pattern constraint (only
     SpoolCreate did), so PATCH could plant malformed values in the DB.
  2. Frontend: ColorSection hex input's `val.length <= 6 ? 'FF' : ''`
     emitted 7-char rgba for 5-char input (XXXXX + FF = 7) and for
     7-char typed input (no alpha appended).
  3. Read path: SpoolResponse inherited the write-side pattern, so a
     single bad row 500'd the entire list endpoint instead of being
     tolerated through serialize.

  SpoolUpdate.rgba now carries the same ^[0-9A-Fa-f]{8}$ pattern as
  SpoolCreate. The hex input emits a fully-formed 8-char RRGGBBAA on
  every keystroke — 8-char paste passes through, 7-char drops the
  stray, shorter input pads RGB with '0' and appends FF alpha.
  SpoolResponse.rgba is now Optional[str] with no pattern — write-side
  validation is the right place for format rules; responses must
  tolerate historical rows.

  Tests: 16 schema tests (SpoolCreate/Update reject, SpoolResponse
  tolerate), 7 frontend tests covering every input length 0–8 plus
  non-hex strip. A user who already has a bad row in their DB now sees
  it render with a default color instead of having to hand-edit SQLite.
2026-04-21 14:40:03 +02:00
maziggy 75fa935851 Fix filament color name and subtype inconsistencies (#857) 2026-04-01 12:20:12 +02:00
maziggy 4f5a9f4b95 Fix spool form profile dropdown truncating long preset names (#534)
Long filament profile names were cut off because inline filament ID
codes consumed horizontal space in the dropdown. Remove the codes from
dropdown items (selected code still shown below the input) and widen
the modal from max-w-lg to max-w-xl.
2026-02-27 07:30:03 +01:00
maziggy e450c3b728 Add visual spool grouping and optional brand/subtype in quick-add (#480)
Group toggle in inventory toolbar collapses identical unused/unassigned
spools into expandable rows/cards with count badges. Grouping key uses
material, subtype, brand, color, and label weight. Brand and subtype
fields now appear in quick-add mode as optional (no asterisk, no
validation). Quantity field restricted to quick-add mode only.
2026-02-22 10:05:03 +01:00
maziggy 16fa84bc16 Add bulk spool addition and stock spools
Add Quick Add mode to the spool form for simplified entry (material +
color + weight only), and a quantity field (1-100) for creating multiple
identical spools at once. Stock spools (no slicer profile) are computed
rather than stored — any spool without slicer_filament shows an amber
"Stock" badge. New filter chips (All/Stock/Configured) on the inventory
page. Backend bulk endpoint creates N spools in one transaction.

Backend:
- SpoolBulkCreate schema with quantity validation (1-100)
- POST /inventory/spools/bulk endpoint

Frontend:
- Quick Add toggle in SpoolFormModal (create mode only)
- Quantity field in FilamentSection (both modes)
- bulkCreateSpools API method and bulkCreateMutation
- Stock column (hidden by default) and stock filter chips
- Inline error rendering moved into FilamentSection
- CellCtx extended with t() for translatable cell content

Tests:
- 13 backend tests (schema validation + endpoint logic)
- 10 frontend tests (validateForm quickAdd + UI behavior)

i18n: 6 locales (en, de, fr, it, ja, pt-BR)
Docs: CHANGELOG, README, website features, wiki inventory
2026-02-21 10:39:18 +01:00
maziggy 7d46b44561 Fix PCTG filament misidentified as PC (#478) 2026-02-21 10:01:26 +01:00
KeyboredandMartinNYHC 8b081dbd9a Allow external materials/brands to be used when adding spool (#455)
* feat(spool-form): enhance brand and material selection in color section

* feat(spool-form): change input field behavoiur when deleting all chars, fix scroll menu theming

* feat(spool-form): add available materials to SpoolFormModal and FilamentSection

* refactor(ColorSection): remove unnecessary hasCatalogMatch variable

* feat(SpoolFormModal): add bilateral brand and material filtering logic

* feat(ColorCatalogSettings): improve input field sizes and layout for color and material selection

* Refactor colorCatalog settings and dependencies, allow custom materials, fix ml-12 marging, show catalog colors on brand/material selection

* Refactor ColorCatalogSettings and ColorSection components; remove  onBlur logic in FilamentSection

* Prioritize exact matches in search results

* Added inventory.useCustomMaterial to locales

* Remove unused filteredCatalogColors logic and update button rendering; update types file to import Printer and SpoolKProfile

* Fix indentation for useCustomMaterial

* Refactor showCatalogSection logic to only depend on matched catalog colors

* Remove extra newline before brand dropdown

* Clean up ColorSection.tsx by removing blank lines

Removed unnecessary blank lines in ColorSection.tsx.

* Ensure material property is a string in ColorSection component

---------

Co-authored-by: MartinNYHC <mz@v8w.de>
2026-02-20 18:34:24 +01:00
Matteo Parenti 9f19e8a2ff Add currency symbol, hide projected cost for no costs configured, add cost rounding to decimal point, fixed isExternal logic 2026-02-20 11:36:26 +01:00
Matteo Parenti 04ffca204f Add cost tracking for spools and usage history 2026-02-19 14:13:18 +01:00
maziggy 11d9c98b55 fix(inventory): require brand and subtype in spool form (#417)
The spool add/edit modal allowed saving without brand or subtype,
causing incomplete tray_sub_brands when assigned to AMS slots.
BambuStudio then failed to recognize the filament profile.

Brand and Subtype are now mandatory with validation errors on submit.
2026-02-18 14:59:03 +01:00
Matteo Parenti a649a246b2 Merge branch 'feature/filament-fix' of https://github.com/Keybored02/bambuddy into feature/filament-fix 2026-02-18 12:03:02 +01:00
Matteo Parenti 87c6bfe672 fix(spool-form): improve handling of core_weight_catalog_id and catalog ID selection logic 2026-02-18 12:02:55 +01:00
MartinNYHC b0d341a58a Merge branch '0.2.1b' into feature/filament-fix 2026-02-18 11:32:43 +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
Matteo Parenti 233f50c38d fix(inventory): spool catalog entry ID matching 2026-02-17 16:07:15 +01:00
Matteo Parenti 30b6190163 Add Spool card fixes and additions, It locale update 2026-02-13 16:26:18 +01:00
maziggy e6e62f2a68 Add spool inventory: AMS slot assignment, usage tracking, and remaining weight editing
Built-in spool inventory with AMS slot assignment for non-BL spools,
automatic filament usage tracking via 3MF estimates, and remaining
weight editing in the spool form.

Inventory features:
- AMS slot assignment: assign/unassign manual spools to AMS tray slots
- Filter out Bambu Lab spools (RFID-managed) from assignment modal
- Filter out already-assigned spools (one spool per slot)
- Auto-unlink manual assignments when a BL spool is inserted
- Hide assign/unassign UI on BL-occupied slots
- Case-insensitive fingerprint matching for auto-unlink logic

Usage tracking:
- 3MF-based filament consumption tracking for non-BL spools
- Extract per-filament used_g from archived 3MF slice_info
- Map 3MF slot_id to AMS (ams_id, tray_id) positions
- Scale estimates by print progress for failed/aborted prints
- Skip BL spools (tracked via AMS remain% delta, Path 1)
- Prevent double-counting with handled_trays set

Other:
- Add remaining weight field to spool edit form
- Fix hardcoded "Empty" string in AssignSpoolModal (now i18n)
- Add backend unit tests for usage_tracker (11 tests)
- Add frontend tests for AssignSpoolModal filters (5 tests)
- Update CHANGELOG, README, wiki docs, and website
2026-02-12 15:42:27 +01:00
maziggy ec82092bc7 Sync 2026-02-12 07:07:50 +01:00