A Bambu Studio or OrcaSlicer 3MF names the filaments its plates use, but reading that means shelling out to Python, so it can't be done per page load. A background pass — in the same loop as thumbnails and fingerprints — reads it once per model into models.filaments, which turns "what can I print now" into a comparison of two lists. Adds a Filament page for the spools on hand: material, colour, brand and grams left, edited in place. Matching is on material only; a project's exact colours would make almost nothing printable, and a different colour still prints. Measured on the dev library: 10 models read with no errors, the two Bambu projects resolving to four PLA colours each, and no plate images written because the reader skips --images and queues behind anything interactive. Not done yet: nothing in the library view uses this — no "printable now" badge or filter — and the Filament page itself has not been seen rendered, because the local preview browser has no session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
47 lines
2.1 KiB
JavaScript
47 lines
2.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),
|
|
// 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');
|
|
}
|
|
|
|
// Just the filaments a project asks for: no --images, so no plate previews are
|
|
// written, and queued behind anything someone is waiting on — this runs over the
|
|
// whole library in the background.
|
|
async function readFilaments(inputPath) {
|
|
const info = await parseResult(
|
|
await runScript('threemf_info.py', [inputPath], { timeout: 60000, isBackground: true }),
|
|
'reading this 3MF project');
|
|
return Array.isArray(info.filaments) ? info.filaments : [];
|
|
}
|
|
|
|
module.exports = { convert3mfToGlb, readProjectInfo, readFilaments };
|