Add RTCP simulation QA updates
This commit is contained in:
@@ -84,8 +84,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!doc.querySelector(".machine-preview")) {
|
||||
throw new Error("missing machine preview");
|
||||
if (!doc.querySelector(".toolpath-preview")) {
|
||||
throw new Error("missing toolpath preview");
|
||||
}
|
||||
let canvas = doc.querySelector("[data-five-axis-canvas]");
|
||||
if (!canvas) {
|
||||
@@ -95,13 +95,17 @@
|
||||
canvas.dataset.threeReady !== "true" ||
|
||||
canvas.dataset.threeFrameApi !== "web-rtcp-5axis-motion-frame" ||
|
||||
canvas.dataset.threeSceneMode !== "program-preview-and-tool-execution" ||
|
||||
canvas.dataset.threePreviewScope !== "machine-reference-and-toolpath" ||
|
||||
canvas.dataset.threeMachineReferenceModel !== "webgl-five-axis-reference" ||
|
||||
canvas.dataset.threeCameraControls !== "orbit-pan-zoom" ||
|
||||
Number(canvas.dataset.threePathPoints ?? 0) < 64 ||
|
||||
Number(canvas.dataset.threeSceneObjects ?? 0) < 5 ||
|
||||
Number(canvas.dataset.threeSceneObjects ?? 0) < 12 ||
|
||||
!canvas.dataset.threeToolhead ||
|
||||
!canvas.dataset.threeToolAxis ||
|
||||
!canvas.dataset.threeTcpPose ||
|
||||
canvas.dataset.threeToolExecutionMarker !== "true"
|
||||
canvas.dataset.threeToolExecutionMarker !== "true" ||
|
||||
canvas.dataset.threeTcpMarker !== "sphere" ||
|
||||
canvas.dataset.threeToolAxisMarker !== "line"
|
||||
) {
|
||||
throw new Error(`Three.js preview did not expose ready render state: ${JSON.stringify(canvas.dataset)}`);
|
||||
}
|
||||
@@ -428,6 +432,10 @@
|
||||
}
|
||||
if (
|
||||
canvas.dataset.threeSceneMode !== "program-preview-and-tool-execution" ||
|
||||
canvas.dataset.threePreviewScope !== "machine-reference-and-toolpath" ||
|
||||
canvas.dataset.threeMachineReferenceModel !== "webgl-five-axis-reference" ||
|
||||
canvas.dataset.threeTcpMarker !== "sphere" ||
|
||||
canvas.dataset.threeToolAxisMarker !== "line" ||
|
||||
canvas.dataset.threeToolpathPreviewSource !== "linuxcnc_interpreter_canonical_motion" ||
|
||||
canvas.dataset.threeToolExecutionTraceSource !== "linuxcnc_tp_samples_or_task_motion_hal_feedback" ||
|
||||
canvas.dataset.threePathFitBounds !== "ok" ||
|
||||
@@ -813,23 +821,38 @@
|
||||
});
|
||||
|
||||
function assertCanvasNonblank(canvas, context) {
|
||||
const stats = canvasPixelStats(canvas, context);
|
||||
if (stats.nonBlackRatio <= 0.02) {
|
||||
throw new Error(`${context}: non-black pixel ratio too low ${JSON.stringify(stats)}`);
|
||||
}
|
||||
if (stats.averageLuminance <= 5) {
|
||||
throw new Error(`${context}: average luminance too low ${JSON.stringify(stats)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function canvasPixelStats(canvas, context) {
|
||||
const gl = canvas.getContext("webgl2") || canvas.getContext("webgl");
|
||||
if (!gl) {
|
||||
throw new Error(`${context}: missing WebGL context`);
|
||||
}
|
||||
const pixel = new Uint8Array(4);
|
||||
gl.readPixels(
|
||||
Math.floor(canvas.width / 2),
|
||||
Math.floor(canvas.height / 2),
|
||||
1,
|
||||
1,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
pixel,
|
||||
);
|
||||
if (pixel[0] === 0 && pixel[1] === 0 && pixel[2] === 0 && pixel[3] === 0) {
|
||||
throw new Error(`${context}: center pixel was blank`);
|
||||
const width = Math.max(canvas.width || 0, 1);
|
||||
const height = Math.max(canvas.height || 0, 1);
|
||||
const pixels = new Uint8Array(width * height * 4);
|
||||
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||
let nonBlack = 0;
|
||||
let luminance = 0;
|
||||
for (let index = 0; index < pixels.length; index += 4) {
|
||||
const luma = pixels[index] * 0.2126 + pixels[index + 1] * 0.7152 + pixels[index + 2] * 0.0722;
|
||||
luminance += luma;
|
||||
if (luma > 8) nonBlack += 1;
|
||||
}
|
||||
const total = width * height;
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
nonBlackRatio: nonBlack / total,
|
||||
averageLuminance: luminance / total,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -63,11 +63,27 @@ assert.equal(state.rtcpState, "on");
|
||||
|
||||
store.dispatch({ type: "SET_MODE", mode: "manual" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "HOME" });
|
||||
state = store.getState();
|
||||
const homePose = { ...state.axisPose };
|
||||
assert.equal(homePose.x, 43);
|
||||
assert.equal(homePose.y, -32.15);
|
||||
assert.equal(homePose.z, -11.306);
|
||||
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 0.5 });
|
||||
await waitForTaskHal(store);
|
||||
state = store.getState();
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.teleopMode, 1);
|
||||
assert.equal(state.programRuntimeFeedback.sourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||
assertNear(state.axisPose.x, homePose.x + 0.5, "task/HAL JOG X+ should keep HOME work-pose continuity");
|
||||
assertNear(state.axisPose.y, homePose.y, "task/HAL JOG X+ should not reset Y");
|
||||
assertNear(state.axisPose.z, homePose.z, "task/HAL JOG X+ should not reset Z");
|
||||
|
||||
store.dispatch({ type: "JOG", axis: "y", direction: -1, increment: 0.5 });
|
||||
await waitForTaskHal(store);
|
||||
state = store.getState();
|
||||
assertNear(state.axisPose.x, homePose.x + 0.5, "task/HAL JOG Y- should not reset X");
|
||||
assertNear(state.axisPose.y, homePose.y - 0.5, "task/HAL JOG Y- should keep HOME work-pose continuity");
|
||||
assertNear(state.axisPose.z, homePose.z, "task/HAL JOG Y- should not reset Z");
|
||||
|
||||
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
||||
await waitForTaskHal(store);
|
||||
@@ -94,3 +110,7 @@ async function waitForTaskHal(store) {
|
||||
}
|
||||
throw new Error("task/HAL store command did not settle");
|
||||
}
|
||||
|
||||
function assertNear(actual, expected, message) {
|
||||
assert.equal(Math.abs(Number(actual) - Number(expected)) < 1e-9, true, `${message}: ${actual} !== ${expected}`);
|
||||
}
|
||||
|
||||
@@ -428,4 +428,90 @@ assert.equal(state.machine.powerOn, false);
|
||||
assert.equal(state.machine.taskState, "estop-reset");
|
||||
assert.equal(state.operatorMessage, "estop reset; machine off");
|
||||
|
||||
const taskHalSwitchkinsStore = createSimulationStore({
|
||||
programStartLine: 1,
|
||||
activeLine: 5,
|
||||
kinsType: "tcp-xyzac",
|
||||
rtcpState: "on",
|
||||
programExecutionSourceMode: "linuxcnc-interpreter-wasm",
|
||||
programExecution: {
|
||||
sourceMode: "linuxcnc-interpreter-wasm",
|
||||
motion: [
|
||||
{
|
||||
type: "STRAIGHT_TRAVERSE",
|
||||
line: 5,
|
||||
axes: { x: 1, y: 2, z: 3, a: 10, c: 20 },
|
||||
switchkinsType: 1,
|
||||
kinsType: "tcp",
|
||||
},
|
||||
{
|
||||
type: "STRAIGHT_FEED",
|
||||
line: 20,
|
||||
axes: { x: 2, y: 3, z: 4, a: 0, c: 0 },
|
||||
switchkinsType: 0,
|
||||
kinsType: "identity",
|
||||
},
|
||||
],
|
||||
summary: {
|
||||
motionEventCount: 2,
|
||||
switchkinsEventCount: 2,
|
||||
switchkinsCodes: ["M428", "M429"],
|
||||
},
|
||||
},
|
||||
});
|
||||
taskHalSwitchkinsStore.dispatch({
|
||||
type: "TASK_HAL_STATUS_APPLIED",
|
||||
status: taskHalStatusForSwitchkinsLine({ activeLine: 5, switchkinsType: 0 }),
|
||||
operatorMessage: "task/HAL status retained program TCP switchkins",
|
||||
});
|
||||
state = taskHalSwitchkinsStore.getState();
|
||||
assert.equal(state.activeLine, 5);
|
||||
assert.equal(state.kinsType, "tcp-xyzac");
|
||||
assert.equal(state.rtcpState, "on");
|
||||
|
||||
taskHalSwitchkinsStore.dispatch({
|
||||
type: "TASK_HAL_STATUS_APPLIED",
|
||||
status: taskHalStatusForSwitchkinsLine({ activeLine: 20, switchkinsType: 0 }),
|
||||
operatorMessage: "task/HAL status applied program identity switchkins",
|
||||
});
|
||||
state = taskHalSwitchkinsStore.getState();
|
||||
assert.equal(state.activeLine, 20);
|
||||
assert.equal(state.kinsType, "identity");
|
||||
assert.equal(state.rtcpState, "off");
|
||||
|
||||
console.log("rtcp_store_smoke=ok");
|
||||
|
||||
function taskHalStatusForSwitchkinsLine({ activeLine, switchkinsType }) {
|
||||
return {
|
||||
semanticBoundary: "linuxcnc_task_motion_hal_wasm_simulation_runtime",
|
||||
task: {
|
||||
state: "ON",
|
||||
mode: "AUTO",
|
||||
interpState: "READING",
|
||||
execState: "WAITING_FOR_MOTION",
|
||||
},
|
||||
motionStatus: {
|
||||
motion: {
|
||||
programLine: activeLine,
|
||||
motionType: 1,
|
||||
switchkinsType,
|
||||
currentVel: 1,
|
||||
requestedVel: 1,
|
||||
inPosition: false,
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
taskState: "on",
|
||||
taskMode: "auto",
|
||||
interpState: "reading",
|
||||
activeLine,
|
||||
switchkinsType,
|
||||
axisPoseFrame: "work",
|
||||
axisPose: { x: 1, y: 2, z: 3, a: 10, b: 0, c: 20 },
|
||||
currentVelocity: 60,
|
||||
servoCycle: 1,
|
||||
taskCycle: 1,
|
||||
motionQueueDepth: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user