Lock AXIS panel contract to source behavior
This commit is contained in:
@@ -185,7 +185,6 @@ export function App() {
|
||||
const jogStepPending = useRef(false);
|
||||
const continuousJog = useRef<{ axis: string; direction: number } | null>(null);
|
||||
const programFileInput = useRef<HTMLInputElement>(null);
|
||||
const [plotCleared, setPlotCleared] = useState(false);
|
||||
const [operationTrace, setOperationTrace] = useState<XyzacOperationTrace[]>([]);
|
||||
const feedOverridePending = useRef<number | null>(null);
|
||||
const feedOverrideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -314,9 +313,6 @@ export function App() {
|
||||
if (index >= 0 && homedAxes[index] && typeof confirm === "function" && !confirm(`${selectedAxis.toUpperCase()} is already homed. Re-home this joint?`)) return;
|
||||
void dispatch("HOME", { axis: selectedAxis }).catch(() => undefined);
|
||||
};
|
||||
const clearLoadedPreview = async () => {
|
||||
await operationController.execute("VISUAL_CLEAR", {}, { state: axisState, gate: false });
|
||||
};
|
||||
const stopPreviewProgress = () => {
|
||||
if (previewProgressTimer.current) {
|
||||
clearInterval(previewProgressTimer.current);
|
||||
@@ -366,8 +362,7 @@ export function App() {
|
||||
const operation = await operationController.execute("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }, { state: axisState, gate: false });
|
||||
const resultPayload = (operation.response as { payload?: { configuration?: { state?: string } } }).payload;
|
||||
markPreviewProgress(28, "wasm", "LinuxCNC WASM 配置已加载,准备解析刀具路径");
|
||||
setProgramText(normalizedSource); setProgramPath(PROGRAM_PATH); setPlotCleared(false);
|
||||
await clearLoadedPreview();
|
||||
setProgramText(normalizedSource); setProgramPath(PROGRAM_PATH);
|
||||
markPreviewProgress(38, "wasm", "LinuxCNC WASM 正在解析刀具路径");
|
||||
const programLoad = await operationController.execute("PROGRAM_LOAD", { source: normalizedSource, path: PROGRAM_PATH }, { state: axisState, gate: false });
|
||||
finishPreviewProgress(PROGRAM_PATH, programLoad.response);
|
||||
@@ -393,8 +388,7 @@ export function App() {
|
||||
if (!response.ok) throw new Error(`program_asset_http_${response.status}`);
|
||||
const source = normalizeProgramSource(await response.text());
|
||||
if (!source.trim()) throw new Error("program_asset_empty");
|
||||
setProgramText(source); setProgramPath(path); setPlotCleared(false);
|
||||
await clearLoadedPreview();
|
||||
setProgramText(source); setProgramPath(path);
|
||||
markPreviewProgress(38, "wasm", "LinuxCNC WASM 正在解析刀具路径");
|
||||
const programLoad = await operationController.execute("PROGRAM_LOAD", { source, path }, { state: axisState, gate: false });
|
||||
finishPreviewProgress(path, programLoad.response);
|
||||
@@ -412,8 +406,7 @@ export function App() {
|
||||
setProgramLoading(true);
|
||||
startPreviewProgress(programPath || "当前程序");
|
||||
setCommandStatus("PROGRAM_LOAD pending: waiting for LinuxCNC WASM preview");
|
||||
setPlotCleared(false);
|
||||
void clearLoadedPreview()
|
||||
void Promise.resolve()
|
||||
.then(() => {
|
||||
markPreviewProgress(38, "wasm", "LinuxCNC WASM 正在解析刀具路径");
|
||||
return operationController.execute("PROGRAM_LOAD", { source: programText, path: programPath }, { state: axisState, gate: false });
|
||||
@@ -444,10 +437,8 @@ export function App() {
|
||||
const path = file.name === "3D_Chips.ngc" ? "linuxcnc/nc_files/3D_Chips.ngc" : `browser/${file.name}`;
|
||||
setProgramText(source);
|
||||
setProgramPath(path);
|
||||
setPlotCleared(false);
|
||||
setCommandStatus("PROGRAM_LOAD pending: waiting for LinuxCNC WASM preview");
|
||||
setCaseStatus(`${file.name} loading through linuxcnc-wasm...`);
|
||||
await clearLoadedPreview();
|
||||
markPreviewProgress(38, "wasm", "LinuxCNC WASM 正在解析刀具路径");
|
||||
const programLoad = await operationController.execute("PROGRAM_LOAD", { source, path }, { state: axisState, gate: false });
|
||||
finishPreviewProgress(path, programLoad.response);
|
||||
@@ -487,7 +478,7 @@ export function App() {
|
||||
if (!event.ctrlKey && event.key === "B") { event.preventDefault(); execute("SPINDLE_BRAKE", { spindle: 0, engaged: true }); return; }
|
||||
if (!event.ctrlKey && event.key === "b") { event.preventDefault(); execute("SPINDLE_BRAKE", { spindle: 0, engaged: false }); return; }
|
||||
const key = event.key.toLowerCase();
|
||||
if (event.ctrlKey && key === "k") { event.preventDefault(); void dispatch("VISUAL_CLEAR", {}, true, "vismachClear").then(() => setPlotCleared(true)).catch(() => undefined); return; }
|
||||
if (event.ctrlKey && key === "k") { event.preventDefault(); void dispatch("VISUAL_CLEAR", {}, true, "vismachClear").catch(() => undefined); return; }
|
||||
if (event.ctrlKey && key === "r") { event.preventDefault(); loadProgram(); return; }
|
||||
if (event.ctrlKey && event.key === "Home") { event.preventDefault(); homeAll(); return; }
|
||||
if (event.ctrlKey && event.key === "End") { event.preventDefault(); promptTouchOff(true); return; }
|
||||
@@ -583,7 +574,7 @@ export function App() {
|
||||
const visibleProgramStatus = programLoading ? "loading" : String(state.program?.program_status ?? "unknown");
|
||||
const homedAxes = Array.isArray(state.snapshot?.motion?.homed_axes) ? state.snapshot.motion.homed_axes : [];
|
||||
const allHomed = homedAxes.length >= 5 && homedAxes.slice(0, 5).every(Boolean);
|
||||
const xyzacCommand = (kind: string, payload: Record<string, unknown> = {}, action?: string) => { void dispatch(kind, payload, true, action ?? null).then(() => { if (kind === "VISUAL_CLEAR") setPlotCleared(true); }).catch(() => undefined); };
|
||||
const xyzacCommand = (kind: string, payload: Record<string, unknown> = {}, action?: string) => { void dispatch(kind, payload, true, action ?? null).catch(() => undefined); };
|
||||
const position = xyzacView.pose ? `X${Number(xyzacView.pose.x).toFixed(3)} Y${Number(xyzacView.pose.y).toFixed(3)} Z${Number(xyzacView.pose.z).toFixed(3)} A${Number(xyzacView.pose.a).toFixed(3)} C${Number(xyzacView.pose.c).toFixed(3)}` : "WASM state pending";
|
||||
useEffect(() => {
|
||||
if (!uiCacheHydrated.current || !wasmSnapshotReady) return;
|
||||
@@ -618,7 +609,7 @@ export function App() {
|
||||
<XyzacTrtPanel view={xyzacView} command={xyzacCommand} loading={programLoading} />
|
||||
<XyzacLiveParameters snapshot={state.snapshot} />
|
||||
<section className="axis-top-pane" data-axis-feature="AXIS-UI-06"><div className="axis-notebook left-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Manual, MDI and diagnostics controls"><button id="axis-tab-manual" data-axis-control="tab-manual" type="button" role="tab" aria-controls="axis-panel-manual" aria-selected={leftTab === "manual"} className={leftTab === "manual" ? "active" : ""} onClick={enterManualMode}>Manual Control [F3]</button><button id="axis-tab-mdi" data-axis-control="tab-mdi" type="button" role="tab" aria-controls="axis-panel-mdi" aria-selected={leftTab === "mdi"} className={leftTab === "mdi" ? "active" : ""} onClick={enterMdiMode}>MDI [F5]</button><button id="axis-tab-diagnostics" data-axis-control="tab-diagnostics" type="button" role="tab" aria-controls="axis-panel-diagnostics" aria-selected={leftTab === "diagnostics"} className={leftTab === "diagnostics" ? "active" : ""} onClick={() => setLeftTab("diagnostics")}>Diagnostics</button></div><div id="axis-panel-manual" role="tabpanel" aria-labelledby="axis-tab-manual" hidden={leftTab !== "manual"}><ManualControl action={execute} canJog={!programLoading && manualReady && gates.jog.enabled} canHome={!programLoading && gates.home.enabled} canSpindle={!programLoading && manualReady && Boolean(gates.spindle?.enabled)} canCoolant={!programLoading && idle && manualReady && Boolean(gates.coolant?.enabled)} canTouchOff={!programLoading && manualReady && Boolean(gates.touchOff?.enabled)} canToolTouchOff={!programLoading && manualReady && Boolean(gates.toolTouchOff?.enabled)} selectedAxis={selectedAxis} setSelectedAxis={setSelectedAxis} spindleDirection={spindleDirection} spindleBrake={spindleBrake} coolant={coolant} onSpindle={onSpindle} onCoolant={onCoolant} onHomeAxis={homeAxis} onTouchOff={() => promptTouchOff(false)} onToolTouchOff={() => promptTouchOff(true)} jogScale={Number.isFinite(sourceJogScale) ? sourceJogScale : null} onJogScale={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} devices={state.devices ?? {}} /></div><div id="axis-panel-mdi" role="tabpanel" aria-labelledby="axis-tab-mdi" hidden={leftTab !== "mdi"}><MdiControl action={execute} ready={!programLoading && manualReady && gates.mdi.enabled} /></div><div id="axis-panel-diagnostics" role="tabpanel" aria-labelledby="axis-tab-diagnostics" hidden={leftTab !== "diagnostics"}><DiagnosticsPanel state={state} gates={gates} /><XyzacDiagnostics view={xyzacView} operations={operationTrace} alarms={state.alarms} error={state.error} /></div></div><div className="axis-notebook right-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Preview and DRO"><button id="axis-tab-preview" data-axis-control="tab-preview" type="button" role="tab" aria-controls="axis-panel-preview" aria-selected={rightTab === "preview"} className={rightTab === "preview" ? "active" : ""} onClick={() => setRightTab("preview")}>Preview</button><button id="axis-tab-dro" data-axis-control="tab-dro" type="button" role="tab" aria-controls="axis-panel-dro" aria-selected={rightTab === "dro"} className={rightTab === "dro" ? "active" : ""} onClick={() => setRightTab("dro")}>DRO</button></div><div id="axis-panel-preview" role="tabpanel" aria-labelledby="axis-tab-preview" className="axis-preview" hidden={rightTab !== "preview"}><XyzacToolPreviewFromSnapshot snapshot={programLoading ? null : state.snapshot} view={view} zoom={zoom} rotateMode={rotateMode} showPath={layers.path} showLive={layers.live} showLimits={layers.limits} showVelocity={layers.velocity} showOffsets={layers.offsets} showExtents={layers.extents} /></div><div id="axis-panel-dro" role="tabpanel" aria-labelledby="axis-tab-dro" hidden={rightTab !== "dro"}><DroView axes={axes} state={state} coordType={coordType} displayType={displayType} metric={metric} setCoordType={setCoordType} setDisplayType={setDisplayType} setMetric={setMetric} /></div></div></section>
|
||||
<section className="axis-metrics" data-axis-feature="AXIS-VIEW-03"><div className="axis-slider"><label>Jog Speed:<output>{jogScalePercent === null ? "-" : `${jogScalePercent}%`}</output><input type="range" data-axis-control="override-jog-speed" min="6" max="100" value={jogScalePercent ?? 6} onChange={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} disabled={programLoading || !gates.jog.enabled || jogScalePercent === null} aria-label="Jog speed" /></label><label>Feed Override:<output>{feedOverridePercent === null ? "-" : `${feedOverridePercent}%`}</output><input type="range" data-axis-control="override-feed" min="0" max="200" step="1" value={feedOverridePercent ?? 0} onChange={requestFeedOverride} disabled={!gates.override.enabled || programLoading || feedOverridePercent === null} aria-label="Feed override" aria-valuetext={feedOverridePercent === null ? "WASM state pending" : `${feedOverridePercent}%`} data-axis-feed-override={feedOverridePercent ?? ""} /></label><label>Spindle Override:<output>{spindleOverridePercent === null ? "-" : `${spindleOverridePercent}%`}</output><input type="range" data-axis-control="override-spindle" min="0" max="200" value={spindleOverridePercent ?? 0} onChange={(event) => void dispatch("SET_SPINDLE_OVERRIDE", { spindle: 0, scale: Number(event.target.value) / 100 })} disabled={programLoading || !gates.override.enabled || spindleOverridePercent === null} aria-label="Spindle override" /></label><label>Max Velocity:<output>{maxVelocityPercent === null ? "-" : `${maxVelocityPercent}%`}</output><input type="range" data-axis-control="override-max-velocity" min="6" max="100" value={maxVelocityPercent ?? 6} onChange={(event) => void dispatch("SET_MAX_VELOCITY", { percent: Number(event.target.value) })} disabled={programLoading || !gates.override.enabled || maxVelocityPercent === null} aria-label="Max velocity" /></label><label title="blocked_by_abi: LinuxCNC rapidrate is not exposed by the selected WASM TP"><span>Rapid Override</span><output>Unavailable</output><input type="range" data-axis-control="override-rapid" min="0" max="120" value="0" disabled aria-label="Rapid override" /></label></div><div className="axis-layer-controls" aria-label="Preview layers">{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => <label key={layer}><input type="checkbox" data-axis-control={`preview-layer-${layer}`} data-axis-feature="AXIS-VIEW-03" checked={layers[layer]} onChange={(event) => setLayers((current) => ({ ...current, [layer]: event.target.checked }))} /> {layer}</label>)}</div></section>
|
||||
<section className="axis-metrics" data-axis-feature="AXIS-VIEW-03"><div className="axis-slider"><label>Jog Speed:<output>{jogScalePercent === null ? "-" : `${jogScalePercent}%`}</output><input type="range" data-axis-control="override-jog-speed" min="6" max="100" value={jogScalePercent ?? 6} onChange={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} disabled={programLoading || jogScalePercent === null} aria-label="Jog speed" /></label><label>Feed Override:<output>{feedOverridePercent === null ? "-" : `${feedOverridePercent}%`}</output><input type="range" data-axis-control="override-feed" min="0" max="200" step="1" value={feedOverridePercent ?? 0} onChange={requestFeedOverride} disabled={programLoading || feedOverridePercent === null} aria-label="Feed override" aria-valuetext={feedOverridePercent === null ? "WASM state pending" : `${feedOverridePercent}%`} data-axis-feed-override={feedOverridePercent ?? ""} /></label><label>Spindle Override:<output>{spindleOverridePercent === null ? "-" : `${spindleOverridePercent}%`}</output><input type="range" data-axis-control="override-spindle" min="0" max="200" value={spindleOverridePercent ?? 0} onChange={(event) => void dispatch("SET_SPINDLE_OVERRIDE", { spindle: 0, scale: Number(event.target.value) / 100 })} disabled={programLoading || spindleOverridePercent === null} aria-label="Spindle override" /></label><label>Max Velocity:<output>{maxVelocityPercent === null ? "-" : `${maxVelocityPercent}%`}</output><input type="range" data-axis-control="override-max-velocity" min="6" max="100" value={maxVelocityPercent ?? 6} onChange={(event) => void dispatch("SET_MAX_VELOCITY", { percent: Number(event.target.value) })} disabled={programLoading || maxVelocityPercent === null} aria-label="Max velocity" /></label><label title="blocked_by_abi: LinuxCNC rapidrate is not exposed by the selected WASM TP"><span>Rapid Override</span><output>Unavailable</output><input type="range" data-axis-control="override-rapid" min="0" max="120" value="0" disabled aria-label="Rapid override" /></label></div><div className="axis-layer-controls" aria-label="Preview layers">{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => <label key={layer}><input type="checkbox" data-axis-control={`preview-layer-${layer}`} data-axis-feature="AXIS-VIEW-03" checked={layers[layer]} onChange={(event) => setLayers((current) => ({ ...current, [layer]: event.target.checked }))} /> {layer}</label>)}</div></section>
|
||||
<XyzacProgramPane status={visibleProgramStatus} currentLine={visibleCurrentLine} sourceHash={programLoading ? "" : String(state.program?.source_hash ?? "")}><ProgramPane text={programText} setText={setProgramText} activeGcodes={activeGcodes} executionTrace={visibleExecutionTrace} programStatus={visibleProgramStatus} currentLine={visibleCurrentLine} executionLine={visibleExecutionLine} executionLineText={visibleExecutionLineText} diagnostic={state.error?.code ?? null} /></XyzacProgramPane>
|
||||
<input ref={programFileInput} className="visually-hidden-file-input" data-axis-control="program-file-input" type="file" accept=".ngc,.nc,.gcode,.tap,text/plain" onChange={onProgramFile} aria-label="Open G-code file" />
|
||||
{aboutOpen && <section className="axis-about-backdrop" role="presentation" onMouseDown={(event) => { if (event.target === event.currentTarget) setAboutOpen(false); }}><div className="axis-about-dialog" role="dialog" aria-modal="true" aria-labelledby="axis-about-title"><img src={axisIcon("axis-24x24.png")} alt="" /><h2 id="axis-about-title">LinuxCNC AXIS Web/WASM</h2><p>Source-compatible control panel for the selected XYZAC TRT simulation.</p><TextButton controlId="about-close" onClick={() => setAboutOpen(false)}>OK</TextButton></div></section>}
|
||||
|
||||
Reference in New Issue
Block a user