Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <title>Bambu Lab .3MF Color Analyzer - Enhanced Matching</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
- <style>
- body {
- font-family: sans-serif;
- background: #f4f4f4;
- padding: 2em;
- color: #333;
- }
- h1 {
- text-align: center;
- }
- .file-section, .results {
- background: white;
- border: 1px solid #ccc;
- border-radius: 8px;
- padding: 1em;
- margin-bottom: 2em;
- }
- .match-item {
- border-left: 4px solid #888;
- padding: 10px;
- margin-bottom: 10px;
- background: #f9f9f9;
- border-radius: 4px;
- position: relative;
- }
- .match-item input[type="radio"] {
- position: absolute;
- top: 10px;
- right: 10px;
- transform: scale(1.5);
- }
- .match-item.selected {
- border-left: 4px solid #0066cc;
- background: #e6f3ff;
- }
- .color-box {
- width: 30px;
- height: 30px;
- display: inline-block;
- border: 1px solid #000;
- vertical-align: middle;
- margin-right: 10px;
- }
- .match-group {
- margin-bottom: 2em;
- }
- .match-color-box {
- width: 20px;
- height: 20px;
- display: inline-block;
- border: 1px solid #000;
- vertical-align: middle;
- margin-right: 5px;
- }
- #generateSummary {
- background: #0066cc;
- color: white;
- padding: 10px 20px;
- border: none;
- border-radius: 5px;
- font-size: 16px;
- cursor: pointer;
- margin: 20px 0;
- }
- #generateSummary:hover {
- background: #0052a3;
- }
- #generateSummary:disabled {
- background: #ccc;
- cursor: not-allowed;
- }
- #summary {
- background: white;
- border: 1px solid #ccc;
- border-radius: 8px;
- padding: 1em;
- margin-top: 2em;
- display: none;
- }
- .summary-item {
- margin-bottom: 15px;
- padding: 10px;
- border-left: 4px solid #0066cc;
- background: #f9f9f9;
- }
- #printSummary {
- background: #28a745;
- color: white;
- padding: 8px 16px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- margin-right: 10px;
- }
- #copySummary {
- background: #6c757d;
- color: white;
- padding: 8px 16px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- }
- @media print {
- body * {
- visibility: hidden;
- }
- #summary, #summary * {
- visibility: visible;
- }
- #summary {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- }
- #printSummary, #copySummary {
- display: none;
- }
- .print-color-box {
- -webkit-print-color-adjust: exact !important;
- print-color-adjust: exact !important;
- color-adjust: exact !important;
- }
- }
- #dropZone {
- border: 2px dashed #aaa;
- border-radius: 8px;
- padding: 20px;
- text-align: center;
- color: #666;
- margin: 1em 0;
- background: #f9f9f9;
- }
- .algorithm-selector {
- margin: 10px 0;
- padding: 10px;
- background: #f0f8ff;
- border-radius: 5px;
- }
- .algorithm-info {
- font-size: 12px;
- color: #666;
- margin-top: 5px;
- }
- .distance-info {
- font-size: 11px;
- color: #888;
- }
- .test-section {
- background: #fff3cd;
- border: 1px solid #ffeaa7;
- border-radius: 8px;
- padding: 1em;
- margin-bottom: 2em;
- }
- .matches-selector {
- margin: 10px 0;
- padding: 10px;
- background: #fff8e1;
- border-radius: 5px;
- border: 1px solid #ffcc02;
- }
- .matches-selector label {
- font-weight: bold;
- margin-right: 15px;
- }
- .matches-selector input[type="radio"] {
- margin-right: 5px;
- margin-left: 15px;
- }
- .matches-info {
- font-size: 12px;
- color: #666;
- margin-top: 5px;
- }
- </style>
- </head>
- <body>
- <h1>Bambu Lab .3MF Color Analyzer - Enhanced Matching</h1>
- <div class="file-section">
- <div id="dropZone">
- <strong>Drag and drop your .3mf file here</strong><br>or use the browse button below.
- </div>
- <input type="file" id="fileInput" accept=".3mf" />
- <br><br>
- <div class="algorithm-selector">
- <label for="algorithmSelect"><strong>Color Matching Algorithm:</strong></label>
- <select id="algorithmSelect">
- <option value="euclidean">Euclidean RGB (Original)</option>
- <option value="deltaE76">Delta E 76 (CIE Lab)</option>
- <option value="deltaE94">Delta E 94 (Improved)</option>
- <option value="deltaE00" selected>Delta E 2000 (Best)</option>
- <option value="weighted">Weighted RGB</option>
- </select>
- <div class="algorithm-info" id="algorithmInfo">
- Delta E 2000 - most perceptually accurate but complex
- </div>
- </div>
- <div class="matches-selector">
- <label><strong>Number of Matches to Show:</strong></label>
- <input type="radio" id="matches3" name="matchCount" value="3" checked>
- <label for="matches3">Top 3 Matches</label>
- <input type="radio" id="matches5" name="matchCount" value="5">
- <label for="matches5">Top 5 Matches</label>
- <div class="matches-info">
- Choose how many color matches to display for each input color
- </div>
- </div>
- <label for="typeFilter"><strong>Filter by Filament Type:</strong></label>
- <select id="typeFilter" multiple size="6">
- <option value="">All Types (No Filter)</option>
- </select>
- <br><br>
- <button onclick="analyze3MF()">Analyze .3MF File</button>
- </div>
- <div id="colorStatus" style="margin-bottom: 1em; font-weight: bold; color: green;"></div>
- <div id="results" class="results"></div>
- <div id="selectionSection" style="display: none;">
- <button id="generateSummary" onclick="generateSummary()">Generate Summary for Selected Colors</button>
- <p style="color: #666; font-size: 14px;">Select one match for each color above, then click to generate a printable summary.</p>
- </div>
- <div id="summary">
- <h2>Selected Filament Colors Summary</h2>
- <div style="margin-bottom: 15px;">
- <button id="printSummary" onclick="window.print()">Print Summary</button>
- <button id="copySummary" onclick="copyToClipboard()">Copy to Clipboard</button>
- </div>
- <div id="summaryContent"></div>
- </div>
- <script>
- const colors = [
- {"type":"PLA Basic", "name":"Jade White", "hex":"#FFFFFF","code":10001,"location":"PLA 04"},
- {"type":"PLA Basic", "name":"Voxel Army Green","hex":"#667B65","code":0,"location":"PLA 01"},
- {"type":"PLA Silk+", "name":"Gold", "hex":"#F4A925","code":13405,"location":"PLA Silk 01"},
- {"type":"PLA Silk+", "name":"White", "hex":"#FFFFFF","code":13110,"location":"PLA Silk 02"},
- {"type":"PLA Matte", "name":"Ivory White", "hex":"#FFFFFF","code":11100,"location":"PLA Matte 04"},
- {"type":"PLA Matte", "name":"Inland Wood Brown","hex":"#D2B48C","code":0,"location":"PLA Matte 02"},
- {"type":"ABS", "name":"Silver", "hex":"#87909A","code":40102,"location":"ABS 02"},
- {"type":"ABS", "name":"Azure", "hex":"#C00D1E","code":40601,"location":"ABS 03"},
- {"type":"ABS-GF", "name":"Black", "hex":"#000000","code":41101,"location":"ABS XX"},
- {"type":"ABS-GF", "name":"Gray", "hex":"#C6C6C6","code":41102,"location":"ABS XX"},
- {"type":"ASA", "name":"White", "hex":"#FFFAF2","code":45100,"location":"ASA XX"},
- {"type":"ASA", "name":"Gray", "hex":"#8A949E","code":45102,"location":"ASA XX"},
- {"type":"ASA", "name":"Red", "hex":"#E02928","code":45200,"location":"ASA XX"},
- {"type":"ASA", "name":"Green", "hex":"#00A6A0","code":45500,"location":"ASA XX"},
- {"type":"ASA", "name":"Blue", "hex":"#2140B4","code":45600,"location":"ASA XX"},
- {"type":"ASA", "name":"Black", "hex":"#000000","code":45101,"location":"ASA XX"},
- {"type":"PETG HF", "name":"Blue", "hex":"#002E96","code":33600,"location":"PETG HF 01"},
- {"type":"PETG HF", "name":"Black", "hex":"#000000","code":33102,"location":"PETG HF 03;Cart"},
- {"type":"PLA Marble", "name":"White Marble", "hex":"#F7F3F0","code":13103,"location":"PLA 02"},
- {"type":"PLA Marble", "name":"Red Granite", "hex":"#AD4E38","code":13201,"location":"PLA 02"},
- {"type":"PLA Translucent", "name":"Cherry Pink", "hex":"#F5B6CD","code":13211,"location":"PLA Translucent 02"},
- {"type":"PLA Translucent", "name":"Red", "hex":"#B50011","code":13210,"location":"PLA Translucent 01"},
- {"type":"PLA Translucent", "name":"Panchroma Yellow", "hex":"#F9ED3D","code":0,"location":"PLA Translucent 03"},
- {"type":"PLA Translucent", "name":"SUNLU Transparent", "hex":"#FFFFFF","code":0,"location":"PLA Translucent 03"},
- {"type":"PETG Translucent", "name":"Clear", "hex":"#F0F2F5","code":32101,"location":"PETG Translucent 01"},
- {"type":"PETG Translucent", "name":"Light Blue", "hex":"#61B0FF","code":32600,"location":"PETG Translucent 01"},
- {"type":"PETG Translucent", "name":"Inland Clear", "hex":"#F0F2F5","code":0,"location":"PETG Translucent 01"}
- ];
- populateTypeFilter();
- var currentMatches = [];
- var currentHexColors = [];
- // Algorithm descriptions
- const algorithmDescriptions = {
- euclidean: "Simple RGB distance - fast but not perceptually accurate",
- deltaE76: "CIE Lab Delta E 76 - perceptually uniform, good for most uses",
- deltaE94: "CIE Lab Delta E 94 - improved weighting for lightness/chroma",
- deltaE00: "CIE Lab Delta E 2000 - most perceptually accurate but complex",
- weighted: "Weighted RGB - considers human eye sensitivity to R/G/B differently"
- };
- // Update algorithm info when selection changes
- document.getElementById('algorithmSelect').addEventListener('change', function() {
- const info = document.getElementById('algorithmInfo');
- info.textContent = algorithmDescriptions[this.value];
- // Re-analyze if we have colors loaded
- if (currentHexColors.length > 0) {
- displayMatches(currentHexColors);
- }
- });
- // Update matches when count selection changes
- document.querySelectorAll('input[name="matchCount"]').forEach(radio => {
- radio.addEventListener('change', function() {
- // Re-analyze if we have colors loaded
- if (currentHexColors.length > 0) {
- displayMatches(currentHexColors);
- }
- });
- });
- // Update matches when type filter changes
- document.getElementById('typeFilter').addEventListener('change', function() {
- // Re-analyze if we have colors loaded
- if (currentHexColors.length > 0) {
- displayMatches(currentHexColors);
- }
- });
- <!-- fetch("bambu-colors.json") -->
- <!-- .then(res => res.json()) -->
- <!-- .then(data => { -->
- <!-- colors = data; -->
- <!-- document.getElementById("colorStatus").innerHTML = `✅ Loaded ${data.length} colors.`; -->
- <!-- populateTypeFilter(); -->
- <!-- }) -->
- <!-- .catch(error => { -->
- <!-- document.getElementById("colorStatus").innerHTML = `❌ Error loading colors: ${error.message}`; -->
- <!-- document.getElementById("colorStatus").style.color = "red"; -->
- <!-- }); -->
- function populateTypeFilter() {
- const typeFilter = document.getElementById("typeFilter");
- const uniqueTypes = [...new Set(colors.map(c => c.type))].sort();
- typeFilter.innerHTML = '<option value="">All Types (No Filter)</option>';
- uniqueTypes.forEach(type => {
- const option = document.createElement("option");
- option.value = type;
- option.textContent = type;
- typeFilter.appendChild(option);
- });
- }
- function getMatchCount() {
- const checkedRadio = document.querySelector('input[name="matchCount"]:checked');
- return parseInt(checkedRadio.value);
- }
- function hexToRgb(hex) {
- const result = hex.replace("#", "").match(/.{1,2}/g);
- return result ? result.map((v) => parseInt(v, 16)) : [0, 0, 0];
- }
- // RGB to XYZ conversion (D65 illuminant)
- function rgbToXyz(r, g, b) {
- // Normalize RGB values
- r /= 255; g /= 255; b /= 255;
- // Apply gamma correction
- r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
- g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
- b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
- // Observer = 2°, Illuminant = D65
- const x = r * 0.4124 + g * 0.3576 + b * 0.1805;
- const y = r * 0.2126 + g * 0.7152 + b * 0.0722;
- const z = r * 0.0193 + g * 0.1192 + b * 0.9505;
- return [x * 100, y * 100, z * 100];
- }
- // XYZ to LAB conversion
- function xyzToLab(x, y, z) {
- // Reference white D65
- const xn = 95.047, yn = 100.000, zn = 108.883;
- x /= xn; y /= yn; z /= zn;
- const fx = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x + 16/116);
- const fy = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y + 16/116);
- const fz = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z + 16/116);
- const l = 116 * fy - 16;
- const a = 500 * (fx - fy);
- const b = 200 * (fy - fz);
- return [l, a, b];
- }
- // RGB to LAB direct conversion
- function rgbToLab(r, g, b) {
- const [x, y, z] = rgbToXyz(r, g, b);
- return xyzToLab(x, y, z);
- }
- // Color difference algorithms
- function getEuclideanDistance(c1, c2) {
- return Math.sqrt((c1[0]-c2[0])**2 + (c1[1]-c2[1])**2 + (c1[2]-c2[2])**2);
- }
- function getWeightedRGBDistance(c1, c2) {
- // Human eye is more sensitive to green, less to blue
- const rWeight = 0.3, gWeight = 0.59, bWeight = 0.11;
- return Math.sqrt(
- rWeight * (c1[0]-c2[0])**2 +
- gWeight * (c1[1]-c2[1])**2 +
- bWeight * (c1[2]-c2[2])**2
- );
- }
- function getDeltaE76(lab1, lab2) {
- const dL = lab1[0] - lab2[0];
- const da = lab1[1] - lab2[1];
- const db = lab1[2] - lab2[2];
- return Math.sqrt(dL*dL + da*da + db*db);
- }
- function getDeltaE94(lab1, lab2) {
- const dL = lab1[0] - lab2[0];
- const da = lab1[1] - lab2[1];
- const db = lab1[2] - lab2[2];
- const c1 = Math.sqrt(lab1[1]*lab1[1] + lab1[2]*lab1[2]);
- const c2 = Math.sqrt(lab2[1]*lab2[1] + lab2[2]*lab2[2]);
- const dC = c1 - c2;
- const dH = Math.sqrt(da*da + db*db - dC*dC);
- const sL = 1;
- const sC = 1 + 0.045 * c1;
- const sH = 1 + 0.015 * c1;
- return Math.sqrt((dL/sL)**2 + (dC/sC)**2 + (dH/sH)**2);
- }
- function getDeltaE00(lab1, lab2) {
- // Simplified Delta E 2000 - full implementation is very complex
- const dL = lab1[0] - lab2[0];
- const da = lab1[1] - lab2[1];
- const db = lab1[2] - lab2[2];
- const c1 = Math.sqrt(lab1[1]*lab1[1] + lab1[2]*lab1[2]);
- const c2 = Math.sqrt(lab2[1]*lab2[1] + lab2[2]*lab2[2]);
- const cBar = (c1 + c2) / 2;
- const g = 0.5 * (1 - Math.sqrt(Math.pow(cBar, 7) / (Math.pow(cBar, 7) + Math.pow(25, 7))));
- const a1p = lab1[1] * (1 + g);
- const a2p = lab2[1] * (1 + g);
- const c1p = Math.sqrt(a1p*a1p + lab1[2]*lab1[2]);
- const c2p = Math.sqrt(a2p*a2p + lab2[2]*lab2[2]);
- const cBarP = (c1p + c2p) / 2;
- const dCp = c2p - c1p;
- const dap = a2p - a1p;
- const dbp = lab2[2] - lab1[2];
- const dHp = Math.sqrt(dap*dap + dbp*dbp - dCp*dCp);
- const sL = 1 + (0.015 * Math.pow(lab1[0] - 50, 2)) / Math.sqrt(20 + Math.pow(lab1[0] - 50, 2));
- const sC = 1 + 0.045 * cBarP;
- const sH = 1 + 0.015 * cBarP;
- return Math.sqrt((dL/sL)**2 + (dCp/sC)**2 + (dHp/sH)**2);
- }
- function getColorDistance(c1, c2, algorithm) {
- switch(algorithm) {
- case 'euclidean':
- return getEuclideanDistance(c1, c2);
- case 'weighted':
- return getWeightedRGBDistance(c1, c2);
- case 'deltaE76':
- const lab1_76 = rgbToLab(c1[0], c1[1], c1[2]);
- const lab2_76 = rgbToLab(c2[0], c2[1], c2[2]);
- return getDeltaE76(lab1_76, lab2_76);
- case 'deltaE94':
- const lab1_94 = rgbToLab(c1[0], c1[1], c1[2]);
- const lab2_94 = rgbToLab(c2[0], c2[1], c2[2]);
- return getDeltaE94(lab1_94, lab2_94);
- case 'deltaE00':
- const lab1_00 = rgbToLab(c1[0], c1[1], c1[2]);
- const lab2_00 = rgbToLab(c2[0], c2[1], c2[2]);
- return getDeltaE00(lab1_00, lab2_00);
- default:
- return getEuclideanDistance(c1, c2);
- }
- }
- async function analyze3MF() {
- if (colors.length === 0) {
- alert("Color data is not loaded yet. Please try again in a moment.");
- return;
- }
- window.uploadedFileName = undefined;
- const fileInput = document.getElementById("fileInput");
- const file = fileInput.files[0];
- window.uploadedFileName = file.name;
- if (!file) return alert("Please select a .3mf file.");
- try {
- const zip = await JSZip.loadAsync(file);
- const configFile = Object.keys(zip.files).find(name => name.toLowerCase().endsWith("project_settings.config"));
- if (!configFile) return alert("No project_settings.config found in .3mf");
- const configText = await zip.files[configFile].async("string");
- const filamentColorMatch = configText.match(/"filament_colour":\s*\[([\s\S]*?)\]/);
- let colorHexes = [];
- if (filamentColorMatch) {
- colorHexes = [...new Set((filamentColorMatch[1].match(/#[A-Fa-f0-9]{6}/g) || []).map(c => c.toUpperCase()))];
- } else {
- colorHexes = [...new Set((configText.match(/#[A-Fa-f0-9]{6}/g) || []).map(c => c.toUpperCase()))].slice(0, 16);
- }
- if (colorHexes.length === 0) {
- alert("No hex color codes found.");
- return;
- }
- currentHexColors = colorHexes;
- displayMatches(colorHexes);
- document.getElementById("selectionSection").style.display = "block";
- document.getElementById("summary").style.display = "none";
- } catch (error) {
- alert("Error processing .3MF file: " + error.message);
- }
- }
- function displayMatches(hexColors) {
- const resultsDiv = document.getElementById("results");
- const typeSelect = document.getElementById("typeFilter");
- const selectedTypes = Array.from(typeSelect.selectedOptions).map(opt => opt.value).filter(v => v !== "");
- const algorithm = document.getElementById('algorithmSelect').value;
- const matchCount = getMatchCount();
- resultsDiv.innerHTML = "";
- currentMatches = [];
- const filteredColors = selectedTypes.length === 0 ? colors : colors.filter(c => selectedTypes.includes(c.type));
- const filterStatus = selectedTypes.length > 0
- ? ` (filtered to ${selectedTypes.join(", ")} - ${filteredColors.length} colors)`
- : ` (all types - ${filteredColors.length} colors)`;
- hexColors.forEach((hex, i) => {
- const rgb = hexToRgb(hex);
- const colorDistances = filteredColors.filter(c => c.hex).map(c => {
- const targetRgb = hexToRgb(c.hex);
- return {
- ...c,
- distance: getColorDistance(rgb, targetRgb, algorithm)
- };
- }).sort((a, b) => a.distance - b.distance).slice(0, matchCount);
- const group = document.createElement("div");
- group.className = "match-group";
- group.innerHTML = `<h3>Input Color ${i+1}: <span class='color-box' style='background:${hex}'></span> ${hex}${filterStatus} [${algorithm}] - Top ${matchCount}</h3>`;
- if (colorDistances.length === 0) {
- group.innerHTML += `<p style="color: #666; font-style: italic;">No matches found for selected filament type.</p>`;
- } else {
- colorDistances.forEach((match, index) => {
- const item = document.createElement("div");
- item.className = "match-item";
- // Different thresholds for different algorithms
- let isExactMatch = false;
- if (algorithm === 'euclidean' || algorithm === 'weighted') {
- isExactMatch = match.distance < 5;
- } else {
- isExactMatch = match.distance < 3; // Delta E < 3 is generally considered a good match
- }
- const exactMatchLabel = isExactMatch ? " 🎯 <strong style='color: #00AA00;'>EXCELLENT MATCH!</strong>" : "";
- if (isExactMatch) {
- item.style.borderLeft = "4px solid #00AA00";
- item.style.backgroundColor = "#f0fff0";
- }
- const radioId = `color${i}_match${index}`;
- item.innerHTML = `
- <input type="radio" name="color${i}" id="${radioId}" value="${index}" onchange="updateSelection(${i}, ${index})" ${index === 0 ? 'checked' : ''}>
- <strong>${index + 1}.</strong>
- <span class='match-color-box' style='background:${match.hex}'></span>
- ${match.name} (${match.hex})${exactMatchLabel}<br>
- <strong>Type:</strong> ${match.type}<br>
- <strong>Code:</strong> ${match.code}<br>
- <strong>Location:</strong> ${match.location}<br>
- <span class="distance-info"><strong>Distance:</strong> ${match.distance.toFixed(2)} (${algorithm})</span>
- `;
- if (index === 0) {
- item.classList.add('selected');
- }
- group.appendChild(item);
- });
- }
- resultsDiv.appendChild(group);
- currentMatches[i] = {
- inputHex: hex,
- inputIndex: i,
- matches: colorDistances,
- selectedIndex: 0
- };
- });
- }
- function updateSelection(colorIndex, matchIndex) {
- currentMatches[colorIndex].selectedIndex = matchIndex;
- const colorGroup = document.querySelectorAll('.match-group')[colorIndex];
- const matchItems = colorGroup.querySelectorAll('.match-item');
- matchItems.forEach((item, idx) => {
- if (idx === matchIndex) {
- item.classList.add('selected');
- } else {
- item.classList.remove('selected');
- }
- });
- }
- function generateSummary() {
- const summaryContent = document.getElementById("summaryContent");
- const algorithm = document.getElementById('algorithmSelect').value;
- const matchCount = getMatchCount();
- //let summaryHTML = "";
- //let textSummary = "Selected Filament Colors Summary\n" + "=".repeat(35) + "\n\n";
- let fileName = window.uploadedFileName || "Unknown File";
- let summaryHTML = `<p><strong>File:</strong> ${fileName}</p>`;
- let textSummary = `Selected Filament Colors Summary\n` +
- `File: ${fileName}\n` +
- "=".repeat(35) + "\n\n";
- currentMatches.forEach((colorData, i) => {
- const selectedMatch = colorData.matches[colorData.selectedIndex];
- let isExactMatch = false;
- if (algorithm === 'euclidean' || algorithm === 'weighted') {
- isExactMatch = selectedMatch.distance < 5;
- } else {
- isExactMatch = selectedMatch.distance < 3;
- }
- summaryHTML += `
- <div class="summary-item">
- <strong>Color ${i+1}:</strong>
- <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>
- ${selectedMatch.name} (${selectedMatch.hex})${isExactMatch ? " 🎯 EXCELLENT MATCH" : ""}<br>
- <strong>Type:</strong> ${selectedMatch.type} | <strong>Code:</strong> ${selectedMatch.code}<br>
- <strong>Location:</strong> ${selectedMatch.location}<br>
- <strong>Distance:</strong> ${selectedMatch.distance.toFixed(2)} (${algorithm}, Top ${matchCount})<br>
- <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>
- </div>
- `;
- textSummary += `Color ${i+1}: ${selectedMatch.name} (${selectedMatch.hex})${isExactMatch ? " 🎯 EXCELLENT MATCH" : ""}\n`;
- textSummary += `Type: ${selectedMatch.type} | Code: ${selectedMatch.code}\n`;
- textSummary += `Location: ${selectedMatch.location}\n`;
- textSummary += `Distance: ${selectedMatch.distance.toFixed(2)} (${algorithm}, Top ${matchCount})\n\n`;
- });
- summaryContent.innerHTML = summaryHTML;
- document.getElementById("summary").style.display = "block";
- window.currentTextSummary = textSummary;
- document.getElementById("summary").scrollIntoView({ behavior: 'smooth' });
- }
- function copyToClipboard() {
- if (window.currentTextSummary) {
- navigator.clipboard.writeText(window.currentTextSummary).then(() => {
- const button = document.getElementById("copySummary");
- const originalText = button.textContent;
- button.textContent = "Copied!";
- button.style.background = "#28a745";
- setTimeout(() => {
- button.textContent = originalText;
- button.style.background = "#6c757d";
- }, 2000);
- }).catch(err => {
- const textArea = document.createElement("textarea");
- textArea.value = window.currentTextSummary;
- document.body.appendChild(textArea);
- textArea.select();
- document.execCommand('copy');
- document.body.removeChild(textArea);
- alert("Summary copied to clipboard!");
- });
- }
- }
- // Drag and drop functionality
- const dropZone = document.getElementById("dropZone");
- const fileInput = document.getElementById("fileInput");
- dropZone.addEventListener("dragover", e => {
- e.preventDefault();
- dropZone.style.background = "#eef";
- });
- dropZone.addEventListener("dragleave", () => {
- dropZone.style.background = "#f9f9f9";
- });
- dropZone.addEventListener("drop", e => {
- e.preventDefault();
- dropZone.style.background = "#f9f9f9";
- const file = e.dataTransfer.files[0];
- if (file && file.name.endsWith(".3mf")) {
- fileInput.files = e.dataTransfer.files;
- } else {
- alert("Only .3mf files are supported.");
- }
- });
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment