// Exercises the Python sidecar scripts against small fixtures. Skipped when the // Python venv isn't installed. const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { execFileSync } = require('child_process'); const { PYTHON } = require('../lib/python'); const { geometryKey } = require('../lib/fingerprint'); const TOOLS = path.join(__dirname, '..', 'tools'); const fixture = (name) => path.join(__dirname, 'fixtures', name); const skip = !fs.existsSync(PYTHON) && 'Python venv not installed'; function fingerprints(...names) { const out = execFileSync(PYTHON, [path.join(TOOLS, 'fingerprint.py'), ...names.map(fixture)], { encoding: 'utf8' }); return out.trim().split('\n').map((line) => JSON.parse(line)); } test('a cube matches across binary STL and a rotated, moved 3MF', { skip }, () => { const [stl, threemf] = fingerprints('cube.stl', 'cube_rotated.3mf'); assert.ok(stl.fp && threemf.fp, JSON.stringify([stl, threemf])); assert.equal(threemf.fp.faces, 12); assert.equal(geometryKey(stl.fp), geometryKey(threemf.fp)); }); // components.3mf is laid out like a Bambu project: the root model's objects are // components pointing (via p:path) at object 1 of a separate part file, which // also holds a decoy object 2. One placement is mirrored. test('3MF components resolve to the one object they reference, once per placement', { skip }, () => { const [r] = fingerprints('components.3mf'); assert.ok(r.fp, JSON.stringify(r)); assert.equal(r.fp.faces, 24); assert.ok(Math.abs(r.fp.volume - 16000) < 1e-6, `volume ${r.fp.volume}`); assert.deepEqual(r.fp.extents.map(Math.round).sort((a, b) => a - b), [10, 40, 120]); }); test('3MF converts to GLB with shared geometry and a combined bounding box', { skip }, () => { const out = path.join(os.tmpdir(), `printhive-test-${process.pid}.glb`); try { const stdout = execFileSync(PYTHON, [path.join(TOOLS, 'convert_3mf.py'), fixture('components.3mf'), out], { encoding: 'utf8' }); const summary = JSON.parse(stdout.trim().split('\n').pop()); assert.equal(summary.faces, 24); assert.deepEqual([summary.bbox.x, summary.bbox.y, summary.bbox.z].map(Math.round), [120, 40, 10]); assert.ok(Math.abs(summary.volume_cm3 - 16) < 1e-6); const glb = fs.readFileSync(out); const gltf = JSON.parse(glb.subarray(20, 20 + glb.readUInt32LE(12)).toString('utf8')); assert.equal(gltf.meshes.length, 1, 'the object is stored once'); assert.equal(gltf.nodes.filter((n) => n.mesh !== undefined).length, 2, 'and placed twice'); } finally { fs.rmSync(out, { force: true }); } });