#!/usr/bin/env node import { spawn } from 'node:child_process'; import { access } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const args = process.argv.slice(2); if (!args.length || args.includes('--help')) { console.log(`Usage: npm run blender:export -- INPUT.blend --output OUTPUT.glb [options] --blender PATH Blender binary (or FORMA_BLENDER; default: blender) --scene NAME Scene to export --objects GLOB Export matching objects; repeat for several patterns --bake-materials Bake procedural/animated surface inputs to UV atlases --resolution 128 Pixels per baked frame, 16–1024 --samples 8 Cycles samples per bake, 1–256 --lighting compat Unitless lights for Forma; spec preserves physical units --start-frame N --end-frame N --frame-step N --allow-lossy Accept approximations listed in OUTPUT.report.json See docs/BLENDER.md for supported features and limitations.`); process.exit(args.length ? 0 : 1); } const input = path.resolve(args.shift()); if (!/\.blend$/i.test(input)) throw Error('Input must be a .blend file'); await access(input); const binaryFlag = args.indexOf('--blender'); let binary = process.env.FORMA_BLENDER || 'blender'; if (binaryFlag >= 0) { if (!args[binaryFlag + 1]) throw Error('--blender requires a path'); binary = args[binaryFlag + 1]; args.splice(binaryFlag, 2); } const outputFlag = args.indexOf('--output'); if (outputFlag < 0 || !args[outputFlag + 1] || !/\.glb$/i.test(args[outputFlag + 1])) throw Error('--output OUTPUT.glb is required'); const output = path.resolve(args[outputFlag + 1]); if (output === input) throw Error('Output must differ from input'); args[outputFlag + 1] = output; const script = fileURLToPath(new URL('../blender/export_forma.py', import.meta.url)); const child = spawn(binary, ['--background', '--factory-startup', '--disable-autoexec', input, '--python-exit-code', '1', '--python', script, '--', ...args], { stdio: 'inherit', shell: false }); child.on('error', error => { console.error(`Cannot start Blender: ${error.message}. Set FORMA_BLENDER or --blender.`); process.exitCode = 1; }); child.on('exit', (code, signal) => { process.exitCode = code ?? (signal ? 1 : 0); }); process.once('SIGINT', () => child.kill('SIGINT')); process.once('SIGTERM', () => child.kill('SIGTERM'));