Add AC/BC RTCP programs and 3D machine manager

This commit is contained in:
2026-09-20 12:31:12 +08:00
parent 1c1e5f9672
commit 8aab531b02
363 changed files with 53213 additions and 58 deletions

View File

@@ -0,0 +1,96 @@
import { useEffect, useMemo, useState } from "react";
import catalogJson from "./rtcp-program-manifest.json";
import { DraggableDialog } from "./DraggableDialog";
export type RtcpProgram = {
id: string;
group: string;
label: string;
path: string;
coordinates: string[];
role: "main" | "support" | "reference";
features: string[];
note: string;
};
type Catalog = {
schema: string;
total: number;
groups: Array<{ id: string; label: string; count: number }>;
programs: RtcpProgram[];
};
export const RTCP_PROGRAM_CATALOG = catalogJson as Catalog;
const roleLabel = (role: RtcpProgram["role"]) => role === "main" ? "主程序" : role === "support" ? "支持/子程序" : "参考程序";
const assetUrl = (path: string) => `${import.meta.env.BASE_URL}${path}`;
const machineLabel = (coordinates: string) => coordinates === "XYZAC" ? "AC(XYZAC)" : coordinates === "XYZBC" ? "BC(XYZBC)" : coordinates;
export function RtcpProgramLibrary({ coordinates, disabled, disabledReason, onLoad }: {
coordinates: string;
disabled: boolean;
disabledReason: string;
onLoad: (program: RtcpProgram, source: string) => Promise<void>;
}) {
const defaultProgram = RTCP_PROGRAM_CATALOG.programs.find((program) => program.role === "main" && program.coordinates.includes(coordinates)) ?? RTCP_PROGRAM_CATALOG.programs[0];
const [open, setOpen] = useState(false);
const [scope, setScope] = useState<"machine" | "all">("machine");
const [selectedId, setSelectedId] = useState(defaultProgram.id);
const [source, setSource] = useState("");
const [sourcePath, setSourcePath] = useState("");
const [message, setMessage] = useState("");
const [pending, setPending] = useState(false);
const selected = RTCP_PROGRAM_CATALOG.programs.find((program) => program.id === selectedId) ?? defaultProgram;
const compatible = selected.role === "main" && selected.coordinates.includes(coordinates);
const machinePrograms = useMemo(() => RTCP_PROGRAM_CATALOG.programs.filter((program) => program.coordinates.includes(coordinates)), [coordinates]);
const visiblePrograms = scope === "machine" ? machinePrograms : RTCP_PROGRAM_CATALOG.programs;
const grouped = RTCP_PROGRAM_CATALOG.groups.map((group) => ({ ...group, programs: visiblePrograms.filter((program) => program.group === group.id) })).filter((group) => group.programs.length > 0);
useEffect(() => {
setScope("machine"); setSelectedId(defaultProgram.id); setSource(""); setSourcePath(""); setMessage("");
}, [coordinates, defaultProgram.id]);
const readSource = async () => {
if (sourcePath === selected.path && source) return source;
const response = await fetch(assetUrl(selected.path));
if (!response.ok) throw new Error(`程序资源读取失败: HTTP ${response.status}`);
const text = (await response.text()).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
if (!text.trim()) throw new Error("程序资源为空");
setSource(text);
setSourcePath(selected.path);
return text;
};
const run = async (action: "source" | "load") => {
setPending(true); setMessage(action === "source" ? "正在读取 LinuxCNC 源程序…" : "正在交给 LinuxCNC WASM 解析…");
try {
const text = await readSource();
if (action === "load") { await onLoad(selected, text); setMessage("程序已加载到 LinuxCNC WASM 控制器"); }
else setMessage(`已读取 ${text.split("\n").length} 行`);
} catch (error) { setMessage(error instanceof Error ? error.message : String(error)); }
finally { setPending(false); }
};
const compatibility = compatible
? `${coordinates} 当前机型可加载;运动由 LinuxCNC WASM 解释器与运动学执行。`
: selected.role !== "main"
? `${roleLabel(selected.role)}用于 remap、O-call 或算法查阅,不作为独立主程序加载。`
: `需要 ${selected.coordinates.join("/")} 运动学;当前机型为 ${coordinates}。`;
return <section className="rtcp-program-library" aria-label={`${machineLabel(coordinates)} RTCP 程序库`} data-rtcp-machine-family={coordinates === "XYZAC" ? "AC" : coordinates === "XYZBC" ? "BC" : coordinates}>
<button type="button" data-axis-control="workspace-rtcp-programs" aria-haspopup="dialog" onClick={() => setOpen(true)}>{machineLabel(coordinates)} RTCP 程序 ({machinePrograms.length})</button>
<DraggableDialog open={open} title={`${machineLabel(coordinates)} LinuxCNC RTCP 程序库`} className="rtcp-program-dialog" onClose={() => setOpen(false)}>
<header className="rtcp-program-summary"><strong>{machineLabel(coordinates)} 程序</strong><span>当前分类:{machinePrograms.length} 个</span><span>可直接加载:{machinePrograms.filter((program) => program.role === "main").length}</span><span>完整源程序:{RTCP_PROGRAM_CATALOG.total} 个</span></header>
<label className="rtcp-program-scope">显示范围<select data-axis-control="rtcp-program-scope" value={scope} onChange={(event) => { const next = event.target.value as "machine" | "all"; setScope(next); if (next === "machine" && !selected.coordinates.includes(coordinates)) setSelectedId(defaultProgram.id); setSource(""); setSourcePath(""); setMessage(""); }}><option value="machine">当前 {machineLabel(coordinates)} 机床</option><option value="all">全部 32 个源程序</option></select></label>
<label className="rtcp-program-select">程序<select data-axis-control="rtcp-program-select" value={selected.id} onChange={(event) => { setSelectedId(event.target.value); setSource(""); setSourcePath(""); setMessage(""); }}>
{grouped.map((group) => <optgroup key={group.id} label={`${group.label} (${group.programs.length})`}>{group.programs.map((program) => <option key={program.id} value={program.id}>{program.label} · {roleLabel(program.role)}</option>)}</optgroup>)}
</select></label>
<dl className="rtcp-program-meta"><div><dt>类型</dt><dd>{roleLabel(selected.role)}</dd></div><div><dt>机型</dt><dd>{selected.coordinates.join(" / ")}</dd></div><div><dt>特点</dt><dd>{selected.features.join(" · ")}</dd></div><div><dt>源路径</dt><dd><code>{selected.path}</code></dd></div></dl>
<p>{selected.note}</p><p className={compatible ? "rtcp-compatible" : "rtcp-incompatible"}>{compatibility}</p>
<div className="rtcp-program-actions">
<button type="button" data-axis-control="rtcp-program-source" disabled={pending} onClick={() => void run("source")}>查看源程序</button>
<button type="button" data-axis-control="rtcp-program-load" disabled={pending || disabled || !compatible} title={disabled ? disabledReason : !compatible ? compatibility : undefined} onClick={() => void run("load")}>加载到控制器</button>
<a data-axis-control="rtcp-program-download" href={assetUrl(selected.path)} download={selected.path.split("/").at(-1)}>下载 .ngc</a>
</div>
<output role="status">{message}</output>
{source && sourcePath === selected.path && <textarea className="rtcp-program-source" readOnly value={source} aria-label={`${selected.label} 源程序`} spellCheck={false} />}
</DraggableDialog>
</section>;
}