flold

3MF Color Analyzer

Aug 26th, 2025
215
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 26.85 KB | Software | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.   <meta charset="UTF-8" />
  5.   <title>Bambu Lab .3MF Color Analyzer - Enhanced Matching</title>
  6.   <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
  7.   <style>
  8.     body {
  9.       font-family: sans-serif;
  10.       background: #f4f4f4;
  11.       padding: 2em;
  12.       color: #333;
  13.     }
  14.     h1 {
  15.       text-align: center;
  16.     }
  17.     .file-section, .results {
  18.       background: white;
  19.       border: 1px solid #ccc;
  20.       border-radius: 8px;
  21.       padding: 1em;
  22.       margin-bottom: 2em;
  23.     }
  24.     .match-item {
  25.       border-left: 4px solid #888;
  26.       padding: 10px;
  27.       margin-bottom: 10px;
  28.       background: #f9f9f9;
  29.       border-radius: 4px;
  30.       position: relative;
  31.     }
  32.     .match-item input[type="radio"] {
  33.       position: absolute;
  34.       top: 10px;
  35.       right: 10px;
  36.       transform: scale(1.5);
  37.     }
  38.     .match-item.selected {
  39.       border-left: 4px solid #0066cc;
  40.       background: #e6f3ff;
  41.     }
  42.     .color-box {
  43.       width: 30px;
  44.       height: 30px;
  45.       display: inline-block;
  46.       border: 1px solid #000;
  47.       vertical-align: middle;
  48.       margin-right: 10px;
  49.     }
  50.     .match-group {
  51.       margin-bottom: 2em;
  52.     }
  53.     .match-color-box {
  54.       width: 20px;
  55.       height: 20px;
  56.       display: inline-block;
  57.       border: 1px solid #000;
  58.       vertical-align: middle;
  59.       margin-right: 5px;
  60.     }
  61.     #generateSummary {
  62.       background: #0066cc;
  63.       color: white;
  64.       padding: 10px 20px;
  65.       border: none;
  66.       border-radius: 5px;
  67.       font-size: 16px;
  68.       cursor: pointer;
  69.       margin: 20px 0;
  70.     }
  71.     #generateSummary:hover {
  72.       background: #0052a3;
  73.     }
  74.     #generateSummary:disabled {
  75.       background: #ccc;
  76.       cursor: not-allowed;
  77.     }
  78.     #summary {
  79.       background: white;
  80.       border: 1px solid #ccc;
  81.       border-radius: 8px;
  82.       padding: 1em;
  83.       margin-top: 2em;
  84.       display: none;
  85.     }
  86.     .summary-item {
  87.       margin-bottom: 15px;
  88.       padding: 10px;
  89.       border-left: 4px solid #0066cc;
  90.       background: #f9f9f9;
  91.     }
  92.     #printSummary {
  93.       background: #28a745;
  94.       color: white;
  95.       padding: 8px 16px;
  96.       border: none;
  97.       border-radius: 4px;
  98.       cursor: pointer;
  99.       margin-right: 10px;
  100.     }
  101.     #copySummary {
  102.       background: #6c757d;
  103.       color: white;
  104.       padding: 8px 16px;
  105.       border: none;
  106.       border-radius: 4px;
  107.       cursor: pointer;
  108.     }
  109.     @media print {
  110.       body * {
  111.         visibility: hidden;
  112.       }
  113.       #summary, #summary * {
  114.         visibility: visible;
  115.       }
  116.       #summary {
  117.         position: absolute;
  118.         left: 0;
  119.         top: 0;
  120.         width: 100%;
  121.       }
  122.       #printSummary, #copySummary {
  123.         display: none;
  124.       }
  125.       .print-color-box {
  126.         -webkit-print-color-adjust: exact !important;
  127.         print-color-adjust: exact !important;
  128.         color-adjust: exact !important;
  129.       }
  130.     }
  131.     #dropZone {
  132.       border: 2px dashed #aaa;
  133.       border-radius: 8px;
  134.       padding: 20px;
  135.       text-align: center;
  136.       color: #666;
  137.       margin: 1em 0;
  138.       background: #f9f9f9;
  139.     }
  140.     .algorithm-selector {
  141.       margin: 10px 0;
  142.       padding: 10px;
  143.       background: #f0f8ff;
  144.       border-radius: 5px;
  145.     }
  146.     .algorithm-info {
  147.       font-size: 12px;
  148.       color: #666;
  149.       margin-top: 5px;
  150.     }
  151.     .distance-info {
  152.       font-size: 11px;
  153.       color: #888;
  154.     }
  155.     .test-section {
  156.       background: #fff3cd;
  157.       border: 1px solid #ffeaa7;
  158.       border-radius: 8px;
  159.       padding: 1em;
  160.       margin-bottom: 2em;
  161.     }
  162.     .matches-selector {
  163.       margin: 10px 0;
  164.       padding: 10px;
  165.       background: #fff8e1;
  166.       border-radius: 5px;
  167.       border: 1px solid #ffcc02;
  168.     }
  169.     .matches-selector label {
  170.       font-weight: bold;
  171.       margin-right: 15px;
  172.     }
  173.     .matches-selector input[type="radio"] {
  174.       margin-right: 5px;
  175.       margin-left: 15px;
  176.     }
  177.     .matches-info {
  178.       font-size: 12px;
  179.       color: #666;
  180.       margin-top: 5px;
  181.     }
  182.   </style>
  183. </head>
  184. <body>
  185.   <h1>Bambu Lab .3MF Color Analyzer - Enhanced Matching</h1>
  186.  
  187.   <div class="file-section">
  188.     <div id="dropZone">
  189.       <strong>Drag and drop your .3mf file here</strong><br>or use the browse button below.
  190.     </div>
  191.     <input type="file" id="fileInput" accept=".3mf" />
  192.     <br><br>
  193.    
  194.     <div class="algorithm-selector">
  195.       <label for="algorithmSelect"><strong>Color Matching Algorithm:</strong></label>
  196.       <select id="algorithmSelect">
  197.         <option value="euclidean">Euclidean RGB (Original)</option>
  198.         <option value="deltaE76">Delta E 76 (CIE Lab)</option>
  199.         <option value="deltaE94">Delta E 94 (Improved)</option>
  200.         <option value="deltaE00" selected>Delta E 2000 (Best)</option>
  201.         <option value="weighted">Weighted RGB</option>
  202.       </select>
  203.       <div class="algorithm-info" id="algorithmInfo">
  204.         Delta E 2000 - most perceptually accurate but complex
  205.       </div>
  206.     </div>
  207.  
  208.     <div class="matches-selector">
  209.       <label><strong>Number of Matches to Show:</strong></label>
  210.       <input type="radio" id="matches3" name="matchCount" value="3" checked>
  211.       <label for="matches3">Top 3 Matches</label>
  212.       <input type="radio" id="matches5" name="matchCount" value="5">
  213.       <label for="matches5">Top 5 Matches</label>
  214.       <div class="matches-info">
  215.         Choose how many color matches to display for each input color
  216.       </div>
  217.     </div>
  218.    
  219.     <label for="typeFilter"><strong>Filter by Filament Type:</strong></label>
  220.     <select id="typeFilter" multiple size="6">
  221.       <option value="">All Types (No Filter)</option>
  222.     </select>
  223.     <br><br>
  224.     <button onclick="analyze3MF()">Analyze .3MF File</button>
  225.   </div>
  226.  
  227.   <div id="colorStatus" style="margin-bottom: 1em; font-weight: bold; color: green;"></div>
  228.  
  229.  
  230.   <div id="results" class="results"></div>
  231.   <div id="selectionSection" style="display: none;">
  232.     <button id="generateSummary" onclick="generateSummary()">Generate Summary for Selected Colors</button>
  233.     <p style="color: #666; font-size: 14px;">Select one match for each color above, then click to generate a printable summary.</p>
  234.   </div>
  235.  
  236.   <div id="summary">
  237.     <h2>Selected Filament Colors Summary</h2>
  238.     <div style="margin-bottom: 15px;">
  239.       <button id="printSummary" onclick="window.print()">Print Summary</button>
  240.       <button id="copySummary" onclick="copyToClipboard()">Copy to Clipboard</button>
  241.     </div>
  242.     <div id="summaryContent"></div>
  243.   </div>
  244.  
  245.   <script>
  246.     const colors = [
  247.       {"type":"PLA Basic",          "name":"Jade White",    "hex":"#FFFFFF","code":10001,"location":"PLA 04"},
  248.       {"type":"PLA Basic",          "name":"Voxel Army Green","hex":"#667B65","code":0,"location":"PLA 01"},
  249.  
  250.       {"type":"PLA Silk+",          "name":"Gold",          "hex":"#F4A925","code":13405,"location":"PLA Silk 01"},
  251.       {"type":"PLA Silk+",          "name":"White",         "hex":"#FFFFFF","code":13110,"location":"PLA Silk 02"},
  252.  
  253.       {"type":"PLA Matte",          "name":"Ivory White",   "hex":"#FFFFFF","code":11100,"location":"PLA Matte 04"},
  254.       {"type":"PLA Matte",          "name":"Inland Wood Brown","hex":"#D2B48C","code":0,"location":"PLA Matte 02"},
  255.  
  256.       {"type":"ABS",                "name":"Silver",        "hex":"#87909A","code":40102,"location":"ABS 02"},
  257.       {"type":"ABS",                "name":"Azure",         "hex":"#C00D1E","code":40601,"location":"ABS 03"},
  258.  
  259.       {"type":"ABS-GF",             "name":"Black",         "hex":"#000000","code":41101,"location":"ABS XX"},
  260.       {"type":"ABS-GF",             "name":"Gray",          "hex":"#C6C6C6","code":41102,"location":"ABS XX"},
  261.  
  262.       {"type":"ASA",                "name":"White",         "hex":"#FFFAF2","code":45100,"location":"ASA XX"},
  263.       {"type":"ASA",                "name":"Gray",          "hex":"#8A949E","code":45102,"location":"ASA XX"},
  264.       {"type":"ASA",                "name":"Red",           "hex":"#E02928","code":45200,"location":"ASA XX"},
  265.       {"type":"ASA",                "name":"Green",         "hex":"#00A6A0","code":45500,"location":"ASA XX"},
  266.       {"type":"ASA",                "name":"Blue",          "hex":"#2140B4","code":45600,"location":"ASA XX"},
  267.       {"type":"ASA",                "name":"Black",         "hex":"#000000","code":45101,"location":"ASA XX"},
  268.  
  269.       {"type":"PETG HF",            "name":"Blue",          "hex":"#002E96","code":33600,"location":"PETG HF 01"},
  270.       {"type":"PETG HF",            "name":"Black",         "hex":"#000000","code":33102,"location":"PETG HF 03;Cart"},
  271.  
  272.       {"type":"PLA Marble",         "name":"White Marble",  "hex":"#F7F3F0","code":13103,"location":"PLA 02"},
  273.       {"type":"PLA Marble",         "name":"Red Granite",   "hex":"#AD4E38","code":13201,"location":"PLA 02"},
  274.  
  275.       {"type":"PLA Translucent",    "name":"Cherry Pink",   "hex":"#F5B6CD","code":13211,"location":"PLA Translucent 02"},
  276.       {"type":"PLA Translucent",    "name":"Red",           "hex":"#B50011","code":13210,"location":"PLA Translucent 01"},
  277.  
  278.       {"type":"PLA Translucent",    "name":"Panchroma Yellow",      "hex":"#F9ED3D","code":0,"location":"PLA Translucent 03"},
  279.       {"type":"PLA Translucent",    "name":"SUNLU Transparent",     "hex":"#FFFFFF","code":0,"location":"PLA Translucent 03"},
  280.  
  281.  
  282.       {"type":"PETG Translucent",   "name":"Clear",         "hex":"#F0F2F5","code":32101,"location":"PETG Translucent 01"},
  283.       {"type":"PETG Translucent",   "name":"Light Blue",    "hex":"#61B0FF","code":32600,"location":"PETG Translucent 01"},
  284.       {"type":"PETG Translucent",   "name":"Inland Clear",  "hex":"#F0F2F5","code":0,"location":"PETG Translucent 01"}
  285.     ];
  286.     populateTypeFilter();
  287.  
  288.     var currentMatches = [];
  289.     var currentHexColors = [];
  290.  
  291.     // Algorithm descriptions
  292.     const algorithmDescriptions = {
  293.       euclidean: "Simple RGB distance - fast but not perceptually accurate",
  294.       deltaE76: "CIE Lab Delta E 76 - perceptually uniform, good for most uses",
  295.       deltaE94: "CIE Lab Delta E 94 - improved weighting for lightness/chroma",
  296.       deltaE00: "CIE Lab Delta E 2000 - most perceptually accurate but complex",
  297.       weighted: "Weighted RGB - considers human eye sensitivity to R/G/B differently"
  298.     };
  299.  
  300.     // Update algorithm info when selection changes
  301.     document.getElementById('algorithmSelect').addEventListener('change', function() {
  302.       const info = document.getElementById('algorithmInfo');
  303.       info.textContent = algorithmDescriptions[this.value];
  304.      
  305.       // Re-analyze if we have colors loaded
  306.       if (currentHexColors.length > 0) {
  307.         displayMatches(currentHexColors);
  308.       }
  309.     });
  310.  
  311.     // Update matches when count selection changes
  312.     document.querySelectorAll('input[name="matchCount"]').forEach(radio => {
  313.       radio.addEventListener('change', function() {
  314.         // Re-analyze if we have colors loaded
  315.         if (currentHexColors.length > 0) {
  316.           displayMatches(currentHexColors);
  317.         }
  318.       });
  319.     });
  320.  
  321.     // Update matches when type filter changes
  322.     document.getElementById('typeFilter').addEventListener('change', function() {
  323.       // Re-analyze if we have colors loaded
  324.       if (currentHexColors.length > 0) {
  325.         displayMatches(currentHexColors);
  326.       }
  327.     });
  328.  
  329.     <!-- fetch("bambu-colors.json") -->
  330.       <!-- .then(res => res.json()) -->
  331.       <!-- .then(data => { -->
  332.         <!-- colors = data; -->
  333.         <!-- document.getElementById("colorStatus").innerHTML = `✅ Loaded ${data.length} colors.`; -->
  334.         <!-- populateTypeFilter(); -->
  335.       <!-- }) -->
  336.       <!-- .catch(error => { -->
  337.         <!-- document.getElementById("colorStatus").innerHTML = `❌ Error loading colors: ${error.message}`; -->
  338.         <!-- document.getElementById("colorStatus").style.color = "red"; -->
  339.       <!-- }); -->
  340.  
  341.     function populateTypeFilter() {
  342.       const typeFilter = document.getElementById("typeFilter");
  343.       const uniqueTypes = [...new Set(colors.map(c => c.type))].sort();
  344.       typeFilter.innerHTML = '<option value="">All Types (No Filter)</option>';
  345.       uniqueTypes.forEach(type => {
  346.         const option = document.createElement("option");
  347.         option.value = type;
  348.         option.textContent = type;
  349.         typeFilter.appendChild(option);
  350.       });
  351.     }
  352.  
  353.     function getMatchCount() {
  354.       const checkedRadio = document.querySelector('input[name="matchCount"]:checked');
  355.       return parseInt(checkedRadio.value);
  356.     }
  357.  
  358.     function hexToRgb(hex) {
  359.       const result = hex.replace("#", "").match(/.{1,2}/g);
  360.       return result ? result.map((v) => parseInt(v, 16)) : [0, 0, 0];
  361.     }
  362.  
  363.     // RGB to XYZ conversion (D65 illuminant)
  364.     function rgbToXyz(r, g, b) {
  365.       // Normalize RGB values
  366.       r /= 255; g /= 255; b /= 255;
  367.      
  368.       // Apply gamma correction
  369.       r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
  370.       g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
  371.       b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
  372.      
  373.       // Observer = 2°, Illuminant = D65
  374.       const x = r * 0.4124 + g * 0.3576 + b * 0.1805;
  375.       const y = r * 0.2126 + g * 0.7152 + b * 0.0722;
  376.       const z = r * 0.0193 + g * 0.1192 + b * 0.9505;
  377.      
  378.       return [x * 100, y * 100, z * 100];
  379.     }
  380.  
  381.     // XYZ to LAB conversion
  382.     function xyzToLab(x, y, z) {
  383.       // Reference white D65
  384.       const xn = 95.047, yn = 100.000, zn = 108.883;
  385.      
  386.       x /= xn; y /= yn; z /= zn;
  387.      
  388.       const fx = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x + 16/116);
  389.       const fy = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y + 16/116);
  390.       const fz = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z + 16/116);
  391.      
  392.       const l = 116 * fy - 16;
  393.       const a = 500 * (fx - fy);
  394.       const b = 200 * (fy - fz);
  395.      
  396.       return [l, a, b];
  397.     }
  398.  
  399.     // RGB to LAB direct conversion
  400.     function rgbToLab(r, g, b) {
  401.       const [x, y, z] = rgbToXyz(r, g, b);
  402.       return xyzToLab(x, y, z);
  403.     }
  404.  
  405.     // Color difference algorithms
  406.     function getEuclideanDistance(c1, c2) {
  407.       return Math.sqrt((c1[0]-c2[0])**2 + (c1[1]-c2[1])**2 + (c1[2]-c2[2])**2);
  408.     }
  409.  
  410.     function getWeightedRGBDistance(c1, c2) {
  411.       // Human eye is more sensitive to green, less to blue
  412.       const rWeight = 0.3, gWeight = 0.59, bWeight = 0.11;
  413.       return Math.sqrt(
  414.         rWeight * (c1[0]-c2[0])**2 +
  415.         gWeight * (c1[1]-c2[1])**2 +
  416.         bWeight * (c1[2]-c2[2])**2
  417.       );
  418.     }
  419.  
  420.     function getDeltaE76(lab1, lab2) {
  421.       const dL = lab1[0] - lab2[0];
  422.       const da = lab1[1] - lab2[1];
  423.       const db = lab1[2] - lab2[2];
  424.       return Math.sqrt(dL*dL + da*da + db*db);
  425.     }
  426.  
  427.     function getDeltaE94(lab1, lab2) {
  428.       const dL = lab1[0] - lab2[0];
  429.       const da = lab1[1] - lab2[1];
  430.       const db = lab1[2] - lab2[2];
  431.      
  432.       const c1 = Math.sqrt(lab1[1]*lab1[1] + lab1[2]*lab1[2]);
  433.       const c2 = Math.sqrt(lab2[1]*lab2[1] + lab2[2]*lab2[2]);
  434.       const dC = c1 - c2;
  435.       const dH = Math.sqrt(da*da + db*db - dC*dC);
  436.      
  437.       const sL = 1;
  438.       const sC = 1 + 0.045 * c1;
  439.       const sH = 1 + 0.015 * c1;
  440.      
  441.       return Math.sqrt((dL/sL)**2 + (dC/sC)**2 + (dH/sH)**2);
  442.     }
  443.  
  444.     function getDeltaE00(lab1, lab2) {
  445.       // Simplified Delta E 2000 - full implementation is very complex
  446.       const dL = lab1[0] - lab2[0];
  447.       const da = lab1[1] - lab2[1];
  448.       const db = lab1[2] - lab2[2];
  449.      
  450.       const c1 = Math.sqrt(lab1[1]*lab1[1] + lab1[2]*lab1[2]);
  451.       const c2 = Math.sqrt(lab2[1]*lab2[1] + lab2[2]*lab2[2]);
  452.       const cBar = (c1 + c2) / 2;
  453.      
  454.       const g = 0.5 * (1 - Math.sqrt(Math.pow(cBar, 7) / (Math.pow(cBar, 7) + Math.pow(25, 7))));
  455.      
  456.       const a1p = lab1[1] * (1 + g);
  457.       const a2p = lab2[1] * (1 + g);
  458.      
  459.       const c1p = Math.sqrt(a1p*a1p + lab1[2]*lab1[2]);
  460.       const c2p = Math.sqrt(a2p*a2p + lab2[2]*lab2[2]);
  461.       const cBarP = (c1p + c2p) / 2;
  462.      
  463.       const dCp = c2p - c1p;
  464.       const dap = a2p - a1p;
  465.       const dbp = lab2[2] - lab1[2];
  466.       const dHp = Math.sqrt(dap*dap + dbp*dbp - dCp*dCp);
  467.      
  468.       const sL = 1 + (0.015 * Math.pow(lab1[0] - 50, 2)) / Math.sqrt(20 + Math.pow(lab1[0] - 50, 2));
  469.       const sC = 1 + 0.045 * cBarP;
  470.       const sH = 1 + 0.015 * cBarP;
  471.      
  472.       return Math.sqrt((dL/sL)**2 + (dCp/sC)**2 + (dHp/sH)**2);
  473.     }
  474.  
  475.     function getColorDistance(c1, c2, algorithm) {
  476.       switch(algorithm) {
  477.         case 'euclidean':
  478.           return getEuclideanDistance(c1, c2);
  479.         case 'weighted':
  480.           return getWeightedRGBDistance(c1, c2);
  481.         case 'deltaE76':
  482.           const lab1_76 = rgbToLab(c1[0], c1[1], c1[2]);
  483.           const lab2_76 = rgbToLab(c2[0], c2[1], c2[2]);
  484.           return getDeltaE76(lab1_76, lab2_76);
  485.         case 'deltaE94':
  486.           const lab1_94 = rgbToLab(c1[0], c1[1], c1[2]);
  487.           const lab2_94 = rgbToLab(c2[0], c2[1], c2[2]);
  488.           return getDeltaE94(lab1_94, lab2_94);
  489.         case 'deltaE00':
  490.           const lab1_00 = rgbToLab(c1[0], c1[1], c1[2]);
  491.           const lab2_00 = rgbToLab(c2[0], c2[1], c2[2]);
  492.           return getDeltaE00(lab1_00, lab2_00);
  493.         default:
  494.           return getEuclideanDistance(c1, c2);
  495.       }
  496.     }
  497.  
  498.     async function analyze3MF() {
  499.       if (colors.length === 0) {
  500.         alert("Color data is not loaded yet. Please try again in a moment.");
  501.         return;
  502.       }
  503.       window.uploadedFileName = undefined;
  504.       const fileInput = document.getElementById("fileInput");
  505.       const file = fileInput.files[0];
  506.       window.uploadedFileName = file.name;
  507.       if (!file) return alert("Please select a .3mf file.");
  508.  
  509.       try {
  510.         const zip = await JSZip.loadAsync(file);
  511.         const configFile = Object.keys(zip.files).find(name => name.toLowerCase().endsWith("project_settings.config"));
  512.         if (!configFile) return alert("No project_settings.config found in .3mf");
  513.  
  514.         const configText = await zip.files[configFile].async("string");
  515.        
  516.         const filamentColorMatch = configText.match(/"filament_colour":\s*\[([\s\S]*?)\]/);
  517.         let colorHexes = [];
  518.        
  519.         if (filamentColorMatch) {
  520.           colorHexes = [...new Set((filamentColorMatch[1].match(/#[A-Fa-f0-9]{6}/g) || []).map(c => c.toUpperCase()))];
  521.         } else {
  522.           colorHexes = [...new Set((configText.match(/#[A-Fa-f0-9]{6}/g) || []).map(c => c.toUpperCase()))].slice(0, 16);
  523.         }
  524.  
  525.         if (colorHexes.length === 0) {
  526.           alert("No hex color codes found.");
  527.           return;
  528.         }
  529.  
  530.         currentHexColors = colorHexes;
  531.         displayMatches(colorHexes);
  532.         document.getElementById("selectionSection").style.display = "block";
  533.         document.getElementById("summary").style.display = "none";
  534.       } catch (error) {
  535.         alert("Error processing .3MF file: " + error.message);
  536.       }
  537.     }
  538.  
  539.     function displayMatches(hexColors) {
  540.       const resultsDiv = document.getElementById("results");
  541.       const typeSelect = document.getElementById("typeFilter");
  542.       const selectedTypes = Array.from(typeSelect.selectedOptions).map(opt => opt.value).filter(v => v !== "");
  543.       const algorithm = document.getElementById('algorithmSelect').value;
  544.       const matchCount = getMatchCount();
  545.       resultsDiv.innerHTML = "";
  546.       currentMatches = [];
  547.  
  548.       const filteredColors = selectedTypes.length === 0 ? colors : colors.filter(c => selectedTypes.includes(c.type));
  549.         const filterStatus = selectedTypes.length > 0
  550.           ? ` (filtered to ${selectedTypes.join(", ")} - ${filteredColors.length} colors)`
  551.           : ` (all types - ${filteredColors.length} colors)`;
  552.      
  553.       hexColors.forEach((hex, i) => {
  554.         const rgb = hexToRgb(hex);
  555.        
  556.         const colorDistances = filteredColors.filter(c => c.hex).map(c => {
  557.           const targetRgb = hexToRgb(c.hex);
  558.           return {
  559.             ...c,
  560.             distance: getColorDistance(rgb, targetRgb, algorithm)
  561.           };
  562.         }).sort((a, b) => a.distance - b.distance).slice(0, matchCount);
  563.  
  564.         const group = document.createElement("div");
  565.         group.className = "match-group";
  566.         group.innerHTML = `<h3>Input Color ${i+1}: <span class='color-box' style='background:${hex}'></span> ${hex}${filterStatus} [${algorithm}] - Top ${matchCount}</h3>`;
  567.  
  568.         if (colorDistances.length === 0) {
  569.           group.innerHTML += `<p style="color: #666; font-style: italic;">No matches found for selected filament type.</p>`;
  570.         } else {
  571.           colorDistances.forEach((match, index) => {
  572.             const item = document.createElement("div");
  573.             item.className = "match-item";
  574.            
  575.             // Different thresholds for different algorithms
  576.             let isExactMatch = false;
  577.             if (algorithm === 'euclidean' || algorithm === 'weighted') {
  578.               isExactMatch = match.distance < 5;
  579.            } else {
  580.              isExactMatch = match.distance < 3; // Delta E < 3 is generally considered a good match
  581.            }
  582.            
  583.            const exactMatchLabel = isExactMatch ? " 🎯 <strong style='color: #00AA00;'>EXCELLENT MATCH!</strong>" : "";
  584.            
  585.             if (isExactMatch) {
  586.               item.style.borderLeft = "4px solid #00AA00";
  587.               item.style.backgroundColor = "#f0fff0";
  588.             }
  589.            
  590.             const radioId = `color${i}_match${index}`;
  591.            
  592.             item.innerHTML = `
  593.               <input type="radio" name="color${i}" id="${radioId}" value="${index}" onchange="updateSelection(${i}, ${index})" ${index === 0 ? 'checked' : ''}>
  594.               <strong>${index + 1}.</strong>
  595.               <span class='match-color-box' style='background:${match.hex}'></span>
  596.               ${match.name} (${match.hex})${exactMatchLabel}<br>
  597.               <strong>Type:</strong> ${match.type}<br>
  598.               <strong>Code:</strong> ${match.code}<br>
  599.               <strong>Location:</strong> ${match.location}<br>
  600.               <span class="distance-info"><strong>Distance:</strong> ${match.distance.toFixed(2)} (${algorithm})</span>
  601.             `;
  602.            
  603.             if (index === 0) {
  604.               item.classList.add('selected');
  605.             }
  606.            
  607.             group.appendChild(item);
  608.           });
  609.         }
  610.  
  611.         resultsDiv.appendChild(group);
  612.        
  613.         currentMatches[i] = {
  614.           inputHex: hex,
  615.           inputIndex: i,
  616.           matches: colorDistances,
  617.           selectedIndex: 0
  618.         };
  619.       });
  620.     }
  621.  
  622.     function updateSelection(colorIndex, matchIndex) {
  623.       currentMatches[colorIndex].selectedIndex = matchIndex;
  624.      
  625.       const colorGroup = document.querySelectorAll('.match-group')[colorIndex];
  626.       const matchItems = colorGroup.querySelectorAll('.match-item');
  627.      
  628.       matchItems.forEach((item, idx) => {
  629.         if (idx === matchIndex) {
  630.           item.classList.add('selected');
  631.         } else {
  632.           item.classList.remove('selected');
  633.         }
  634.       });
  635.     }
  636.  
  637.     function generateSummary() {
  638.       const summaryContent = document.getElementById("summaryContent");
  639.       const algorithm = document.getElementById('algorithmSelect').value;
  640.       const matchCount = getMatchCount();
  641.       //let summaryHTML = "";
  642.       //let textSummary = "Selected Filament Colors Summary\n" + "=".repeat(35) + "\n\n";
  643.       let fileName = window.uploadedFileName || "Unknown File";
  644.       let summaryHTML = `<p><strong>File:</strong> ${fileName}</p>`;
  645.       let textSummary = `Selected Filament Colors Summary\n` +
  646.         `File: ${fileName}\n` +
  647.          "=".repeat(35) + "\n\n";
  648.  
  649.      
  650.       currentMatches.forEach((colorData, i) => {
  651.         const selectedMatch = colorData.matches[colorData.selectedIndex];
  652.         let isExactMatch = false;
  653.         if (algorithm === 'euclidean' || algorithm === 'weighted') {
  654.           isExactMatch = selectedMatch.distance < 5;
  655.        } else {
  656.          isExactMatch = selectedMatch.distance < 3;
  657.        }
  658.        
  659.        summaryHTML += `
  660.          <div class="summary-item">
  661.             <strong>Color ${i+1}:</strong>
  662.             <span class='match-color-box print-color-box' style='background:${selectedMatch.hex} !important; border: 3px solid ${selectedMatch.hex} !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; color-adjust: exact;'></span>
  663.             ${selectedMatch.name} (${selectedMatch.hex})${isExactMatch ? " 🎯 EXCELLENT MATCH" : ""}<br>
  664.             <strong>Type:</strong> ${selectedMatch.type} | <strong>Code:</strong> ${selectedMatch.code}<br>
  665.             <strong>Location:</strong> ${selectedMatch.location}<br>
  666.             <strong>Distance:</strong> ${selectedMatch.distance.toFixed(2)} (${algorithm}, Top ${matchCount})<br>
  667.             <strong>Color Sample:</strong> <span style='padding: 2px 8px; background: ${selectedMatch.hex}; color: white; border: 2px solid ${selectedMatch.hex}; -webkit-print-color-adjust: exact; print-color-adjust: exact;'>${selectedMatch.hex}</span>
  668.           </div>
  669.         `;
  670.        
  671.         textSummary += `Color ${i+1}: ${selectedMatch.name} (${selectedMatch.hex})${isExactMatch ? " 🎯 EXCELLENT MATCH" : ""}\n`;
  672.         textSummary += `Type: ${selectedMatch.type} | Code: ${selectedMatch.code}\n`;
  673.         textSummary += `Location: ${selectedMatch.location}\n`;
  674.         textSummary += `Distance: ${selectedMatch.distance.toFixed(2)} (${algorithm}, Top ${matchCount})\n\n`;
  675.       });
  676.      
  677.       summaryContent.innerHTML = summaryHTML;
  678.       document.getElementById("summary").style.display = "block";
  679.       window.currentTextSummary = textSummary;
  680.       document.getElementById("summary").scrollIntoView({ behavior: 'smooth' });
  681.     }
  682.  
  683.     function copyToClipboard() {
  684.       if (window.currentTextSummary) {
  685.         navigator.clipboard.writeText(window.currentTextSummary).then(() => {
  686.           const button = document.getElementById("copySummary");
  687.           const originalText = button.textContent;
  688.           button.textContent = "Copied!";
  689.           button.style.background = "#28a745";
  690.           setTimeout(() => {
  691.             button.textContent = originalText;
  692.             button.style.background = "#6c757d";
  693.           }, 2000);
  694.         }).catch(err => {
  695.           const textArea = document.createElement("textarea");
  696.           textArea.value = window.currentTextSummary;
  697.           document.body.appendChild(textArea);
  698.           textArea.select();
  699.           document.execCommand('copy');
  700.           document.body.removeChild(textArea);
  701.           alert("Summary copied to clipboard!");
  702.         });
  703.       }
  704.     }
  705.  
  706.     // Drag and drop functionality
  707.     const dropZone = document.getElementById("dropZone");
  708.     const fileInput = document.getElementById("fileInput");
  709.  
  710.     dropZone.addEventListener("dragover", e => {
  711.       e.preventDefault();
  712.       dropZone.style.background = "#eef";
  713.     });
  714.  
  715.     dropZone.addEventListener("dragleave", () => {
  716.       dropZone.style.background = "#f9f9f9";
  717.     });
  718.  
  719.     dropZone.addEventListener("drop", e => {
  720.       e.preventDefault();
  721.       dropZone.style.background = "#f9f9f9";
  722.       const file = e.dataTransfer.files[0];
  723.       if (file && file.name.endsWith(".3mf")) {
  724.        fileInput.files = e.dataTransfer.files;
  725.       } else {
  726.         alert("Only .3mf files are supported.");
  727.       }
  728.     });
  729.   </script>
  730. </body>
  731. </html>
Advertisement
Add Comment
Please, Sign In to add comment