Model library: remembered GLBs with one-click switching

Every loaded model (GLB or converted FBX) is added to 'I miei modelli'
in the Animazioni 3D tab: click a name to switch the dancer instantly
(active one highlighted), x removes it from the list (file untouched).
Persisted in localStorage; the current model is seeded on first run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Fable 5 committed 2026-07-31 11:49:44 +02:00
1 parent 06ae7bc422
commit a8da0a4c9d
3 files changed
+80 -1

No files matched your search

+2
View File
@@ -167,6 +167,8 @@
<input type="file" id="glb-input" accept=".glb,.fbx" hidden />
<div class="hint-mini" id="glb-name">Il modello ruota e balla a tempo di musica nella famiglia “Modello 3D”;
con la camera lo comandi in “Interattivo Avatar”.</div>
<label>I miei modelli</label>
<div id="glb-models" class="glb-models"></div>
<label>Sfondo</label>
<select id="glb-bg">
<option value="gradient">Gradiente</option>
+51 -1
View File
@@ -186,12 +186,61 @@ function renderGlbClips(names) {
box.appendChild(l);
});
}
// Model library: every loaded model is remembered for one-click switching.
let glbModels = [];
try { glbModels = JSON.parse(localStorage.getItem('glbmodels') || '[]'); } catch (e) {}
function saveGlbModels() { localStorage.setItem('glbmodels', JSON.stringify(glbModels)); }
function addGlbModel(name, p) {
const i = glbModels.findIndex(m => m.path === p);
if (i < 0) glbModels.push({ name, path: p }); else glbModels[i].name = name;
saveGlbModels();
renderGlbModels();
}
function renderGlbModels() {
const box = $('#glb-models');
box.innerHTML = '';
if (!glbModels.length) {
box.innerHTML = '<span class="hint-mini">I modelli caricati appariranno qui per il cambio rapido.</span>';
return;
}
glbModels.forEach((mo) => {
const row = document.createElement('div');
row.className = 'glb-model' + (mo.path === glbPath ? ' on' : '');
const nm = document.createElement('span');
nm.className = 'gm-name';
nm.textContent = mo.name;
nm.title = mo.path;
nm.addEventListener('click', () => {
glbPath = mo.path;
localStorage.setItem('glb3d', glbPath);
send({ type: 'glb', path: glbPath });
glbLabel(mo.name);
renderGlbModels();
});
const del = document.createElement('button');
del.className = 'gm-del';
del.textContent = '✕';
del.title = 'Togli dalla lista (il file resta sul disco)';
del.addEventListener('click', () => {
glbModels = glbModels.filter(x => x.path !== mo.path);
saveGlbModels();
renderGlbModels();
});
row.appendChild(nm);
row.appendChild(del);
box.appendChild(row);
});
}
function glbLabel(name) {
$('#glb-name').textContent = name
? '🧊 ' + name + ' — attiva la famiglia “Modello 3D” per vederlo.'
: 'Il modello ruota a tempo di musica nella famiglia “Modello 3D”.';
}
if (glbPath) glbLabel(glbPath.split('/').pop());
if (glbPath) {
glbLabel(glbPath.split('/').pop());
if (!glbModels.some(m => m.path === glbPath)) addGlbModel(glbPath.split('/').pop(), glbPath);
}
renderGlbModels();
// every shader family can be a backdrop too (fluid/interactive/3D excluded:
// fluids are the explicit options above, the others can't render as layers)
EFFECTS.families.forEach((name, fi) => {
@@ -307,6 +356,7 @@ $('#glb-input').addEventListener('change', async (e) => {
localStorage.setItem('glb3d', p);
glbSend();
glbLabel(f.name.replace(/\.fbx$/i, '.glb'));
addGlbModel(f.name.replace(/\.fbx$/i, '.glb'), p);
});
// --- Effect sequence (playlist of effects with auto-cycle) ---
+27
View File
@@ -850,3 +850,30 @@ kbd {
accent-color: #4caf50;
}
#glb-anims .hint-mini { grid-column: 1 / -1; }
/* Model library: remembered GLBs for one-click switching */
.glb-models { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; }
.glb-model {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 8px;
border: 1px solid #444;
border-radius: 8px;
cursor: pointer;
font-size: 11.5px;
opacity: 0.8;
}
.glb-model.on { border-color: #7aa2ff; background: rgba(110,150,255,0.14); opacity: 1; }
.glb-model .gm-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.glb-model .gm-del {
flex: 0 0 auto;
width: auto;
background: transparent;
border: none;
color: #ff8a8a;
padding: 0 2px;
font-size: 11px;
opacity: 0.7;
}
.glb-models .hint-mini { grid-column: 1 / -1; }