mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-08 23:21:58 +02:00
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).
84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import {
|
|
parseStops,
|
|
buildFilamentBackground,
|
|
type FilamentEffect,
|
|
type SwatchType,
|
|
} from './filamentSwatchHelpers';
|
|
|
|
/** Shared filament-colour swatch. See `filamentSwatchHelpers.ts` for the
|
|
* pure helpers + constants this component composes. */
|
|
|
|
export interface FilamentSwatchProps {
|
|
/** RRGGBBAA hex without `#` (Bambu/AMS canonical). Falls back to grey when null. */
|
|
rgba?: string | null;
|
|
/** Comma-separated 6/8-char hex tokens (no `#`). Empty/undefined → solid. */
|
|
extraColors?: string | null;
|
|
/** Visual effect overlay. */
|
|
effectType?: FilamentEffect | string | null;
|
|
/** When `Multicolor`, a conic gradient is used instead of linear. */
|
|
subtype?: string | null;
|
|
/** Tailwind size token applied to width/height (e.g. `w-5 h-5`). Default: `w-5 h-5`. */
|
|
className?: string;
|
|
/** Override the rounded shape — defaults to `rounded-full` (circular). */
|
|
shape?: 'circle' | 'pill' | 'square';
|
|
/** Optional inline style overrides (e.g. height of a card banner). */
|
|
style?: React.CSSProperties;
|
|
/** Native title attribute for hover tooltip. */
|
|
title?: string;
|
|
/** Tune effect appearance based on target div size. */
|
|
effectSize: SwatchType;
|
|
}
|
|
|
|
export function FilamentSwatch({
|
|
rgba,
|
|
extraColors,
|
|
effectType,
|
|
subtype,
|
|
className = 'w-5 h-5',
|
|
shape = 'circle',
|
|
style,
|
|
title,
|
|
effectSize,
|
|
}: FilamentSwatchProps) {
|
|
const stops = useMemo(() => parseStops(extraColors), [extraColors]);
|
|
|
|
const filamentBackground = useMemo(
|
|
() => buildFilamentBackground({ effectSize, rgba, extraColors, effectType, subtype }),
|
|
[effectSize, rgba, extraColors, effectType, subtype]
|
|
);
|
|
const backgroundImage = filamentBackground.backgroundImage;
|
|
const backgroundSize = filamentBackground.backgroundSize;
|
|
|
|
const shapeClass =
|
|
shape === 'circle' ? 'rounded-full' : shape === 'pill' ? 'rounded-full' : 'rounded';
|
|
|
|
// Compute a sensible title fallback — solid hex or gradient summary.
|
|
// Show the full 8-char rgba when alpha < FF so the tooltip on a Clear /
|
|
// translucent spool reflects what's actually painted (#1545).
|
|
const titleHex = (() => {
|
|
if (!rgba) return undefined;
|
|
const clean = rgba.replace(/^#/, '');
|
|
if (clean.length >= 8 && clean.substring(6, 8).toLowerCase() !== 'ff') {
|
|
return `#${clean.substring(0, 8)}`;
|
|
}
|
|
return `#${clean.substring(0, 6)}`;
|
|
})();
|
|
const computedTitle =
|
|
title ??
|
|
(stops.length > 0
|
|
? stops.join(', ')
|
|
: titleHex);
|
|
|
|
return (
|
|
<span
|
|
data-testid="filament-swatch"
|
|
className={`${className} ${shapeClass} border border-black/20 inline-block flex-shrink-0`}
|
|
style={{ backgroundImage, backgroundSize, ...style }}
|
|
title={computedTitle}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default FilamentSwatch;
|