继续完成 web-rtcp-5axis-sim-plan
结论:完成 LinuxCNC kinematics WASM ABI 覆盖,并将 web-rtcp-5axis-sim-plan 的 RTCP frame/boundary adapter 接到 xyzac-trt kinematics SDK;Node、build、browser smoke 验证通过。
This commit is contained in:
203
web-rtcp-5axis-sim-plan/app/src/visualization/five-axis-scene.js
Normal file
203
web-rtcp-5axis-sim-plan/app/src/visualization/five-axis-scene.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import * as THREE from "../vendor/three/three.module.js";
|
||||
|
||||
const scenes = new WeakMap();
|
||||
|
||||
export function renderFiveAxisScene(canvas, state) {
|
||||
const preview = scenes.get(canvas) || createScene(canvas);
|
||||
scenes.set(canvas, preview);
|
||||
|
||||
resizeRenderer(preview);
|
||||
updateMachinePose(preview, state);
|
||||
preview.renderer.render(preview.scene, preview.camera);
|
||||
|
||||
const pointCount = preview.pathLine.geometry.getAttribute("position").count;
|
||||
canvas.dataset.threeReady = "true";
|
||||
canvas.dataset.threeRevision = THREE.REVISION;
|
||||
canvas.dataset.threePathPoints = String(pointCount);
|
||||
canvas.dataset.threeSceneObjects = String(countSceneObjects(preview.scene));
|
||||
canvas.dataset.threeToolhead = JSON.stringify(toRoundedVector(preview.toolGroup.position));
|
||||
canvas.dataset.threeToolAxis = JSON.stringify(toRoundedVector(state.toolAxisVector));
|
||||
canvas.dataset.threeTcpPose = JSON.stringify(toRoundedPose(state.tcpPose));
|
||||
canvas.dataset.threeRtcpState = state.rtcpState;
|
||||
canvas.dataset.threeSelectedView = state.preview.selectedView;
|
||||
canvas.dataset.threeFrameApi = state.rtcpFrame.apiName;
|
||||
}
|
||||
|
||||
function createScene(canvas) {
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
canvas,
|
||||
antialias: true,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
renderer.setClearColor(0x050505, 1);
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(38, 1, 0.1, 100);
|
||||
camera.position.set(4.2, -6.4, 4.8);
|
||||
camera.lookAt(0, 0, 0);
|
||||
|
||||
const ambient = new THREE.AmbientLight(0xffffff, 0.46);
|
||||
const key = new THREE.DirectionalLight(0xffffff, 1.1);
|
||||
key.position.set(3, -5, 7);
|
||||
scene.add(ambient, key);
|
||||
|
||||
const grid = new THREE.GridHelper(6.8, 12, 0x444444, 0x242424);
|
||||
grid.rotation.x = Math.PI / 2;
|
||||
scene.add(grid);
|
||||
|
||||
const axes = new THREE.AxesHelper(1.25);
|
||||
axes.position.set(-2.7, -2.25, -1.05);
|
||||
scene.add(axes);
|
||||
|
||||
const envelope = buildEnvelope();
|
||||
scene.add(envelope);
|
||||
|
||||
const tableGroup = new THREE.Group();
|
||||
const table = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(2.55, 1.9, 0.16),
|
||||
new THREE.MeshStandardMaterial({ color: 0x353535, roughness: 0.72, metalness: 0.2 }),
|
||||
);
|
||||
const platter = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(0.72, 0.72, 0.13, 48),
|
||||
new THREE.MeshStandardMaterial({ color: 0x4f5960, roughness: 0.62, metalness: 0.35 }),
|
||||
);
|
||||
platter.rotation.x = Math.PI / 2;
|
||||
platter.position.z = 0.14;
|
||||
tableGroup.add(table, platter);
|
||||
scene.add(tableGroup);
|
||||
|
||||
const pathLine = buildToolpath();
|
||||
scene.add(pathLine);
|
||||
|
||||
const toolGroup = new THREE.Group();
|
||||
const toolBody = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(0.035, 0.055, 0.86, 24),
|
||||
new THREE.MeshStandardMaterial({ color: 0x26d7df, emissive: 0x073a3d, roughness: 0.32 }),
|
||||
);
|
||||
toolBody.rotation.x = Math.PI / 2;
|
||||
toolBody.position.z = 0.43;
|
||||
const tcpPoint = new THREE.Mesh(
|
||||
new THREE.SphereGeometry(0.075, 24, 16),
|
||||
new THREE.MeshStandardMaterial({ color: 0x1ffff4, emissive: 0x094f4f, roughness: 0.2 }),
|
||||
);
|
||||
const toolAxis = new THREE.Line(
|
||||
new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), new THREE.Vector3(0, 0, 1)]),
|
||||
new THREE.LineBasicMaterial({ color: 0x21f2f2, linewidth: 2 }),
|
||||
);
|
||||
toolGroup.add(toolBody, tcpPoint, toolAxis);
|
||||
scene.add(toolGroup);
|
||||
|
||||
const preview = {
|
||||
renderer,
|
||||
scene,
|
||||
camera,
|
||||
tableGroup,
|
||||
toolGroup,
|
||||
toolAxis,
|
||||
pathLine,
|
||||
};
|
||||
resizeRenderer(preview);
|
||||
return preview;
|
||||
}
|
||||
|
||||
function buildEnvelope() {
|
||||
const geometry = new THREE.BoxGeometry(5.8, 4.3, 2.6);
|
||||
const edges = new THREE.EdgesGeometry(geometry);
|
||||
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xcc2525 }));
|
||||
line.position.z = 0.2;
|
||||
return line;
|
||||
}
|
||||
|
||||
function buildToolpath() {
|
||||
const points = [];
|
||||
for (let index = 0; index < 72; index += 1) {
|
||||
const t = index / 71;
|
||||
const x = -2.3 + t * 4.6;
|
||||
const y = Math.sin(t * Math.PI * 13) * 0.28;
|
||||
const z = -0.75 + Math.sin(t * Math.PI * 2) * 0.36;
|
||||
points.push(new THREE.Vector3(x, y, z));
|
||||
}
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
return new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0xf4f4f4 }));
|
||||
}
|
||||
|
||||
function updateMachinePose(preview, state) {
|
||||
const tcp = state.tcpPose;
|
||||
const tool = state.toolAxisVector;
|
||||
const tcpPosition = new THREE.Vector3(
|
||||
clamp(tcp.x * 0.035, -2.7, 2.7),
|
||||
clamp(tcp.y * 0.035, -2.0, 2.0),
|
||||
clamp(tcp.z * 0.04 + 0.35, -1.1, 1.9),
|
||||
);
|
||||
const toolAxisVector = new THREE.Vector3(tool.x, tool.y, tool.z || 1).normalize();
|
||||
|
||||
preview.toolGroup.position.copy(tcpPosition);
|
||||
preview.toolGroup.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), toolAxisVector);
|
||||
preview.toolAxis.geometry.setFromPoints([
|
||||
new THREE.Vector3(0, 0, 0),
|
||||
toolAxisVector.clone().multiplyScalar(state.rtcpState === "on" ? 1.2 : 0.85),
|
||||
]);
|
||||
preview.tableGroup.rotation.x = THREE.MathUtils.degToRad(state.axisPose.a);
|
||||
preview.tableGroup.rotation.z = THREE.MathUtils.degToRad(state.axisPose.c);
|
||||
setCameraView(preview.camera, state.preview.selectedView);
|
||||
}
|
||||
|
||||
function setCameraView(camera, selectedView) {
|
||||
if (selectedView === "x") {
|
||||
camera.position.set(6, 0.02, 0.4);
|
||||
} else if (selectedView === "y") {
|
||||
camera.position.set(0.02, -6, 0.6);
|
||||
} else if (selectedView === "z") {
|
||||
camera.position.set(0.01, -0.02, 7);
|
||||
} else {
|
||||
camera.position.set(4.2, -6.4, 4.8);
|
||||
}
|
||||
camera.lookAt(0, 0, 0);
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
function resizeRenderer(preview) {
|
||||
const { canvas } = preview.renderer.domElement;
|
||||
const width = Math.max(canvas.clientWidth, 320);
|
||||
const height = Math.max(canvas.clientHeight, 240);
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
preview.renderer.setSize(width, height, false);
|
||||
}
|
||||
preview.camera.aspect = width / height;
|
||||
preview.camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
function countSceneObjects(object) {
|
||||
let count = 1;
|
||||
for (const child of object.children) {
|
||||
count += countSceneObjects(child);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function toRoundedVector(vector) {
|
||||
return {
|
||||
x: round(vector.x),
|
||||
y: round(vector.y),
|
||||
z: round(vector.z),
|
||||
};
|
||||
}
|
||||
|
||||
function toRoundedPose(pose) {
|
||||
return {
|
||||
x: round(pose.x),
|
||||
y: round(pose.y),
|
||||
z: round(pose.z),
|
||||
a: round(pose.a),
|
||||
c: round(pose.c),
|
||||
};
|
||||
}
|
||||
|
||||
function round(value) {
|
||||
return Math.round(Number(value) * 1000) / 1000;
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
Reference in New Issue
Block a user