printhive/lib/converter.js
dlawler489 fcb418f119 Show 3MF projects plate by plate in their filament colours
- threemf_stream reads Bambu Studio / OrcaSlicer project metadata: plates
  (name, objects, preview images), filaments, and each part's type and
  extruder. Placements now record which build item and part they belong to.
- convert_3mf.py leaves out negative/modifier/support parts, colours each
  part by its filament (a part's extruder 0 falls back to its object's), and
  takes --plate N. Geometry is still stored once per (part, colour).
- New threemf_info.py lists plates and filaments without loading geometry
  and extracts plate preview images; served via /api/{files,entries}/:id/project.
- /glb?plate=N builds and caches a per-plate preview. Cached GLB names carry
  a converter version, so previews made before colours are rebuilt.
- Model detail: plate chips (with the slicer's plate images) under the
  viewer — multi-plate projects open on plate 1 — plus "All plates", and a
  Filaments section with colour swatches.
- Fingerprints leave out negative/modifier parts, so a project matches the
  model it prints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 07:49:58 +10:00

37 lines
1.7 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),
// and reads their slicer project details (plates, filaments).
const { runScript } = require('./python');
// Parses a sidecar script's last JSON line, turning its error / failure into a throw.
function parseResult({ err, stdout, stderr }, what) {
let result = null;
try {
result = JSON.parse(stdout.trim().split('\n').pop());
} catch { /* handled below */ }
if (result && result.error) throw new Error(result.error);
if (err) {
if (err.killed) throw new Error(`Timed out ${what}.`);
throw new Error(stderr ? stderr.trim().split('\n').slice(-3).join(' ') : err.message);
}
if (!result) throw new Error(`Unparseable output ${what}.`);
return result;
}
// Returns { glb, plate, volume_cm3, bbox, faces } or throws. `plate` limits the
// output to one plate of a slicer project.
async function convert3mfToGlb(inputPath, outputPath, { plate = null } = {}) {
const args = [inputPath, outputPath];
if (plate) args.push('--plate', String(plate));
return parseResult(await runScript('convert_3mf.py', args, { timeout: 120000 }), 'converting this 3MF for preview');
}
// Returns { plates: [{ index, name, objectCount, thumbnail }], filaments: [{ colour, type }] }.
// Plate previews are written into imagesDir as <n>.png / <n>-small.png.
async function readProjectInfo(inputPath, imagesDir) {
return parseResult(await runScript('threemf_info.py', [inputPath, '--images', imagesDir], { timeout: 60000 }),
'reading this 3MF project');
}
module.exports = { convert3mfToGlb, readProjectInfo };