75 lines
5.7 KiB
JavaScript
75 lines
5.7 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
import { pathToFileURL } from 'node:url';
|
|
import createModule from '../build-wasm/linuxcnc-sim-core.js';
|
|
import { SimSession, CONTEXT_ID } from '../web/worker/sim-session.mjs';
|
|
import { loadSimulationCase, toWorkerConfiguration } from '../web/workspace/sim-case-loader.mjs';
|
|
import { writeTrtGeometry } from '../web/workspace/trt-geometry.mjs';
|
|
import { MACHINE_DEFINITIONS } from '../web/workspace/machine-definitions.mjs';
|
|
const root = new URL('../', import.meta.url), output = process.env.GLB_EVIDENCE_DIR ? pathToFileURL(`${process.env.GLB_EVIDENCE_DIR.replace(/\/$/, '')}/`) : new URL('coverage/glb-machine/', root);
|
|
const gantry = process.env.GLB_MACHINE_VARIANT === 'gantry';
|
|
const fetchImpl = async (url) => { try { const body = await readFile(new URL(String(url).replace(/^\//, ''), root), 'utf8'); return { ok: true, text: async () => body }; } catch { return { ok: false }; } };
|
|
const { chromium } = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href);
|
|
const browser = await chromium.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
|
|
const page = await browser.newPage();
|
|
const report = { report_id: 'glb-machine-five-axis-motion-v1', status: 'running', machines: [] };
|
|
try {
|
|
await page.goto(process.env.XYZAC_PANEL_URL ?? 'http://127.0.0.1:5174/');
|
|
await page.waitForFunction(() => document.querySelector('main')?.dataset.axisProgramLoading === 'false');
|
|
for (const definition of MACHINE_DEFINITIONS) {
|
|
const id = definition.coordinates.slice(3).toLowerCase();
|
|
const structure = await page.evaluate(async ({ id, gantry }) => {
|
|
const api = await import('/src/imported-machine.ts');
|
|
if (window.motionAsset) api.disposeMachineScene(window.motionAsset.scene);
|
|
window.motionAsset = await api.loadImportedMachine(await (await fetch(`/machines/demo-${id}-${gantry ? 'gantry' : 'xyz-head'}.glb`)).arrayBuffer(), `${id}.glb`);
|
|
return window.motionAsset.structure;
|
|
}, { id, gantry });
|
|
const core = await createModule({ wasmBinary: await readFile(new URL('build-wasm/linuxcnc-sim-core.wasm', root)) });
|
|
const session = new SimSession(core, { sessionId: `glb-motion-${id}` }); let seq = 0;
|
|
const send = (kind, payload = {}) => {
|
|
const result = session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, seq: ++seq, operation_id: `motion-${seq}`, payload });
|
|
assert.notEqual(result.kind, 'ERROR', `${kind}: ${JSON.stringify(result.payload)}`); return result;
|
|
};
|
|
send('HELLO'); send('SESSION_ATTACH');
|
|
const bundle = writeTrtGeometry(await loadSimulationCase({ path: definition.entrypoint, fetchImpl }), structure.offsets);
|
|
send('LOAD_WORKSPACE', { root_path: '/workspace', configuration: toWorkerConfiguration(bundle) });
|
|
const tilt = definition.coordinates[3];
|
|
const source = `G21 G90 G94\nG0 X20 Y10 Z20 ${tilt}0 C0\nM428\nG1 X35 Y24 Z32 ${tilt}25 C40 F900\nG1 X24 Y16 Z24 ${tilt}-20 C-30 F900\nM2\n`;
|
|
send('PROGRAM_LOAD', { source, path: `five-axis-${id}.ngc` });
|
|
send('POWER', { active: true }); send('HOME', { all: true });
|
|
for (let tick = 0; tick < 500 && !session.wasmHomeLimit.read().state.homed; tick++) send('SIM_STEP', { cycles: 1 });
|
|
assert.equal(session.wasmHomeLimit.read().state.homed, true);
|
|
send('PROGRAM_RUN');
|
|
const samples = []; let simultaneousTicks = 0, previous, result;
|
|
for (let tick = 0; tick < 3000; tick++) {
|
|
result = send('SIM_STEP', { cycles: 10 });
|
|
const panel = result.payload.panel_snapshot;
|
|
const joints = panel.visual.joints;
|
|
if (panel.kinematics.active_type === 1 && joints?.length === 5) {
|
|
if (previous && joints.every((v, i) => Math.abs(v - previous[i]) > 1e-8)) simultaneousTicks++;
|
|
samples.push({ tick: result.simulation_tick, pose: panel.visual.pose, joints, offsets: panel.kinematics.offsets });
|
|
previous = joints;
|
|
}
|
|
if (result.payload.program.program_status === 'completed' && result.payload.wasm_motion.state.queue_depth === 0) break;
|
|
}
|
|
assert.equal(result.payload.program.program_status, 'completed');
|
|
assert.ok(simultaneousTicks > 10, `only ${simultaneousTicks} simultaneously moving ticks`);
|
|
assert.ok(samples.length > 10);
|
|
const projection = await page.evaluate(async (samples) => {
|
|
const model = (await import('/src/imported-machine.ts')).buildImportedMachine(window.motionAsset);
|
|
let maxError = 0;
|
|
for (const sample of samples) {
|
|
model.update(sample);
|
|
const point = model.workOrigin.localToWorld(model.toolTip.position.clone().set(sample.pose.x / 1000, sample.pose.y / 1000, sample.pose.z / 1000));
|
|
const tip = model.toolTip.getWorldPosition(point.clone()); maxError = Math.max(maxError, point.distanceTo(tip));
|
|
}
|
|
model.dispose(); return { samples: samples.length, max_error_view_units: maxError };
|
|
}, samples);
|
|
assert.ok(projection.max_error_view_units < 1e-7, JSON.stringify(projection));
|
|
report.machines.push({ coordinates: structure.coordinates, source, simultaneous_ticks: simultaneousTicks, ...projection, trajectory: samples });
|
|
}
|
|
report.status = 'verified';
|
|
} catch (error) { report.status = 'failed'; report.error = String(error.stack ?? error); throw error; }
|
|
finally { await writeFile(new URL('motion.report.json', output), JSON.stringify(report, null, 2) + '\n'); await browser.close(); }
|
|
console.log(JSON.stringify({ report_id: report.report_id, status: report.status, machines: report.machines.map(({ coordinates, simultaneous_ticks, samples, max_error_view_units }) => ({ coordinates, simultaneous_ticks, samples, max_error_view_units })) }));
|