Self-hosted Express + Postgres app for organizing STL/3MF/OBJ/etc. with: - Model/file library, tags, collections, print settings - URL import: Thingiverse (API), Printables (GraphQL), MakerWorld (FlareSolverr + token), cubee3d/other (metadata + manual files) - In-browser 3D viewer; 3MF rendered via Python/trimesh GLB conversion - Auto thumbnails, license/commercial-use tracking, in-app editing - Settings (API tokens / FlareSolverr) stored in DB with .env fallback Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
32 lines
1.1 KiB
JavaScript
32 lines
1.1 KiB
JavaScript
// Converts 3MF files to GLB via the Python/trimesh sidecar, so the browser can
|
|
// render Bambu/MakerWorld production-extension 3MFs (which the JS 3MFLoader can't).
|
|
|
|
const path = require('path');
|
|
const { execFile } = require('child_process');
|
|
|
|
const PYTHON = path.join(__dirname, '..', 'tools', 'venv', 'bin', 'python');
|
|
const SCRIPT = path.join(__dirname, '..', 'tools', 'convert_3mf.py');
|
|
|
|
// Returns { glb, volume_cm3, bbox, faces } or throws.
|
|
function convert3mfToGlb(inputPath, outputPath) {
|
|
return new Promise((resolve, reject) => {
|
|
execFile(
|
|
PYTHON,
|
|
[SCRIPT, inputPath, outputPath],
|
|
{ maxBuffer: 16 * 1024 * 1024, timeout: 120000 },
|
|
(err, stdout, stderr) => {
|
|
if (err) return reject(new Error(stderr ? stderr.split('\n').slice(-3).join(' ') : err.message));
|
|
let result;
|
|
try {
|
|
result = JSON.parse(stdout.trim().split('\n').pop());
|
|
} catch {
|
|
return reject(new Error('Converter returned unparseable output.'));
|
|
}
|
|
if (result.error) return reject(new Error(result.error));
|
|
resolve(result);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
module.exports = { convert3mfToGlb };
|