colors.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Bambu Lab filament hex color to name mapping (from bambu-color-names.csv)
  2. const BAMBU_HEX_COLORS: Record<string, string> = {
  3. '000000': 'Black', '001489': 'Blue', '002e96': 'Blue', '0047bb': 'Blue', '00482b': 'Pine Green',
  4. '004ea8': 'Blue', '0056b8': 'Cobalt Blue', '0069b1': 'Lake Blue', '0072ce': 'Blue', '0078bf': 'Marine Blue',
  5. '0085ad': 'Light Blue', '0086d6': 'Cyan', '008bda': 'Blue', '009639': 'Green', '009bd8': 'Cyan',
  6. '009fa1': 'Teal', '00a6a0': 'Green', '00ae42': 'Bambu Green', '00b1b7': 'Turquoise', '00bb31': 'Green',
  7. '018814': 'Candy Green', '042f56': 'Dark Blue', '0a2989': 'Blue', '0a2ca5': 'Blue', '0c2340': 'Navy Blue',
  8. '0c3b95': 'Blue', '101820': 'Black', '147bd1': 'Blue', '164b35': 'Green', '16b08e': 'Malachite Green',
  9. '1d7c6a': 'Oxide Green Metallic', '1f79e5': 'Lake Blue', '2140b4': 'Blue', '25282a': 'Black', '2842ad': 'Royal Blue',
  10. '2d2b28': 'Onyx Black Sparkle', '324585': 'Indigo Blue', '353533': 'Gray', '39541a': 'Forest Green',
  11. '39699e': 'Cobalt Blue Metallic', '3b665e': 'Green', '3f5443': 'Alpine Green Sparkle', '3f8e43': 'Mistletoe Green',
  12. '424379': 'Nebulae', '43403d': 'Iron Gray Metallic', '482960': 'Indigo Purple', '483d8b': 'Royal Purple Sparkle',
  13. '489fdf': 'Azure', '4c241c': 'Rosewood', '4ce4a0': 'Green', '4d3324': 'Dark Chocolate', '4d5054': 'Lava Gray',
  14. '4dafda': 'Cyan', '4f3f24': 'Black Walnut', '515151': 'Dark Gray', '515a6c': 'Gray', '545454': 'Dark Gray',
  15. '565656': 'Titan Gray', '56b7e6': 'Sky Blue', '583061': 'Violet Purple', '5898dd': 'Blue', '594177': 'Purple',
  16. '5b492f': 'Brown', '5b6579': 'Blue Gray', '5c9748': 'Matcha Green', '5e43b7': 'Purple', '5e4b3c': 'Copper',
  17. '5f6367': 'Titan Gray', '61b0ff': 'Translucent Light Blue', '61bf36': 'Green', '61c680': 'Grass Green',
  18. '6667ab': 'Lavender Blue', '684a43': 'Brown', '686865': 'Black', '68724d': 'Dark Green', '688197': 'Blue Gray',
  19. '69398e': 'Iris Purple', '6e88bc': 'Jeans Blue', '6ee53c': 'Lime Green', '6f5034': 'Cocoa Brown', '7248bd': 'Lavender',
  20. '748c45': 'Translucent Olive', '757575': 'Nardo Gray', '75aed8': 'Blue', '77edd7': 'Translucent Teal', '789d4a': 'Olive',
  21. '792b36': 'Crimson Red Sparkle', '7ac0e9': 'Glow Blue', '7ae1bf': 'Mint', '7cd82b': 'Lime Green', '7d6556': 'Dark Brown',
  22. '8344b0': 'Purple', '847d48': 'Bronze', '854ce4': 'Purple', '8671cb': 'Purple', '875718': 'Peanut Brown',
  23. '87909a': 'Silver', '898d8d': 'Gray', '8a949e': 'Gray', '8e8e8e': 'Translucent Gray', '8e9089': 'Gray',
  24. '90ff1a': 'Neon Green', '918669': 'Classic Birch', '939393': 'Gray', '950051': 'Plum', '951e23': 'Burgundy Red',
  25. '959698': 'Silver', '96d8af': 'Light Jade', '96dcb9': 'Mint', '995f11': 'Clay Brown', '999d9d': 'Gray',
  26. '9b9ea0': 'Ash Gray', '9d2235': 'Maroon Red', '9d432c': 'Brown', '9e007e': 'Purple', '9ea2a2': 'Gray',
  27. '9f332a': 'Brick Red', 'a1ffac': 'Glow Green', 'a3d8e1': 'Ice Blue', 'a6a9aa': 'Silver', 'a8a8aa': 'Gray',
  28. 'a8c6ee': 'Baby Blue', 'aa6443': 'Copper Brown Metallic', 'ad4e38': 'Red Granite', 'adb1b2': 'Gray',
  29. 'ae835b': 'Caramel', 'ae96d4': 'Lilac Purple', 'af1685': 'Purple', 'afb1ae': 'Gray', 'b15533': 'Terracotta',
  30. 'b28b33': 'Gold', 'b39b84': 'Iridium Gold Metallic', 'b50011': 'Red', 'b8acd6': 'Lavender', 'b8cde9': 'Ice Blue',
  31. 'ba9594': 'Rose Gold', 'bb3d43': 'Dark Red', 'bc0900': 'Red', 'becf00': 'Bright Green', 'c0df16': 'Green',
  32. 'c12e1f': 'Red', 'c2e189': 'Apple Green', 'c3e2d6': 'Light Cyan', 'c5ed48': 'Lime', 'c6001a': 'Red',
  33. 'c6c6c6': 'Gray', 'c8102e': 'Red', 'c8c8c8': 'Silver', 'c98935': 'Ochre Yellow', 'c9a381': 'Translucent Brown',
  34. 'cbc6b8': 'Bone White', 'cdceca': 'Gray', 'cea629': 'Classic Gold Sparkle', 'd02727': 'Candy Red',
  35. 'd1d3d5': 'Light Gray', 'd32941': 'Red', 'd3b7a7': 'Latte Brown', 'd6001c': 'Red', 'd6abff': 'Translucent Purple',
  36. 'd6cca3': 'White Oak', 'dc3a27': 'Orange', 'dd3c22': 'Vermilion Red', 'de4343': 'Scarlet Red', 'dfd1a7': 'Beige',
  37. 'e02928': 'Red', 'e4bd68': 'Gold', 'e5b03d': 'Gold', 'e83100': 'Red', 'e8afcf': 'Sakura Pink', 'e8dbb7': 'Desert Tan',
  38. 'eaeae4': 'White', 'eaeceb': 'Silver', 'ec008c': 'Magenta', 'ed0000': 'Red', 'eeb1c1': 'Pink', 'efe255': 'Yellow',
  39. 'f0f1a8': 'Clear', 'f17b8f': 'Glow Pink', 'f3cfb2': 'Champagne', 'f3e600': 'Yellow', 'f48438': 'Orange',
  40. 'f4a925': 'Gold', 'f4d53f': 'Yellow', 'f4ee2a': 'Yellow', 'f5547c': 'Hot Pink', 'f55a74': 'Pink',
  41. 'f5b6cd': 'Cherry Pink', 'f5dbab': 'Mellow Yellow', 'f5f1dd': 'White', 'f68b1b': 'Neon Orange', 'f74e02': 'Orange',
  42. 'f75403': 'Orange', 'f7ada6': 'Pink', 'f7d959': 'Lemon Yellow', 'f7e6de': 'Beige', 'f7f3f0': 'White Marble',
  43. 'f8ff80': 'Glow Yellow', 'f99963': 'Mandarin Orange', 'f9c1bd': 'Translucent Pink', 'f9dfb9': 'Cream',
  44. 'f9ef41': 'Yellow', 'f9f7f2': 'Nature', 'f9f7f4': 'White', 'fce300': 'Yellow', 'fce900': 'Yellow',
  45. 'fec600': 'Sunflower Yellow', 'fedb00': 'Yellow', 'ff4800': 'Orange', 'ff671f': 'Orange', 'ff6a13': 'Orange',
  46. 'ff7f41': 'Orange', 'ff9016': 'Pumpkin Orange', 'ff911a': 'Translucent Orange', 'ff9d5b': 'Glow Orange',
  47. 'ffb549': 'Sunflower Yellow', 'ffc72c': 'Tangerine Yellow', 'ffce00': 'Yellow', 'ffd00b': 'Yellow',
  48. 'ffe133': 'Yellow', 'fffaf2': 'White', 'ffffff': 'White',
  49. };
  50. /**
  51. * Convert hex color to basic color name using HSL analysis.
  52. * Used as fallback when hex is not in Bambu database.
  53. */
  54. export function hexToColorName(hex: string | null | undefined): string {
  55. if (!hex || hex.length < 6) return 'Unknown';
  56. const cleanHex = hex.replace('#', '');
  57. const r = parseInt(cleanHex.substring(0, 2), 16);
  58. const g = parseInt(cleanHex.substring(2, 4), 16);
  59. const b = parseInt(cleanHex.substring(4, 6), 16);
  60. const max = Math.max(r, g, b) / 255;
  61. const min = Math.min(r, g, b) / 255;
  62. const l = (max + min) / 2;
  63. let h = 0;
  64. let s = 0;
  65. if (max !== min) {
  66. const d = max - min;
  67. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  68. const rNorm = r / 255, gNorm = g / 255, bNorm = b / 255;
  69. if (max === rNorm) h = ((gNorm - bNorm) / d + (gNorm < bNorm ? 6 : 0)) / 6;
  70. else if (max === gNorm) h = ((bNorm - rNorm) / d + 2) / 6;
  71. else h = ((rNorm - gNorm) / d + 4) / 6;
  72. }
  73. h = h * 360;
  74. if (l < 0.15) return 'Black';
  75. if (l > 0.85) return 'White';
  76. if (s < 0.15) {
  77. if (l < 0.4) return 'Dark Gray';
  78. if (l > 0.6) return 'Light Gray';
  79. return 'Gray';
  80. }
  81. // Brown is orange/yellow hue with lower lightness
  82. if (h >= 15 && h < 45 && l < 0.45) return 'Brown';
  83. if (h >= 45 && h < 70 && l < 0.40) return 'Brown';
  84. if (h < 15 || h >= 345) return 'Red';
  85. if (h < 45) return 'Orange';
  86. if (h < 70) return 'Yellow';
  87. if (h < 150) return 'Green';
  88. if (h < 200) return 'Cyan';
  89. if (h < 260) return 'Blue';
  90. if (h < 290) return 'Purple';
  91. return 'Pink';
  92. }
  93. /**
  94. * Get color name from hex color.
  95. * First tries Bambu Lab color database lookup, then falls back to HSL-based name.
  96. */
  97. export function getColorName(hexColor: string): string {
  98. const hex = hexColor.replace('#', '').toLowerCase().substring(0, 6);
  99. if (BAMBU_HEX_COLORS[hex]) {
  100. return BAMBU_HEX_COLORS[hex];
  101. }
  102. return hexToColorName(hexColor);
  103. }
  104. /**
  105. * Resolve a spool's display color name.
  106. * Tries: stored color_name (if it's a readable name) → hex color database → HSL fallback.
  107. * Detects Bambu internal codes (e.g. "A06-D0") and resolves them to names ("Titan Gray").
  108. */
  109. export function resolveSpoolColorName(colorName: string | null, rgba: string | null): string | null {
  110. // If color_name looks like a readable name (no pattern like "X00-Y0"), use it directly
  111. if (colorName && !/^[A-Z]\d+-[A-Z]\d+$/.test(colorName)) {
  112. return colorName;
  113. }
  114. // Try hex color lookup from rgba
  115. if (rgba && rgba.length >= 6) {
  116. const hex = rgba.substring(0, 6).toLowerCase();
  117. if (BAMBU_HEX_COLORS[hex]) {
  118. return BAMBU_HEX_COLORS[hex];
  119. }
  120. }
  121. // Return null (displayed as "-") — better than showing a code
  122. return null;
  123. }
  124. /**
  125. * Parse an RGBA hex string (e.g., "FF0000FF") to a CSS rgba() color.
  126. * Returns null for empty, all-zero, or fully transparent colors.
  127. */
  128. export function parseFilamentColor(rgba: string): string | null {
  129. if (!rgba || rgba === '00000000' || rgba.length < 6) return null;
  130. const r = rgba.slice(0, 2);
  131. const g = rgba.slice(2, 4);
  132. const b = rgba.slice(4, 6);
  133. const a = rgba.length >= 8 ? parseInt(rgba.slice(6, 8), 16) / 255 : 1;
  134. if (a === 0) return null;
  135. return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${a})`;
  136. }
  137. /**
  138. * Check if a hex color is light (for choosing text contrast).
  139. * Uses luminance formula: 0.299*R + 0.587*G + 0.114*B.
  140. */
  141. export function isLightColor(hex: string | null): boolean {
  142. if (!hex || hex.length < 6) return false;
  143. const cleanHex = hex.replace('#', '');
  144. const r = parseInt(cleanHex.slice(0, 2), 16);
  145. const g = parseInt(cleanHex.slice(2, 4), 16);
  146. const b = parseInt(cleanHex.slice(4, 6), 16);
  147. return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.6;
  148. }