继续完成 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:
2026-06-21 16:44:29 +08:00
parent a6eda3fbff
commit 626bcfe8e3
101 changed files with 101586 additions and 770 deletions

View 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);
}