Add LinuxCNC five-axis case catalog
This commit is contained in:
@@ -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<void> };
|
||||
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<string, MotionTransform>) => 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
|
||||
<DraggableDialog open={open} title={`三维机床管理 · ${coordinates}`} className="machine-manager-dialog" cancelGuard={() => filePickerOpen.current} onClose={() => setOpen(false)}>
|
||||
<div className="machine-manager-workbench" aria-busy={pending}>
|
||||
<div className="machine-type-step"><label><strong>1 · 机床类型</strong><select data-axis-control="machine-manager-type" aria-label="设计机床类型" value={coordinates} disabled={pending || followLive} onChange={(event) => {
|
||||
const next = event.target.value as "XYZAC" | "XYZBC";
|
||||
edit((draft) => changeMachineType(draft, next)); setTab("structure");
|
||||
const next = event.target.value;
|
||||
if (!supportedCoordinateOptions.has(next)) { setMessage(`${next} 已登记源码案例,但当前 Web/WASM 运动学适配尚未完成`); return; }
|
||||
edit((draft) => changeMachineType(draft, next as "XYZAC" | "XYZBC")); setTab("structure");
|
||||
setMessage(`设计机型已选择 ${next};保留模型层级和 XYZ/C 绑定,请重新配置 ${next[3]} 轴并核对标定。可撤销恢复。`);
|
||||
}}><option value="XYZAC">XYZAC · A/C 五轴转台</option><option value="XYZBC">XYZBC · B/C 五轴转台</option></select></label><div><span>2 · 结构与关节 → 3 · 验证与标定 → 4 · 保存 GLB</span><small data-axis-control="machine-manager-type-note">{coordinates === "XYZAC" ? "A 绕 X 倾转,C 绕 Z 回转" : "B 绕 Y 倾转,C 绕 Z 回转"} · 控制面板当前 {controllerCoordinates}{coordinates !== controllerCoordinates ? ";应用或导入模型时切换" : ""}</small></div></div>
|
||||
}}>{catalogCoordinateOptions.map((axisSet) => { const caseItem = FIVE_AXIS_CASES.find((item) => item.coordinates === axisSet); return <option key={axisSet} value={axisSet} disabled={!supportedCoordinateOptions.has(axisSet)}>{caseItem?.label ?? axisSet}{supportedCoordinateOptions.has(axisSet) ? "" : " · 待 WASM 适配"}</option>; })}</select></label><div><span>2 · 结构与关节 → 3 · 验证与标定 → 4 · 保存 GLB</span><small data-axis-control="machine-manager-type-note">{coordinates === "XYZAC" ? "A 绕 X 倾转,C 绕 Z 回转" : "B 绕 Y 倾转,C 绕 Z 回转"} · 控制面板当前 {controllerCoordinates}{coordinates !== controllerCoordinates ? ";应用或导入模型时切换" : ""} · 其它源码类型见“文件与恢复”的案例库</small></div></div>
|
||||
<div className="machine-manager-toolbar"><button type="button" data-axis-control="machine-document-new" disabled={pending || followLive} onClick={newDocument}>新建机床</button><button type="button" data-axis-control="machine-document-open" disabled={pending || followLive} onClick={() => beginFilePicker(openFile.current)}>打开已保存机床</button><button type="button" data-axis-control="machine-document-from-controller" disabled={!currentMachine?.scene.userData.linuxcncJointModel || pending || followLive} onClick={() => void editControllerModel()}>编辑控制面板机床</button><button type="button" data-axis-control="machine-manager-import" disabled={pending || followLive} onClick={() => beginFilePicker(file.current)}>导入 GLB 部件</button><button type="button" data-axis-control="machine-manager-undo" disabled={!history.past.length || pending || followLive} onClick={() => changeHistory("undo")}>撤销</button><button type="button" data-axis-control="machine-manager-redo" disabled={!history.future.length || pending || followLive} onClick={() => changeHistory("redo")}>重做</button><span>{parts.length} 个模型 · {bindings.filter((b) => coordinates.includes(b.dof.axis)).length}/5 轴已绑定</span><span className="machine-save-state" data-axis-control="machine-manager-draft-state">{!parts.length ? "新建机床" : savedSignature === signature ? "与保存版本一致" : applied === signature ? "已应用当前配置" : exported === signature ? "已导出 · 尚未应用" : "配置尚未应用"}</span></div>
|
||||
<div className="machine-axis-overview" aria-label="数控轴绑定总览">{[...coordinates].map((axis) => { const binding = bindings.find((b) => b.dof.axis === axis); return <button type="button" key={axis} data-axis-control={`machine-axis-locate-${axis.toLowerCase()}`} className={binding ? "bound" : "missing"} onClick={() => moveToBinding(axis)} title={binding ? `${binding.name} · 父级 ${binding.dof.direction.toUpperCase()} ${binding.dof.sign > 0 ? "+" : "−"}` : `待绑定 ${axis} 轴`}><strong>{axis}</strong><span>{binding?.name ?? "待绑定"}</span><small>{binding ? `${binding.dof.sign > 0 ? "+" : "−"}${binding.dof.direction.toUpperCase()}` : "选择节点"}</small></button>; })}</div>
|
||||
<div className="machine-manager-layout">
|
||||
@@ -386,6 +390,7 @@ export function MachineManagerPanel({ coordinates: controllerCoordinates, disabl
|
||||
<section id="machine-panel-files" role="tabpanel" aria-labelledby="machine-tab-files" hidden={tab !== "files"} className="machine-document-panel">
|
||||
<h3>机床文档</h3><label>模型名称<input data-axis-control="machine-document-name" maxLength={80} value={documentInfo.name} disabled={pending || followLive} placeholder={`${coordinates} 机床`} onChange={(event) => { const name = event.target.value; edit((draft) => ({ ...draft, documentInfo: { ...documentInfo, name } })); }} /></label>
|
||||
<label>设计说明<textarea data-axis-control="machine-document-description" maxLength={2000} value={documentInfo.description} disabled={pending || followLive} placeholder="用途、标定说明、版本变更…" onChange={(event) => { const description = event.target.value; edit((draft) => ({ ...draft, documentInfo: { ...documentInfo, description } })); }} /></label>
|
||||
<section className="machine-case-catalog" data-axis-control="machine-case-catalog"><header><h3>LinuxCNC 五轴案例库</h3><span>{FIVE_AXIS_CASES.length} 个结构/运动学案例</span></header><p>案例来源于仓库内 LinuxCNC 配置、示例 G 代码和运动学源码。选中 XYZAC/XYZBC 可直接进入当前 Web/WASM 工作流;其它类型保留完整参数和源码索引,待对应运动学适配器接入后启用。</p><ul>{FIVE_AXIS_CASES.map((item) => <li key={item.id} data-machine-case={item.id} className={item.coordinates === coordinates ? "active" : ""}><div><strong>{item.label}</strong><span>{item.kinematics} · {item.coordinates} · {item.joints} 关节</span><small>{item.description}</small></div><em className={`machine-case-status machine-case-status-${item.webWasm}`}>{FIVE_AXIS_STATUS_LABELS[item.webWasm]}</em><details><summary>查看本地案例与参数</summary><code>{item.localCases.join("\n")}</code><small>运动学源码:{item.kinematicsSource}<br />结构参数:{item.designParameters.join("、")}</small></details></li>)}</ul><small>官方参考:<a href="https://linuxcnc.org/docs/html/motion/5-axis-kinematics.html" target="_blank" rel="noreferrer">5-Axis Kinematics</a> · <a href="https://linuxcnc.org/docs/html/man/man9/kins.9.html" target="_blank" rel="noreferrer">kins(9)</a> · <a href="https://linuxcnc.org/docs/html/motion/switchkins.html" target="_blank" rel="noreferrer">switchkins</a></small></section>
|
||||
<div className="machine-save-location" data-axis-control="machine-document-location"><strong>文件保存在哪里?</strong><p>{saveLocation}</p><p>GLB 下载到浏览器设置的目录,通常为“下载”。请打开浏览器下载记录查看实际位置;网页无法读取磁盘完整路径。</p><p>保存时另存一份到本浏览器机床库,可在刷新页面后重新打开。本地机床库仅属于当前浏览器及网站地址,不会上传云服务器;清理网站数据会删除这些副本。</p></div>
|
||||
<h3>本浏览器机床库 <small>{library.length} 个保存版本</small></h3><div className="machine-library-tools"><input data-axis-control="machine-library-search" aria-label="搜索已保存机床" placeholder="搜索名称 / 机型" value={libraryQuery} onChange={(event) => setLibraryQuery(event.target.value)} /><button type="button" onClick={() => void refreshLibrary()}>刷新</button></div>
|
||||
{libraryError && <p role="alert">{libraryError}</p>}
|
||||
|
||||
3
web/react/src/legacy.d.ts
vendored
3
web/react/src/legacy.d.ts
vendored
@@ -10,6 +10,9 @@ declare module "*.mjs" {
|
||||
export const MACHINE_DEFINITIONS: readonly MachineDefinition[];
|
||||
export const DEFAULT_MACHINE: MachineDefinition;
|
||||
export function machineDefinition(value?: string): MachineDefinition | null;
|
||||
export type FiveAxisCase = { id: string; label: string; family: string; machineClass: string; coordinates: string; joints: number; kinematics: string; rtcp: string; webWasm: "supported" | "adapter-required" | "identity-only"; description: string; localCases: readonly string[]; kinematicsSource: string; designParameters: readonly string[] };
|
||||
export const FIVE_AXIS_CASES: readonly FiveAxisCase[];
|
||||
export const FIVE_AXIS_STATUS_LABELS: Record<FiveAxisCase["webWasm"], string>;
|
||||
export const SIM_CASE_CATALOG: Array<{ path: string; label?: string; category?: string; status?: string }>;
|
||||
export function loadSimulationCase(options: { path: string; baseUrl?: string }): Promise<{ status: string; entrypoint: string; files: Record<string, string>; missing: string[]; fetched: string[]; state_hash: string; halManifest: unknown[] }>;
|
||||
export function toWorkerConfiguration(bundle: unknown): Record<string, unknown>;
|
||||
|
||||
@@ -530,3 +530,21 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
|
||||
.machine-library-list span, .machine-library-list time, .machine-library-list small { font-size: 11px; color: #526c6d; }
|
||||
.machine-library-list li > div { display: flex; gap: 6px; }
|
||||
.machine-library-list button, .machine-library-tools button { padding: 5px 8px; border: 1px solid #9babad; border-radius: 3px; background: #eaf2f0; }
|
||||
.machine-case-catalog { margin: 12px 0; padding: 10px; border: 1px solid #adc8c1; border-radius: 4px; background: #edf5f3; }
|
||||
.machine-case-catalog > header { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
||||
.machine-case-catalog > header h3 { margin: 0; }
|
||||
.machine-case-catalog > header span { color: #526c6d; font-size: 11px; }
|
||||
.machine-case-catalog > p { margin: 7px 0; color: #526c6d; font-size: 11px; line-height: 1.5; }
|
||||
.machine-case-catalog ul { display: grid; gap: 6px; margin: 8px 0; padding: 0; list-style: none; }
|
||||
.machine-case-catalog li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; padding: 8px; border: 1px solid #c1d2ce; border-radius: 4px; background: #fff; }
|
||||
.machine-case-catalog li.active { border-color: #398187; box-shadow: inset 3px 0 #398187; }
|
||||
.machine-case-catalog li > div { display: grid; gap: 3px; min-width: 0; }
|
||||
.machine-case-catalog li strong { overflow-wrap: anywhere; }
|
||||
.machine-case-catalog li span, .machine-case-catalog li small { color: #526c6d; font-size: 11px; line-height: 1.45; }
|
||||
.machine-case-catalog li em { align-self: start; padding: 3px 6px; border-radius: 12px; background: #dcebe8; color: #285d5c; font-size: 10px; font-style: normal; white-space: nowrap; }
|
||||
.machine-case-catalog li em.machine-case-status-adapter-required { background: #fff0d4; color: #78521c; }
|
||||
.machine-case-catalog li em.machine-case-status-identity-only { background: #e8e8e8; color: #5d6262; }
|
||||
.machine-case-catalog details { grid-column: 1 / -1; color: #526c6d; font-size: 11px; }
|
||||
.machine-case-catalog details code { display: block; margin: 5px 0; padding: 6px; overflow: auto; white-space: pre-wrap; background: #f2f6f5; color: #315d61; }
|
||||
.machine-case-catalog details small { display: block; }
|
||||
.machine-case-catalog > small a { color: #246c73; }
|
||||
|
||||
Reference in New Issue
Block a user