diff --git a/src/OpenPrintTagWriterHTML.h b/src/OpenPrintTagWriterHTML.h index ec55c5e..81a0368 100644 --- a/src/OpenPrintTagWriterHTML.h +++ b/src/OpenPrintTagWriterHTML.h @@ -46,32 +46,34 @@ const char OPENPRINTTAG_WRITER_HTML[] PROGMEM = R"rawliteral(

Basic

- - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -268,6 +270,8 @@ const char OPENPRINTTAG_WRITER_HTML[] PROGMEM = R"rawliteral( var writerForm = document.getElementById('writerForm'); var materialTypeEl = document.getElementById('material_type'); + var materialSearchEl = document.getElementById('material_search'); + var materialListEl = document.getElementById('material-list'); var materialNameEl = document.getElementById('material_name'); var backBtn = document.getElementById('backBtn'); @@ -278,6 +282,44 @@ const char OPENPRINTTAG_WRITER_HTML[] PROGMEM = R"rawliteral( syncColorPicker('colorPicker', 'colorHex'); setupAdvancedToggle('advancedToggle', 'advancedBox'); + // Sync material search → hidden material_type ID + function syncMaterialId() { + var opts = materialListEl.querySelectorAll('option'); + materialTypeEl.value = '0'; // default PLA + for (var i = 0; i < opts.length; i++) { + if (opts[i].value === materialSearchEl.value) { + materialTypeEl.value = opts[i].dataset.id; + break; + } + } + } + + // Auto-fill temps and density from material selection + var optFieldMap = { + minPrintTemp: 'min_print_temp', maxPrintTemp: 'max_print_temp', + minBedTemp: 'min_bed_temp', maxBedTemp: 'max_bed_temp', + density: 'density' + }; + trackAutoFill(['min_print_temp','max_print_temp','min_bed_temp','max_bed_temp','density']); + materialSearchEl.addEventListener('input', function() { + syncMaterialId(); + autoFillMaterialData(materialSearchEl.value, optFieldMap); + }); + loadMaterialDb().then(function(db) { + // Expand datalist with API materials that aren't in the hardcoded list + var existing = {}; + var opts = materialListEl.querySelectorAll('option'); + for (var i = 0; i < opts.length; i++) existing[opts[i].value.toUpperCase()] = true; + Object.keys(db).sort().forEach(function(key) { + if (!existing[key]) { + var opt = document.createElement('option'); + opt.value = db[key].material || key; + opt.dataset.id = db[key].id || '0'; + materialListEl.appendChild(opt); + } + }); + }); + function showStatusView() { createView.classList.add('hidden'); statusView.classList.remove('hidden'); @@ -291,7 +333,7 @@ const char OPENPRINTTAG_WRITER_HTML[] PROGMEM = R"rawliteral( } function syncMaterialNameFromSelection() { - var selectedText = materialTypeEl.options[materialTypeEl.selectedIndex].text; + var selectedText = materialSearchEl.value || 'PLA'; if (!materialNameEl.value.trim() || materialNameEl.dataset.autoFilled === 'true') { materialNameEl.value = selectedText.toUpperCase(); materialNameEl.dataset.autoFilled = 'true'; @@ -481,7 +523,7 @@ const char OPENPRINTTAG_WRITER_HTML[] PROGMEM = R"rawliteral( } } - materialTypeEl.addEventListener('change', syncMaterialNameFromSelection); + materialSearchEl.addEventListener('input', syncMaterialNameFromSelection); materialNameEl.addEventListener('input', function() { materialNameEl.value = materialNameEl.value.toUpperCase(); diff --git a/src/OpenTag3DWriterHTML.h b/src/OpenTag3DWriterHTML.h index fe533bf..c177071 100644 --- a/src/OpenTag3DWriterHTML.h +++ b/src/OpenTag3DWriterHTML.h @@ -42,28 +42,29 @@ const char OPENTAG3D_WRITER_HTML[] PROGMEM = R"rawliteral(
- + + + + + + + + + + + + + + + + + + + + + + +
@@ -308,6 +309,45 @@ const char OPENTAG3D_WRITER_HTML[] PROGMEM = R"rawliteral( syncColorPicker('colorPicker', 'colorHex'); setupAdvancedToggle('advancedToggle', 'advancedBox'); + // Auto-fill temps and density from material selection + var baseMaterialEl = document.getElementById('base_material'); + var modifiersEl = document.getElementById('material_modifiers'); + var ot3dFieldMap = { + minPrintTemp: 'min_print_temp_c', maxPrintTemp: 'max_print_temp_c', + minBedTemp: 'min_bed_temp_c', maxBedTemp: 'max_bed_temp_c', + density: 'density' + }; + trackAutoFill(['print_temp_c','bed_temp_c','min_print_temp_c','max_print_temp_c','min_bed_temp_c','max_bed_temp_c','density']); + function ot3dAutoFill() { + var name = baseMaterialEl.value; + var mod = modifiersEl ? modifiersEl.value : ''; + if (mod && mod !== 'None') name = name + '-' + mod; + autoFillMaterialData(name, ot3dFieldMap); + // Also fill the basic print/bed temp fields + var m = lookupMaterial(name); + if (m) { + var pt = document.getElementById('print_temp_c'); + if (pt && pt.dataset.autoFilled !== 'false') { pt.value = m.extruder_temp; pt.dataset.autoFilled = 'true'; } + var bt = document.getElementById('bed_temp_c'); + if (bt && bt.dataset.autoFilled !== 'false') { bt.value = m.bed_temp; bt.dataset.autoFilled = 'true'; } + } + } + baseMaterialEl.addEventListener('input', ot3dAutoFill); + if (modifiersEl) modifiersEl.addEventListener('change', ot3dAutoFill); + loadMaterialDb().then(function(db) { + var dl = document.getElementById('material-list'); + var existing = {}; + var opts = dl.querySelectorAll('option'); + for (var i = 0; i < opts.length; i++) existing[opts[i].value.toUpperCase()] = true; + Object.keys(db).sort().forEach(function(key) { + if (!existing[key]) { + var opt = document.createElement('option'); + opt.value = db[key].material || key; + dl.appendChild(opt); + } + }); + }); + var EXTENDED_IDS = [ 'serial_number', 'online_url', 'measured_filament_weight_g', 'empty_spool_weight_g', 'min_print_temp_c', 'max_print_temp_c', diff --git a/src/SharedJS.h b/src/SharedJS.h index 72a75ea..bc454c6 100644 --- a/src/SharedJS.h +++ b/src/SharedJS.h @@ -129,6 +129,125 @@ function setupAdvancedToggle(toggleId, boxId) { return { open: function(){ set(true); }, close: function(){ set(false); } }; } +/* ---- Material data auto-fill ---- */ + +// Hardcoded fallback — works offline when API is unreachable +var _materialFallback = { + 'PLA': {extruder_temp:240, bed_temp:55, density:1.24}, + 'PETG': {extruder_temp:270, bed_temp:75, density:1.27}, + 'ABS': {extruder_temp:280, bed_temp:90, density:1.04}, + 'ASA': {extruder_temp:280, bed_temp:100, density:1.05}, + 'TPU': {extruder_temp:250, bed_temp:40, density:1.21}, + 'PC': {extruder_temp:290, bed_temp:115, density:1.3}, + 'PCTG': {extruder_temp:270, bed_temp:75, density:1.21}, + 'PP': {extruder_temp:250, bed_temp:60, density:0.9}, + 'HIPS': {extruder_temp:270, bed_temp:95, density:1.03}, + 'PVA': {extruder_temp:240, bed_temp:60, density:1.23}, + 'PEEK': {extruder_temp:410, bed_temp:145, density:1.32}, + 'PA6': {extruder_temp:300, bed_temp:45, density:1.52}, + 'PA12': {extruder_temp:300, bed_temp:45, density:1.52}, + 'PEI': {extruder_temp:380, bed_temp:130, density:1.27}, + 'CPE': {extruder_temp:270, bed_temp:80, density:1.25} +}; +var _materialDb = {}; +var _materialDbLoaded = false; + +function loadMaterialDb() { + if (_materialDbLoaded) return Promise.resolve(_materialDb); + // Start with fallback data + Object.keys(_materialFallback).forEach(function(k) { + _materialDb[k] = _materialFallback[k]; + }); + return fetch('https://api.tigertag.io/api:tigertag/SpoolmanDB/materials') + .then(function(r) { return r.ok ? r.json() : []; }) + .then(function(data) { + if (Array.isArray(data)) { + data.forEach(function(m) { + if (m.material) _materialDb[m.material.toUpperCase()] = m; + }); + } + _materialDbLoaded = true; + return _materialDb; + }) + .catch(function() { + _materialDbLoaded = true; + return _materialDb; + }); +} + +function lookupMaterial(name) { + if (!name) return null; + var key = name.toUpperCase().replace(/\s+/g, ''); + // Exact match first + if (_materialDb[key]) return _materialDb[key]; + // Try with common separators + var withDash = key.replace(/([A-Z]+)(\d)/, '$1-$2'); + if (_materialDb[withDash]) return _materialDb[withDash]; + // Prefix match (e.g. "PA6 (Nylon 6)" → "PA6") + var prefix = key.split(/[^A-Z0-9-]/)[0]; + if (prefix && _materialDb[prefix]) return _materialDb[prefix]; + return null; +} + +// Track which fields the user has manually edited +function trackAutoFill(fieldIds) { + fieldIds.forEach(function(id) { + var el = document.getElementById(id); + if (!el) return; + el.dataset.autoFilled = 'true'; + el.addEventListener('input', function() { + el.dataset.autoFilled = 'false'; + }); + el.addEventListener('change', function() { + if (el.value === '') el.dataset.autoFilled = 'true'; + }); + }); +} + +function autoFillMaterialData(materialName, fieldMap) { + var m = lookupMaterial(materialName); + if (!m) return; + if (m.extruder_temp) { + if (fieldMap.minPrintTemp) { + var el = document.getElementById(fieldMap.minPrintTemp); + if (el && el.dataset.autoFilled !== 'false') { + el.value = Math.max(0, m.extruder_temp - 10); + el.dataset.autoFilled = 'true'; + } + } + if (fieldMap.maxPrintTemp) { + var el = document.getElementById(fieldMap.maxPrintTemp); + if (el && el.dataset.autoFilled !== 'false') { + el.value = m.extruder_temp + 10; + el.dataset.autoFilled = 'true'; + } + } + } + if (m.bed_temp) { + if (fieldMap.minBedTemp) { + var el = document.getElementById(fieldMap.minBedTemp); + if (el && el.dataset.autoFilled !== 'false') { + el.value = Math.max(0, m.bed_temp - 5); + el.dataset.autoFilled = 'true'; + } + } + if (fieldMap.maxBedTemp) { + var el = document.getElementById(fieldMap.maxBedTemp); + if (el && el.dataset.autoFilled !== 'false') { + el.value = m.bed_temp + 5; + el.dataset.autoFilled = 'true'; + } + } + } + if (m.density && fieldMap.density) { + var el = document.getElementById(fieldMap.density); + if (el && el.dataset.autoFilled !== 'false') { + el.value = m.density; + el.dataset.autoFilled = 'true'; + } + } +} + /* ---- Tag kind labels ---- */ var TAG_KIND_LABELS = { 'OpenPrintTag': 'OpenPrintTag', diff --git a/src/TigerTagWriterHTML.h b/src/TigerTagWriterHTML.h index 7ec8312..ddf87a6 100644 --- a/src/TigerTagWriterHTML.h +++ b/src/TigerTagWriterHTML.h @@ -42,69 +42,73 @@ const char TIGERTAG_WRITER_HTML[] PROGMEM = R"rawliteral(

Filament

- - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Brand not listed? Use Generic. Request a new brand →
@@ -310,6 +314,23 @@ const char TIGERTAG_WRITER_HTML[] PROGMEM = R"rawliteral( syncColorPicker('colorPicker', 'colorHex'); setupAdvancedToggle('advancedToggle', 'advancedBox'); + // Sync brand name input → hidden brand_id + var brandNameEl = document.getElementById('brand_name'); + var brandIdEl = document.getElementById('brand_id'); + var brandList = document.getElementById('brand-list'); + if (brandNameEl && brandIdEl && brandList) { + brandNameEl.addEventListener('input', function() { + var opts = brandList.querySelectorAll('option'); + brandIdEl.value = '65535'; // default to Generic + for (var i = 0; i < opts.length; i++) { + if (opts[i].value === brandNameEl.value) { + brandIdEl.value = opts[i].dataset.id; + break; + } + } + }); + } + // Fetch TigerTag API data — fallback to hardcoded options on failure var materialData = {}; // Store full material data for auto-fill on selection @@ -346,21 +367,44 @@ const char TIGERTAG_WRITER_HTML[] PROGMEM = R"rawliteral( } } + // Sync material search → hidden material_id + var materialSearchEl = document.getElementById('material_search'); + var materialIdEl = document.getElementById('material_id'); + var materialListEl = document.getElementById('material-list'); + + function syncMaterialId() { + var opts = materialListEl.querySelectorAll('option'); + materialIdEl.value = '38219'; // default PLA + for (var i = 0; i < opts.length; i++) { + if (opts[i].value === materialSearchEl.value) { + materialIdEl.value = opts[i].dataset.id; + break; + } + } + } + + trackAutoFill(['nozzle_min','nozzle_max','bed_min','bed_max']); + function autoFillFromMaterial() { - var id = document.getElementById('material_id').value; + syncMaterialId(); + var id = materialIdEl.value; var m = materialData[id]; if (!m) return; + var nMin = document.getElementById('nozzle_min'); + var nMax = document.getElementById('nozzle_max'); + var bMin = document.getElementById('bed_min'); + var bMax = document.getElementById('bed_max'); if (m.extruder_temp) { - document.getElementById('nozzle_min').value = Math.max(0, m.extruder_temp - 10); - document.getElementById('nozzle_max').value = m.extruder_temp + 10; + if (nMin && nMin.dataset.autoFilled !== 'false') { nMin.value = Math.max(0, m.extruder_temp - 10); nMin.dataset.autoFilled = 'true'; } + if (nMax && nMax.dataset.autoFilled !== 'false') { nMax.value = m.extruder_temp + 10; nMax.dataset.autoFilled = 'true'; } } if (m.bed_temp) { - document.getElementById('bed_min').value = Math.max(0, m.bed_temp - 5); - document.getElementById('bed_max').value = m.bed_temp + 5; + if (bMin && bMin.dataset.autoFilled !== 'false') { bMin.value = Math.max(0, m.bed_temp - 5); bMin.dataset.autoFilled = 'true'; } + if (bMax && bMax.dataset.autoFilled !== 'false') { bMax.value = m.bed_temp + 5; bMax.dataset.autoFilled = 'true'; } } } - document.getElementById('material_id').addEventListener('change', autoFillFromMaterial); + materialSearchEl.addEventListener('input', autoFillFromMaterial); (async function loadTigerTagAPI() { try { @@ -374,10 +418,16 @@ const char TIGERTAG_WRITER_HTML[] PROGMEM = R"rawliteral( if (materials) { materials.forEach(function(m) { materialData[m.id] = m; }); materials.sort(function(a, b) { return a.material.localeCompare(b.material); }); - populateSelect('material_id', materials, - function(m) { return m.id; }, - function(m) { return m.material; } - ); + var dl = document.getElementById('material-list'); + if (dl && materials.length > dl.options.length) { + dl.innerHTML = ''; + materials.forEach(function(m) { + var opt = document.createElement('option'); + opt.value = m.material; + opt.dataset.id = m.id; + dl.appendChild(opt); + }); + } } } } catch(e) { /* keep hardcoded fallback */ } @@ -392,10 +442,16 @@ const char TIGERTAG_WRITER_HTML[] PROGMEM = R"rawliteral( ]); if (brands) { brands.sort(function(a, b) { return a.name.localeCompare(b.name); }); - populateSelect('brand_id', brands, - function(b) { return b.id; }, - function(b) { return b.name; } - ); + var dl = document.getElementById('brand-list'); + if (dl && brands.length > dl.options.length) { + dl.innerHTML = ''; + brands.forEach(function(b) { + var opt = document.createElement('option'); + opt.value = b.name; + opt.dataset.id = b.id; + dl.appendChild(opt); + }); + } } } } catch(e) { /* keep hardcoded fallback */ } diff --git a/src/UIDRegistrationHTML.h b/src/UIDRegistrationHTML.h index 9c825f8..126085a 100644 --- a/src/UIDRegistrationHTML.h +++ b/src/UIDRegistrationHTML.h @@ -50,31 +50,32 @@ const char UID_REGISTRATION_HTML[] PROGMEM = R"rawliteral(
- + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -210,8 +211,32 @@ const char UID_REGISTRATION_HTML[] PROGMEM = R"rawliteral( syncColorPicker('colorPicker', 'colorHex'); setupAdvancedToggle('advancedToggle', 'advancedBox'); + // Auto-fill temps and density from material selection + var nfcFieldMap = { + minPrintTemp: 'min_print_temp', maxPrintTemp: 'max_print_temp', + minBedTemp: 'min_bed_temp', maxBedTemp: 'max_bed_temp', + density: 'density' + }; + trackAutoFill(['min_print_temp','max_print_temp','min_bed_temp','max_bed_temp','density']); + materialTypeEl.addEventListener('input', function() { + autoFillMaterialData(materialTypeEl.value, nfcFieldMap); + }); + loadMaterialDb().then(function(db) { + var dl = document.getElementById('material-list'); + var existing = {}; + var opts = dl.querySelectorAll('option'); + for (var i = 0; i < opts.length; i++) existing[opts[i].value.toUpperCase()] = true; + Object.keys(db).sort().forEach(function(key) { + if (!existing[key]) { + var opt = document.createElement('option'); + opt.value = db[key].material || key; + dl.appendChild(opt); + } + }); + }); + function syncMaterialNameFromSelection() { - var selectedText = materialTypeEl.options[materialTypeEl.selectedIndex].text; + var selectedText = materialTypeEl.value || 'PLA'; if (!materialNameEl.value.trim() || materialNameEl.dataset.autoFilled === 'true') { materialNameEl.value = selectedText.toUpperCase(); materialNameEl.dataset.autoFilled = 'true'; @@ -223,7 +248,7 @@ const char UID_REGISTRATION_HTML[] PROGMEM = R"rawliteral( resultBox.className = type ? 'result ' + type : 'result'; } - materialTypeEl.addEventListener('change', syncMaterialNameFromSelection); + materialTypeEl.addEventListener('input', syncMaterialNameFromSelection); materialNameEl.addEventListener('input', function() { materialNameEl.value = materialNameEl.value.toUpperCase();