Select machine type in design dialog and save importable machine GLB
This commit is contained in:
@@ -13,7 +13,7 @@ const [contract, app, panel, preview] = await Promise.all([
|
||||
const controlIds = [
|
||||
"menu-file", "menu-file-edit-tool-data", "menu-file-quit", "menu-help", "menu-help-about", "about-close",
|
||||
"toolbar-estop", "toolbar-power", "toolbar-open", "toolbar-load-case", "toolbar-reload", "toolbar-run", "toolbar-step", "toolbar-pause-resume", "toolbar-stop", "toolbar-block-delete", "toolbar-optional-pause", "toolbar-zoom-in", "toolbar-zoom-out", "toolbar-view-z", "toolbar-view-z2", "toolbar-view-x", "toolbar-view-y", "toolbar-view-y2", "toolbar-view-p", "toolbar-rotate", "toolbar-clear-live-plot",
|
||||
"workspace-case-select", "workspace-load-3d-chips", "workspace-rtcp-programs", "rtcp-program-scope", "rtcp-program-select", "rtcp-program-source", "rtcp-program-load", "rtcp-program-download", "machine-manager-settings", "machine-manager-import", "machine-manager-files", "machine-manager-delete", "machine-manager-role", "machine-manager-parent", "machine-manager-show-origin", "machine-tree-node", "machine-tree-origin", "machine-tree-axis-role", "machine-joint-direction", "machine-joint-sign", "machine-joint-value", "machine-joint-step-minus", "machine-joint-step-plus", "machine-joint-reset", "machine-joint-follow-live", "machine-joint-live-state", "machine-manager-export-json", "machine-manager-undo", "machine-manager-redo", "machine-manager-draft-state", "machine-tree-search", "machine-tree-expand", "machine-tree-collapse", "machine-manager-validation", "machine-test-x", "machine-test-y", "machine-test-z", "machine-test-a", "machine-test-b", "machine-test-c", "machine-test-reset", "machine-test-state", "machine-manager-apply", "machine-manager-export", "machine-manager-tab-structure", "machine-manager-tab-motion", "machine-manager-tab-calibration", "machine-view-fit", "machine-view-selected", "machine-view-front", "machine-view-top", "manual-home-all", "manual-home-joint-select",
|
||||
"workspace-case-select", "workspace-load-3d-chips", "workspace-rtcp-programs", "rtcp-program-scope", "rtcp-program-select", "rtcp-program-source", "rtcp-program-load", "rtcp-program-download", "machine-manager-settings", "machine-manager-type", "machine-manager-type-note", "machine-manager-import", "machine-manager-files", "machine-manager-delete", "machine-manager-role", "machine-manager-parent", "machine-manager-show-origin", "machine-tree-node", "machine-tree-origin", "machine-tree-axis-role", "machine-joint-direction", "machine-joint-sign", "machine-joint-value", "machine-joint-step-minus", "machine-joint-step-plus", "machine-joint-reset", "machine-joint-follow-live", "machine-joint-live-state", "machine-manager-export-json", "machine-manager-undo", "machine-manager-redo", "machine-manager-draft-state", "machine-tree-search", "machine-tree-expand", "machine-tree-collapse", "machine-manager-validation", "machine-test-x", "machine-test-y", "machine-test-z", "machine-test-a", "machine-test-b", "machine-test-c", "machine-test-reset", "machine-test-state", "machine-manager-apply", "machine-manager-export", "machine-manager-tab-structure", "machine-manager-tab-motion", "machine-manager-tab-calibration", "machine-view-fit", "machine-view-selected", "machine-view-front", "machine-view-top", "manual-home-all", "manual-home-joint-select",
|
||||
"switchkins-0", "switchkins-1", "switchkins-2",
|
||||
"tab-manual", "tab-mdi", "tab-diagnostics", "tab-preview", "tab-dro",
|
||||
"manual-axis-x", "manual-axis-y", "manual-axis-z", "manual-axis-a", "manual-axis-c",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
import * as THREE from "../web/react/node_modules/three/build/three.module.js";
|
||||
const pw = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE ?? "/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.js"));
|
||||
const browser = await (pw.default ?? pw).chromium.launch({ headless: true, executablePath: "/usr/bin/google-chrome", args: ["--no-sandbox", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"] });
|
||||
const output = new URL("../coverage/glb-machine-manager/joint-dof/", import.meta.url); await mkdir(output, { recursive: true });
|
||||
const output = process.env.JOINT_TEST_OUTPUT ? pathToFileURL(process.env.JOINT_TEST_OUTPUT.replace(/\/?$/, "/")) : new URL("../coverage/glb-machine-manager/joint-dof/", import.meta.url); await mkdir(output, { recursive: true });
|
||||
const bytes = await readFile(new URL("../MILL700-U_1.glb", import.meta.url));
|
||||
const report = { report_id: "GLB-MGR-E13", status: "running", url: process.env.MACHINE_MANAGER_URL ?? "http://127.0.0.1:5180/", machines: [], browser_errors: [] };
|
||||
const near = (a, b, label) => { assert.equal(a.length, b.length, label); a.forEach((v, i) => assert.ok(Math.abs(v - b[i]) < 2e-6, `${label} ${i}: ${v} vs ${b[i]}`)); };
|
||||
@@ -17,8 +17,8 @@ try {
|
||||
const preview = page.locator("[data-machine-manager-preview]");
|
||||
const idle = () => page.waitForFunction(() => document.querySelector("main.axis-app")?.dataset.axisProgramLoading === "false" && document.querySelector("main.axis-app")?.dataset.axisStateSource === "linuxcnc-wasm", null, { timeout: 60000 });
|
||||
await page.goto(report.url); await idle();
|
||||
if (coordinates === "XYZBC") { await c("workspace-case-select").selectOption("linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"); await idle(); }
|
||||
await c("machine-manager-settings").click(); await c("machine-manager-files").setInputFiles({ name: "MILL700-U_1.glb", mimeType: "model/gltf-binary", buffer: bytes });
|
||||
|
||||
await c("machine-manager-settings").click(); await c("machine-manager-type").selectOption(coordinates); assert.match(await c("workspace-case-select").inputValue(), /xyzac-trt.ini/); await c("machine-manager-files").setInputFiles({ name: "MILL700-U_1.glb", mimeType: "model/gltf-binary", buffer: bytes });
|
||||
await page.waitForFunction(() => document.querySelectorAll('[data-axis-control="machine-tree-node"]').length >= 132);
|
||||
assert.equal(await c("machine-joint-value").isDisabled(), true);
|
||||
assert.equal(await c("machine-manager-apply").isDisabled(), true);
|
||||
@@ -52,6 +52,7 @@ try {
|
||||
assert.equal(json.schema, "linuxcnc.joint-model/1"); assert.equal(json.coordinates, coordinates); assert.deepEqual(Object.keys(json.axisMap).sort(), [...coordinates].sort()); assert.equal(json.nodes.filter((n) => n.joint).length, 5); assert.ok(json.nodes.every((n) => n.joint === null || n.joint.dof === 1));
|
||||
assert.equal(json.nodes.find((n) => n.id === json.axisMap.Z).joint.sign, -1);
|
||||
const glbDownload = page.waitForEvent("download"); await c("machine-manager-export").click(); const glb = await readFile(await (await glbDownload).path());
|
||||
if (process.env.SAVE_MACHINE_MODELS) { await mkdir(process.env.SAVE_MACHINE_MODELS, { recursive: true }); await writeFile(`${process.env.SAVE_MACHINE_MODELS}/MILL700-${coordinates.toLowerCase()}.glb`, glb); }
|
||||
const gltf = JSON.parse(glb.toString("utf8", 20, 20 + glb.readUInt32LE(12)));
|
||||
assert.deepEqual(gltf.scenes[0].extras.linuxcncJointModel, json);
|
||||
const bed = gltf.nodes.find((n) => n.name === "MILL700-A底座"); assert.deepEqual(bed.extras.linuxcncJointModel, json); assert.ok(bed.extras.linuxcncMachineGeometry);
|
||||
@@ -64,6 +65,7 @@ try {
|
||||
await page.keyboard.press("Escape"); await c("machine-glb-settings").click(); await c("machine-glb-file").setInputFiles({ name: `managed-${coordinates.toLowerCase()}.glb`, mimeType: "model/gltf-binary", buffer: glb });
|
||||
await page.waitForFunction(() => document.querySelector(".xyzac-machine-view")?.dataset.machineJointState, null, { timeout: 60000 }); await idle(); await page.keyboard.press("Escape");
|
||||
console.log(`${coordinates}: standard GLB importer verified`);
|
||||
assert.match(await c("workspace-case-select").inputValue(), new RegExp(`${coordinates.toLowerCase()}-trt.ini`));
|
||||
// Apply directly from the restored one-file hierarchy as well.
|
||||
await c("machine-manager-settings").click(); await c("machine-manager-apply").click(); await idle(); assert.match(await page.locator(".machine-manager-footer").textContent(), /已应用/); await page.keyboard.press("Escape");
|
||||
const program = `M428
|
||||
|
||||
@@ -64,7 +64,7 @@ try {
|
||||
await page.keyboard.press("Escape");
|
||||
const xyzbcCase = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"; await control("workspace-case-select").selectOption(xyzbcCase);
|
||||
await page.waitForFunction((path) => document.querySelector('[data-axis-control="workspace-case-select"]')?.value === path && document.querySelector("main.axis-app")?.dataset.axisProgramLoading === "false", xyzbcCase, { timeout: 45000 });
|
||||
await control("machine-manager-settings").click(); assert.match(await page.locator(".machine-manager-dialog .machine-dialog-titlebar").textContent(), /XYZBC/);
|
||||
await control("machine-manager-settings").click(); assert.match(await page.locator(".machine-manager-dialog .machine-dialog-titlebar").textContent(), /XYZAC/); await control("machine-manager-type").selectOption("XYZBC");
|
||||
await control("machine-manager-tab-structure").click(); await selectRoot(5); await control("machine-tree-axis-role").selectOption("B");
|
||||
await control("machine-manager-tab-calibration").click();
|
||||
const bcCalibrations = page.locator(".machine-manager-calibration fieldset.machine-vector"); const bcOrigin = bcCalibrations.filter({ hasText: "C 轴原点" }); await bcOrigin.getByLabel("X", { exact: true }).fill("-18"); await bcOrigin.getByLabel("Y", { exact: true }).fill("14"); await bcOrigin.getByLabel("Z", { exact: true }).fill("12");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { machineDraftHistory } from '../web/react/src/machine-manager-history.ts';
|
||||
const draft = { parts: [], cOriginMm:[0,0,0], tiltOffsetMm:[0,0,0], physicalZero:{x:0,y:0,z:0,a:0,b:0,c:0} };
|
||||
import { machineDraftHistory, changeMachineType } from '../web/react/src/machine-manager-history.ts';
|
||||
const draft = { coordinates:"XYZAC", parts: [], cOriginMm:[0,0,0], tiltOffsetMm:[0,0,0], physicalZero:{x:0,y:0,z:0,a:0,b:0,c:0} };
|
||||
let state = { current:draft, past:[], future:[] };
|
||||
const edit = (n) => ({ type:'edit', change:(d) => ({...d, cOriginMm:[n,0,0]}) });
|
||||
state = machineDraftHistory(state, edit(1)); state = machineDraftHistory(state,edit(1)); assert.equal(state.past.length,1);
|
||||
@@ -9,4 +9,8 @@ state = machineDraftHistory(state,{type:'redo'}); assert.equal(state.current.cOr
|
||||
state = machineDraftHistory(state,{type:'undo'}); state = machineDraftHistory(state,edit(2)); assert.equal(state.future.length,0);
|
||||
for(let i=3;i<45;i++) state=machineDraftHistory(state,edit(i)); assert.equal(state.past.length,30);
|
||||
const current=state.current; state=machineDraftHistory(state,{type:'clear'}); assert.equal(state.current,current); assert.equal(state.past.length,0);
|
||||
console.log(JSON.stringify({report_id:'GLB-MGR-E14-HISTORY',status:'verified',checks:['undo/redo','duplicate edits ignored','new edit discards redo','30-entry bound','clear preserves draft']}));
|
||||
|
||||
const ac = { ...draft, parts: [{ id:"p", showOrigin:true, nodeJoints:{ a:{axis:"A",direction:"x",sign:1}, c:{axis:"C",direction:"z",sign:-1}, x:{axis:"X",direction:"x",sign:1} } }] };
|
||||
const bc = changeMachineType(ac,"XYZBC"); assert.equal(bc.coordinates,"XYZBC"); assert.equal(bc.parts[0].nodeJoints.a,undefined); assert.deepEqual(bc.parts[0].nodeJoints.c,ac.parts[0].nodeJoints.c); assert.equal(ac.parts[0].nodeJoints.a.axis,"A");
|
||||
let types={current:ac,past:[],future:[]};types=machineDraftHistory(types,{type:"edit",change:d=>changeMachineType(d,"XYZBC")});types=machineDraftHistory(types,{type:"undo"});assert.equal(types.current,ac);
|
||||
console.log(JSON.stringify({report_id:'GLB-MGR-E14-HISTORY',status:'verified',checks:['undo/redo','duplicate edits ignored','new edit discards redo','30-entry bound','clear preserves draft','machine type change preserves general axes and clears incompatible tilt','undo restores type and bindings']}));
|
||||
|
||||
51
scripts/test_machine_manager_type.mjs
Normal file
51
scripts/test_machine_manager_type.mjs
Normal file
@@ -0,0 +1,51 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import * as THREE from '../web/react/node_modules/three/build/three.module.js';
|
||||
import { GLTFExporter } from '../web/react/node_modules/three/examples/jsm/exporters/GLTFExporter.js';
|
||||
globalThis.FileReader = class { readAsArrayBuffer(blob) { blob.arrayBuffer().then((result) => { this.result = result; this.onloadend?.(); }); } readAsDataURL(blob) { blob.arrayBuffer().then((result) => { this.result = `data:${blob.type};base64,${Buffer.from(result).toString('base64')}`; this.onloadend?.(); }); } };
|
||||
const scene = new THREE.Scene();scene.name='test-machine';
|
||||
const add = (name,parent) => { const n=new THREE.Mesh(new THREE.BoxGeometry(.04,.04,.04),new THREE.MeshStandardMaterial({color:0x75aab2}));n.name=name;n.position.set(.1,.1,.1);parent.add(n);return n; };
|
||||
const x=add('joint-X',scene), y=add('joint-Y',x);add('joint-Z',y);const tilt=add('joint-tilt',scene);add('joint-C',tilt);
|
||||
const bytes=Buffer.from(await new GLTFExporter().parseAsync(scene,{binary:true,trs:true}));
|
||||
const pw=await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE ?? '/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.js'));
|
||||
const browser=await (pw.default??pw).chromium.launch({headless:true,executablePath:'/usr/bin/google-chrome',args:['--no-sandbox','--use-angle=swiftshader','--enable-unsafe-swiftshader']});
|
||||
const page=await browser.newPage({viewport:{width:1440,height:900}});const c=id=>page.locator(`[data-axis-control="${id}"]`);
|
||||
const output=new URL('../coverage/glb-machine-manager/machine-type/',import.meta.url);await mkdir(output,{recursive:true});
|
||||
const report={report_id:'GLB-MGR-E15',url:process.env.MACHINE_MANAGER_URL??'http://127.0.0.1:5180/',status:'running',checks:[],errors:[]};
|
||||
page.on('pageerror',e=>report.errors.push(e.message));page.on('console',m=>{if(['error','warning'].includes(m.type()))report.errors.push(m.text());});
|
||||
const idle=()=>page.waitForFunction(()=>document.querySelector('main.axis-app')?.dataset.axisProgramLoading==='false'&&document.querySelector('main.axis-app')?.dataset.axisStateSource==='linuxcnc-wasm');
|
||||
const choose=async(name)=>{await c('machine-manager-tab-structure').click();await c('machine-tree-search').fill(name);await c('machine-tree-node').filter({has:page.locator('span').filter({hasText:new RegExp(`^${name}$`)})}).first().click();};
|
||||
try{
|
||||
await page.goto(report.url);await idle();await c('machine-manager-settings').click();
|
||||
assert.equal(await c('machine-manager-type').inputValue(),'XYZAC');
|
||||
await c('machine-manager-type').selectOption('XYZBC');
|
||||
assert.match(await c('workspace-case-select').inputValue(),/xyzac-trt/);
|
||||
await c('machine-manager-files').setInputFiles({name:'machine.glb',mimeType:'model/gltf-binary',buffer:bytes});
|
||||
for(const [axis,name] of [['X','joint-X'],['Y','joint-Y'],['Z','joint-Z'],['B','joint-tilt'],['C','joint-C']]){await choose(name);await c('machine-tree-axis-role').selectOption(axis);}
|
||||
assert.equal(await c('machine-manager-export').isEnabled(),true);
|
||||
await c('machine-manager-type').selectOption('XYZAC');await choose('joint-tilt');assert.equal(await c('machine-tree-axis-role').inputValue(),'');assert.equal(await c('machine-manager-export').isDisabled(),true);assert.match(await c('machine-manager-validation').textContent(),/A/);
|
||||
await c('machine-manager-undo').click();assert.equal(await c('machine-manager-type').inputValue(),'XYZBC');assert.equal(await c('machine-tree-axis-role').inputValue(),'B');
|
||||
await c('machine-manager-redo').click();assert.equal(await c('machine-manager-type').inputValue(),'XYZAC');await c('machine-manager-undo').click();
|
||||
await c('machine-manager-tab-calibration').click();await page.locator('fieldset.machine-zero').getByLabel('B',{exact:true}).fill('12');
|
||||
const promise=page.waitForEvent('download');await c('machine-manager-export').click();const download=await promise;assert.equal(download.suggestedFilename(),'managed-xyzbc.glb');const saved=await readFile(await download.path());await writeFile(new URL('test-xyzbc.glb',output),saved);
|
||||
const json=JSON.parse(saved.toString('utf8',20,20+saved.readUInt32LE(12)));assert.equal(json.scenes[0].extras.linuxcncJointModel.coordinates,'XYZBC');assert.equal(json.scenes[0].extras.linuxcncJointModel.calibration.physicalZero.b,12);assert.deepEqual(json.scenes[0].extras.linuxcncJointModel.controller.axisOrder,[...'XYZBC']);
|
||||
report.checks.push('dialog selects BC while controller stays AC; switching drops incompatible tilt only; undo/redo restores type and bindings; saved GLB contains BC metadata');
|
||||
await page.keyboard.press('Escape');await c('workspace-case-select').selectOption('linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini');await idle();await c('workspace-case-select').selectOption('linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini');await idle();
|
||||
await c('machine-manager-settings').click();assert.equal(await c('machine-manager-type').inputValue(),'XYZBC');await c('machine-manager-type').selectOption('XYZAC');
|
||||
await c('machine-manager-files').setInputFiles({name:'managed-xyzbc.glb',mimeType:'model/gltf-binary',buffer:saved});await page.waitForFunction(()=>document.querySelector('[data-axis-control="machine-manager-type"]')?.value==='XYZBC');
|
||||
assert.equal(await page.locator('[data-machine-manager-preview]').getAttribute('data-machine-manager-part-count'),'1');await choose('joint-tilt');assert.equal(await c('machine-tree-axis-role').inputValue(),'B');
|
||||
await c('machine-manager-tab-calibration').click();assert.equal(await page.locator('fieldset.machine-zero').getByLabel('B',{exact:true}).inputValue(),'12');
|
||||
await c('machine-manager-undo').click();assert.equal(await c('machine-manager-type').inputValue(),'XYZAC');await c('machine-manager-redo').click();assert.equal(await c('machine-manager-type').inputValue(),'XYZBC');
|
||||
report.checks.push('controller workspace changes do not change draft type; reopening saved machine restores type, calibration and hierarchy in one undoable transaction');
|
||||
await page.screenshot({path:new URL('dialog-type.png',output).pathname});
|
||||
await c('machine-manager-apply').click();await page.waitForFunction(()=>document.querySelector('[data-axis-control="workspace-case-select"]')?.value.includes('xyzbc-trt'));await idle();assert.equal(await c('machine-manager-type').inputValue(),'XYZBC');
|
||||
await page.keyboard.press('Escape');await c('workspace-case-select').selectOption('linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini');await idle();await c('machine-manager-settings').click();
|
||||
report.checks.push('applying BC design from AC controller changes controller type without altering design draft');
|
||||
await page.keyboard.press('Escape');await c('machine-glb-settings').click();await c('machine-glb-file').setInputFiles({name:'managed-xyzbc.glb',mimeType:'model/gltf-binary',buffer:saved});
|
||||
await page.waitForFunction(()=>document.querySelector('.xyzac-machine-view')?.dataset.machineJointState&&document.querySelector('[data-axis-control="workspace-case-select"]')?.value.includes('xyzbc-trt'));await idle();
|
||||
const geometry=JSON.parse(await page.locator('.xyzac-machine-view').getAttribute('data-machine-manager-geometry'));assert.equal(geometry.coordinates,'XYZBC');assert.equal(geometry.physicalZero.b,12);
|
||||
await page.keyboard.press('Escape');await page.screenshot({path:new URL('controller-import.png',output).pathname});
|
||||
report.checks.push('controller import accepts saved BC GLB from AC workspace and automatically switches to BC with physical zero');assert.deepEqual(report.errors,[]);report.status='verified';
|
||||
}catch(e){report.status='failed';report.error=e.stack;await page.screenshot({path:new URL('failure.png',output).pathname}).catch(()=>{});throw e;}finally{await writeFile(new URL('browser.report.json',output),JSON.stringify(report,null,2)+'\n');await browser.close();}
|
||||
console.log(JSON.stringify(report));
|
||||
Reference in New Issue
Block a user