From 23d07e72d17067022b27f6e039bcb0054a46c242 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Tue, 22 Sep 2026 07:36:23 +0800 Subject: [PATCH] Add LinuxCNC five-axis case catalog --- docs/linuxcnc-five-axis-case-catalog.md | 34 +++ scripts/validate_five_axis_catalog.mjs | 21 ++ web/react/src/MachineManagerPanel.tsx | 11 +- web/react/src/legacy.d.ts | 3 + web/react/src/styles.css | 18 ++ web/workspace/five-axis-machine-catalog.json | 222 +++++++++++++++++++ web/workspace/five-axis-machine-catalog.mjs | 20 ++ 7 files changed, 326 insertions(+), 3 deletions(-) create mode 100644 docs/linuxcnc-five-axis-case-catalog.md create mode 100644 scripts/validate_five_axis_catalog.mjs create mode 100644 web/workspace/five-axis-machine-catalog.json create mode 100644 web/workspace/five-axis-machine-catalog.mjs diff --git a/docs/linuxcnc-five-axis-case-catalog.md b/docs/linuxcnc-five-axis-case-catalog.md new file mode 100644 index 0000000..963de0f --- /dev/null +++ b/docs/linuxcnc-five-axis-case-catalog.md @@ -0,0 +1,34 @@ +# LinuxCNC 五轴机床案例目录 + +本目录来自仓库内的 LinuxCNC 源码、仿真配置和 G 代码案例。网页机床管理器读取同一份 [five-axis-machine-catalog.json](../web/workspace/five-axis-machine-catalog.json),因此案例清单、运动学名称、坐标轴、源码路径和 Web/WASM 能力状态不会在界面中重复硬编码。 + +## 案例与适用范围 + +| 结构族 | LinuxCNC 运动学 | 坐标/关节 | 本地案例 | Web/WASM 状态 | +|---|---|---:|---|---| +| 转台倾转 AC | `xyzac-trt-kins` | XYZAC / 5 | `configs/sim/axis/vismach/5axis/table-rotary-tilting`、gmoccapy TRT | 已支持正解、逆解、RTCP | +| 转台倾转 BC | `xyzbc-trt-kins` | XYZBC / 5 | `configs/sim/axis/vismach/5axis/table-rotary-tilting`、gmoccapy TRT | 已支持正解、逆解、RTCP | +| 双转台 AB | `xyzab_tdr_kins` | XYZAB / 5 | `configs/sim/axis/vismach/5axis/table-dual-rotary` | 已有 LinuxCNC 案例;需要 Web/WASM 适配器 | +| Bridge Mill | `5axiskins` | XYZBCW/Y / 7 | `configs/sim/axis/vismach/5axis/bridgemill` | 需要支持重复轴和 W 轴的适配器 | +| MAX5 | `maxkins` | XYZBC / 6 | `configs/sim/axis/vismach/5axis/max5` | 需要 maxkins WASM 适配器 | +| Hexapod | `genhexkins` | XYZABC / 6 | `configs/sim/axis/vismach/hexapod-sim` | 需要并联平台参数和迭代求解适配器 | +| Pentapod | `pentakins` | XYZAB / 5 | `configs/sim/axis/pentapod` | 需要五支链参数和迭代求解适配器 | +| PUMA | `pumakins` | XYZABC / 6 | `configs/sim/axis/vismach/puma/puma.ini` | 需要串联机器人适配器 | +| PUMA 560 | `genserkins` | XYZABC / 6 | `configs/sim/axis/vismach/puma/puma560.ini` | 需要 DH/串联机器人适配器 | +| 直角坐标基线 | `trivkins` | XYZAB / 5 | qtdragon、woodpecker、MAX5 trivkins | 仅用于轴映射验证,不提供非线性 RTCP | + +PUMA 和 Hexapod 是六自由度案例,仍然纳入目录是因为 LinuxCNC 的五轴加工/RTCP 工作流可能使用其中的五轴姿态子集;在配置器中会显示“六自由度”标签。SCARA、MELFA、SCORBOT 等同样是 LinuxCNC 的机器人运动学案例,但不属于本目录的五轴机床案例,因此不会伪装成五轴铣床类型。 + +## 适配边界 + +当前 Web/WASM 运动链已经真实接通的是 XYZAC/XYZBC TRT。其它案例已经进入机床管理器的案例库,可以查看源码配置、G 代码和所需结构参数,但在对应适配器完成以前不能在控制面板中声称已经具备正解、逆解或 RTCP。每个 GLB 机床文档应保存 `kinematics`、`coordinates`、`joints`、轴层级、局部原点、关节参数、物理零点和运动学专用参数,适配器再根据这些数据驱动 Three.js 层级。 + +## 校验 + +在仓库根目录执行: + +```bash +node scripts/validate_five_axis_catalog.mjs +``` + +该命令逐项读取本地案例配置、示例 G 代码、README 和运动学源码,确认目录中的路径仍然存在。官方依据包括 [LinuxCNC 5-Axis Kinematics](https://linuxcnc.org/docs/html/motion/5-axis-kinematics.html)、[kins(9)](https://linuxcnc.org/docs/html/man/man9/kins.9.html) 和 [switchable kinematics](https://linuxcnc.org/docs/html/motion/switchkins.html)。 diff --git a/scripts/validate_five_axis_catalog.mjs b/scripts/validate_five_axis_catalog.mjs new file mode 100644 index 0000000..ec80f5f --- /dev/null +++ b/scripts/validate_five_axis_catalog.mjs @@ -0,0 +1,21 @@ +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { FIVE_AXIS_CASES } from "../web/workspace/five-axis-machine-catalog.mjs"; + +const root = resolve(new URL("..", import.meta.url).pathname); +const checks = []; +const errors = []; +for (const item of FIVE_AXIS_CASES) { + const paths = [...item.localCases, item.kinematicsSource]; + for (const relative of paths) { + try { + const text = await readFile(resolve(root, relative), "utf8"); + checks.push({ case: item.id, path: relative, bytes: Buffer.byteLength(text) }); + } catch (error) { + errors.push({ case: item.id, path: relative, error: error instanceof Error ? error.message : String(error) }); + } + } +} +const report = { report_id: "FIVE-AXIS-CATALOG-LOCAL-SOURCE", status: errors.length ? "failed" : "verified", caseCount: FIVE_AXIS_CASES.length, fileCount: checks.length, checks, errors }; +console.log(JSON.stringify(report, null, 2)); +if (errors.length) process.exitCode = 1; diff --git a/web/react/src/MachineManagerPanel.tsx b/web/react/src/MachineManagerPanel.tsx index 54a3e1a..cf6b25a 100644 --- a/web/react/src/MachineManagerPanel.tsx +++ b/web/react/src/MachineManagerPanel.tsx @@ -13,6 +13,7 @@ import { downloadMachine, listSavedMachines, saveMachineCopy, readMachineCopy, d import { machineDocumentFilename } from "./machine-document"; import { MachineNumberInput } from "./MachineManagerInputs"; import { inspectMachineGlb, MAX_MACHINE_BYTES, type Vec3 } from "./machine-urdf"; +import { FIVE_AXIS_CASES, FIVE_AXIS_STATUS_LABELS } from "../../workspace/five-axis-machine-catalog.mjs"; type Props = { currentMachine?: ImportedMachine | null; snapshot?: XyzacVisualSource | null; coordinates: "XYZAC" | "XYZBC"; disabled: boolean; onApply: (machine: ImportedMachine) => Promise }; type TestPose = { x: number; y: number; z: number; a: number; b: number; c: number }; @@ -21,6 +22,8 @@ const zero3 = (): Vec3 => [0, 0, 0]; type EditorTab = "structure" | "motion" | "calibration" | "files"; type PreviewCommand = { action: "fit" | "selected" | "front" | "top"; serial: number }; const formatted = (value: number) => Number.isFinite(value) ? value.toFixed(3) : "—"; +const catalogCoordinateOptions = [...new Set(FIVE_AXIS_CASES.map((item) => item.coordinates))]; +const supportedCoordinateOptions = new Set(FIVE_AXIS_CASES.filter((item) => item.webWasm === "supported").map((item) => item.coordinates)); function ManagerPreview({ coordinates, parts, selectedId, selectedNodePath, testPose, onMotionState, command }: { command: PreviewCommand; coordinates: "XYZAC" | "XYZBC"; parts: MachinePartDraft[]; selectedId: string | null; selectedNodePath: string; testPose: TestPose; onMotionState: (state: Record) => void }) { type PreviewHost = HTMLDivElement & { machineManagerView?: (action: PreviewCommand["action"]) => void; machineManagerSync?: (parts: MachinePartDraft[], selectedId: string | null, selectedNodePath: string, testPose: TestPose, coordinates: "XYZAC" | "XYZBC") => void }; @@ -358,10 +361,11 @@ export function MachineManagerPanel({ coordinates: controllerCoordinates, disabl filePickerOpen.current} onClose={() => setOpen(false)}>
2 · 结构与关节 → 3 · 验证与标定 → 4 · 保存 GLB{coordinates === "XYZAC" ? "A 绕 X 倾转,C 绕 Z 回转" : "B 绕 Y 倾转,C 绕 Z 回转"} · 控制面板当前 {controllerCoordinates}{coordinates !== controllerCoordinates ? ";应用或导入模型时切换" : ""}
+ }}>{catalogCoordinateOptions.map((axisSet) => { const caseItem = FIVE_AXIS_CASES.find((item) => item.coordinates === axisSet); return ; })}
2 · 结构与关节 → 3 · 验证与标定 → 4 · 保存 GLB{coordinates === "XYZAC" ? "A 绕 X 倾转,C 绕 Z 回转" : "B 绕 Y 倾转,C 绕 Z 回转"} · 控制面板当前 {controllerCoordinates}{coordinates !== controllerCoordinates ? ";应用或导入模型时切换" : ""} · 其它源码类型见“文件与恢复”的案例库
{parts.length} 个模型 · {bindings.filter((b) => coordinates.includes(b.dof.axis)).length}/5 轴已绑定{!parts.length ? "新建机床" : savedSignature === signature ? "与保存版本一致" : applied === signature ? "已应用当前配置" : exported === signature ? "已导出 · 尚未应用" : "配置尚未应用"}
{[...coordinates].map((axis) => { const binding = bindings.find((b) => b.dof.axis === axis); return ; })}
@@ -386,6 +390,7 @@ export function MachineManagerPanel({ coordinates: controllerCoordinates, disabl