Dance library: bundled clips bind to any rigged model + repertoire + manual BPM
- models/dance_library.glb: skeleton-only GLB with the 13 Mixamo dances, loaded at startup and bound BY BONE NAME to whatever rigged GLB the user loads — new models dance immediately, no baking needed. - Repertoire UI (Media tab): checkbox chips of the available clips, the director only picks the ticked ones (none = all); persisted. - 'Aggiungi animazioni': extra .glb clips load directly; .fbx are converted on the fly by Blender via a new anim:convert IPC and the bundled scripts/fbx2animglb.py (asarUnpacked for packaged builds). - Manual BPM with TAP tempo: 0 = follow the detected beat, otherwise a pure metronome drives the dance clock. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
83fc655219
commit
97376b22c9
10 files changed
+340
-20
No files matched your search
@@ -108,6 +108,34 @@ async function ensureMicAccess() {
|
||||
} catch (e) { /* getUserMedia will prompt again if needed */ }
|
||||
}
|
||||
|
||||
// Convert a Mixamo FBX animation into a skeleton-only GLB clip via Blender
|
||||
// (when installed): lets the user add moves to the dance library from the
|
||||
// panel without any manual conversion step.
|
||||
function blenderPath() {
|
||||
const cands = ['/Applications/Blender.app/Contents/MacOS/Blender',
|
||||
'/opt/homebrew/bin/blender', '/usr/local/bin/blender'];
|
||||
for (const c of cands) if (fs.existsSync(c)) return c;
|
||||
return null;
|
||||
}
|
||||
ipcMain.handle('anim:convert', async (_e, fbxPath) => {
|
||||
const blender = blenderPath();
|
||||
if (!blender) return { ok: false, error: 'Blender non trovato: serve per convertire gli FBX (oppure carica un .glb)' };
|
||||
const outDir = path.join(app.getPath('userData'), 'anims');
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
const out = path.join(outDir, path.basename(fbxPath).replace(/\.fbx$/i, '') + '.glb');
|
||||
// packaged app: the .py must live outside the asar for Blender to read it
|
||||
const script = path.join(__dirname, 'scripts', 'fbx2animglb.py')
|
||||
.replace('app.asar' + path.sep, 'app.asar.unpacked' + path.sep);
|
||||
return new Promise((resolve) => {
|
||||
execFile(blender, ['-b', '-P', script, '--', fbxPath, out],
|
||||
{ maxBuffer: 1 << 24, timeout: 180000 }, (err, _so, se) => {
|
||||
if (err || !fs.existsSync(out))
|
||||
resolve({ ok: false, error: 'conversione fallita: ' + String(se || (err && err.message) || '').slice(-300) });
|
||||
else resolve({ ok: true, path: out });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Camera permission is requested lazily (only when an interactive preset is
|
||||
// activated), not at startup: no camera prompt for users who never use it.
|
||||
ipcMain.handle('cam:ensure', async () => {
|
||||
|
||||
Reference in new issue
Block a user