Display live WASM machining parameters
This commit is contained in:
@@ -6,7 +6,7 @@ import { SIM_CASE_CATALOG } from "../../workspace/sim-case-catalog.mjs";
|
||||
import { loadSimulationCase, toWorkerConfiguration } from "../../workspace/sim-case-loader.mjs";
|
||||
import { axisIcon } from "./sourceAssets";
|
||||
import { AxisOperationController } from "../../app/axis-operation-controller.mjs";
|
||||
import { XyzacDiagnostics, XyzacProgramPane, XyzacTrtPanel } from "./XyzacTrtPanel";
|
||||
import { XyzacDiagnostics, XyzacLiveParameters, XyzacProgramPane, XyzacTrtPanel } from "./XyzacTrtPanel";
|
||||
import { XYZAC_CASE, projectXyzacPanel, type XyzacOperationTrace } from "./xyzac-panel-contract";
|
||||
|
||||
export const SELECTED_XYZAC_CASE = XYZAC_CASE;
|
||||
@@ -472,6 +472,7 @@ export function App() {
|
||||
<section className="axis-case-strip" data-axis-feature="AXIS-EDIT-03"><label>Simulation case<select data-axis-feature="AXIS-EDIT-03" value={casePath} onChange={(event) => setCasePath(event.target.value)}>{SIM_CASE_CATALOG.filter((entry) => entry.path === SELECTED_XYZAC_CASE).map((entry) => <option key={entry.path} value={entry.path}>{entry.label} | XYZAC TRT</option>)}</select></label><TextButton feature="AXIS-EDIT-03" onClick={loadCase} disabled={!gates.load.enabled || programLoading}>Load case</TextButton><TextButton feature="AXIS-EDIT-03" onClick={loadPackagedProgram} disabled={!gates.load.enabled || programLoading}>Load 3D_Chips.ngc</TextButton><span className={`case-status${programLoading ? " loading" : ""}`} role="status" aria-live="polite" aria-busy={programLoading}>{programLoading ? "PROGRAM_LOAD pending: LinuxCNC WASM preview in progress..." : caseStatus}</span>{previewProgress && <div className={`preview-load-indicator ${previewProgress.phase}`} role="status" aria-live="polite" aria-label="Toolpath preview loading progress"><span title={previewProgress.detail}>{previewProgress.detail}</span><progress max="100" value={previewProgress.percent}>{previewProgress.percent}%</progress><output>{previewProgress.percent}%</output></div>}<span className="command-status" role="status">{commandStatus}</span></section>
|
||||
<MachineSequence wasmReady={wasmSnapshotReady} isEstop={isEstop} machineOn={machineOn} allHomed={allHomed} programLoaded={programLoaded} programRunning={programRunning} programLoading={programLoading} canPower={gates.power.enabled} canHome={gates.home.enabled} canRun={gates.run.enabled} onEstop={emergencyStop} onRelease={() => { void releaseEstop().catch(() => undefined); }} onPower={powerOn} onHomeAll={homeAll} onRun={runProgram} onSequence={runStartupSequence} />
|
||||
<XyzacTrtPanel view={xyzacView} command={xyzacCommand} />
|
||||
<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" 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" type="button" role="tab" aria-controls="axis-panel-mdi" aria-selected={leftTab === "mdi"} className={leftTab === "mdi" ? "active" : ""} onClick={() => setLeftTab("mdi")}>MDI [F5]</button><button id="axis-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={manualReady && gates.jog.enabled} canHome={gates.home.enabled} canSpindle={manualReady && Boolean(gates.spindle?.enabled)} canCoolant={wasmSnapshotReady && Boolean(gates.coolant?.enabled)} canTool={manualReady && Boolean(gates.tool?.enabled)} canProbe={manualReady && Boolean(gates.probe?.enabled)} canTouchOff={manualReady && Boolean(gates.touchOff?.enabled)} canToolTouchOff={manualReady && Boolean(gates.toolTouchOff?.enabled)} onHomeAll={homeAll} selectedAxis={selectedAxis} setSelectedAxis={setSelectedAxis} spindleOn={spindleOn} spindleBrake={spindleBrake} coolant={coolant} onSpindle={onSpindle} onCoolant={onCoolant} 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={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" 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" 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" min="6" max="100" value={jogScalePercent ?? 6} onChange={(event) => void dispatch("SET_JOG_SCALE", { percent: Number(event.target.value) })} disabled={!gates.jog.enabled || jogScalePercent === null} aria-label="Jog speed" /></label><label>Feed Override:<output>{feedOverridePercent === null ? "-" : `${feedOverridePercent}%`}</output><input type="range" 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" min="0" max="200" value={spindleOverridePercent ?? 0} onChange={(event) => void dispatch("SET_SPINDLE_OVERRIDE", { spindle: 0, scale: Number(event.target.value) / 100 })} disabled={!gates.override.enabled || spindleOverridePercent === null} aria-label="Spindle override" /></label><label>Max Velocity:<output>{maxVelocityPercent === null ? "-" : `${maxVelocityPercent}%`}</output><input type="range" min="6" max="100" value={maxVelocityPercent ?? 6} onChange={(event) => void dispatch("SET_MAX_VELOCITY", { percent: Number(event.target.value) })} disabled={!gates.override.enabled || maxVelocityPercent === null} aria-label="Max velocity" /></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-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} onLoad={loadProgram} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled && !programLoading} canRun={gates.run.enabled && !programLoading} canStep={gates.step.enabled && !programLoading} currentLine={visibleCurrentLine} executionLine={visibleExecutionLine} executionLineText={visibleExecutionLineText} diagnostic={state.error?.code ?? null} /></XyzacProgramPane>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { XYZAC_KINEMATICS, type XyzacOperationTrace, type XyzacPanelView } from "./xyzac-panel-contract";
|
||||
import type { SimSnapshotV1 } from "./protocol";
|
||||
|
||||
type Command = (kind: string, payload?: Record<string, unknown>, action?: string) => void;
|
||||
|
||||
@@ -34,6 +35,26 @@ export function XyzacProgramPane({ status, currentLine, sourceHash, children }:
|
||||
return <section className="xyzac-program" aria-label="XYZAC program"><header><strong>XYZAC Program</strong><span>{status}</span><span>Line {currentLine ?? "-"}</span><code>{sourceHash?.slice(0, 18) ?? "no source"}</code></header>{children}</section>;
|
||||
}
|
||||
|
||||
export function XyzacLiveParameters({ snapshot }: { snapshot: SimSnapshotV1 | null }) {
|
||||
const ready = snapshot?.motion_authority === "wasm" && Boolean(snapshot?.task_authority);
|
||||
const motion = (snapshot?.motion ?? {}) as Record<string, unknown>;
|
||||
const program = snapshot?.program ?? null;
|
||||
const number = (value: unknown, digits = 2, unit = "") => ready && typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(digits)}${unit ? ` ${unit}` : ""}` : "-";
|
||||
const integer = (value: unknown) => ready && Number.isSafeInteger(value) ? String(value) : "-";
|
||||
const wasmState = (snapshot?.wasm_motion as { state?: Record<string, unknown> } | undefined)?.state;
|
||||
return <section className="xyzac-live-parameters" data-axis-feature="AXIS-LIVE-PARAMETERS" data-xyzac-parameter-source={ready ? "linuxcnc-wasm" : "pending"} aria-label="Live machining parameters">
|
||||
<div className="xyzac-section-title"><strong>Live parameters</strong><span>{ready ? "LinuxCNC WASM" : "WASM PENDING"}</span></div>
|
||||
<div className="xyzac-parameter-grid">
|
||||
<div><b>Spindle</b><output>{number(motion.spindle_speed_rpm, 1, "rpm")}</output><small>Cmd {number(motion.spindle_command_rpm, 1, "rpm")} / S {number(program?.speed, 1, "rpm")}</small></div>
|
||||
<div><b>Feed actual</b><output>{number(motion.velocity_mm_min, 1, "mm/min")}</output><small>Program {number(motion.feed_command_mm_min, 1, "mm/min")}</small></div>
|
||||
<div><b>TP velocity</b><output>{number(motion.velocity, 3, "mm/s")}</output><small>Limit {number(motion.max_velocity, 3, "mm/s")}</small></div>
|
||||
<div><b>Overrides</b><output>F {number(typeof motion.feed_override === "number" ? motion.feed_override * 100 : Number.NaN, 0, "%")}</output><small>S {number(typeof motion.spindle_override === "number" ? motion.spindle_override * 100 : Number.NaN, 0, "%")}</small></div>
|
||||
<div><b>Planner</b><output>{integer(wasmState?.queue_depth)} queued</output><small>Segment {integer(wasmState?.active_id)}</small></div>
|
||||
<div><b>Execution</b><output>Line {integer(program?.execution_line ?? program?.current_line)}</output><small>{ready ? String(program?.program_status ?? "-") : "-"}</small></div>
|
||||
</div>
|
||||
</section>;
|
||||
}
|
||||
|
||||
export function XyzacDiagnostics({ view, operations, alarms, error }: { view: XyzacPanelView; operations: XyzacOperationTrace[]; alarms: Array<Record<string, unknown>>; error: { code: string; message?: string } | null }) {
|
||||
const disabled = Object.entries(view.gates).filter(([, gate]) => gate.enabled === false);
|
||||
return <section className="xyzac-diagnostics" aria-label="XYZAC diagnostics">
|
||||
|
||||
@@ -27,11 +27,18 @@ export type MotionSnapshot = {
|
||||
on_any_limit?: boolean;
|
||||
fault?: string | null;
|
||||
velocity_limit?: number;
|
||||
velocity_mm_min?: number;
|
||||
feed_command_mm_min?: number;
|
||||
velocity_scale?: number;
|
||||
jog_scale?: number;
|
||||
jog_velocity_linear?: number;
|
||||
jog_velocity_angular?: number;
|
||||
spindle_override?: number;
|
||||
spindle_speed_rpm?: number;
|
||||
spindle_command_rpm?: number;
|
||||
spindle_enabled?: boolean;
|
||||
spindle_at_speed?: boolean;
|
||||
max_velocity?: number;
|
||||
};
|
||||
|
||||
export type TrajectorySnapshot = {
|
||||
|
||||
@@ -90,6 +90,7 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
||||
overrides: (result.overrides as SimSnapshotV1["overrides"]) ?? previous.snapshot.overrides,
|
||||
motion: {
|
||||
...previous.snapshot.motion,
|
||||
...(result.motion as SimSnapshotV1["motion"] | undefined),
|
||||
// Keep the XYZAC axis vector intact on every motion delta. Older
|
||||
// projection code rebuilt only XYZ, which made A/C disappear from
|
||||
// consumers that read the canonical motion array.
|
||||
|
||||
@@ -98,6 +98,13 @@ button { color: inherit; }
|
||||
.xyzac-position-grid output { overflow: hidden; font-size: 13px; text-overflow: ellipsis; }
|
||||
.xyzac-position-grid small { overflow: hidden; color: #666; font: 9px ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.xyzac-offset-row { display: flex; gap: 12px; margin-top: 5px; color: #555; font: 10px ui-monospace, monospace; }
|
||||
.xyzac-live-parameters { margin: 0 5px 5px; padding: 5px 8px; border: 1px solid var(--border-dark); background: #dedede; }
|
||||
.xyzac-live-parameters .xyzac-section-title { margin: 0 0 4px; }
|
||||
.xyzac-parameter-grid { display: grid; grid-template-columns: repeat(6, minmax(120px, 1fr)); gap: 5px; }
|
||||
.xyzac-parameter-grid > div { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 2px 6px; min-width: 0; padding: 4px 6px; border-left: 2px solid #7894aa; background: #f7f7f7; }
|
||||
.xyzac-parameter-grid b { color: #36434c; font-size: 11px; }
|
||||
.xyzac-parameter-grid output { grid-column: 2; color: #111; font-size: 13px; white-space: nowrap; }
|
||||
.xyzac-parameter-grid small { grid-column: 1 / -1; overflow: hidden; color: #666; font: 10px ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.xyzac-program { display: contents; }
|
||||
.xyzac-program > header { display: flex; align-items: center; gap: 14px; min-height: 25px; margin: 0 5px; padding: 3px 8px; border: 1px solid #888; border-bottom: 0; background: #d4d4d4; font-size: 11px; }
|
||||
.xyzac-program > header code { margin-left: auto; color: #4d4d4d; }
|
||||
@@ -180,6 +187,8 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
|
||||
.axis-metrics { padding: 4px 9px; border-top: 1px solid #999; background: #cecece; }
|
||||
.axis-slider { display: grid; grid-template-columns: repeat(3, minmax(170px, 1fr)); gap: 12px; }
|
||||
.axis-slider label { grid-template-columns: auto 48px; }
|
||||
@media (max-width: 900px) { .xyzac-parameter-grid { grid-template-columns: repeat(3, minmax(120px, 1fr)); } }
|
||||
@media (max-width: 560px) { .xyzac-parameter-grid { grid-template-columns: repeat(2, minmax(120px, 1fr)); } }
|
||||
|
||||
.axis-program-pane { display: grid; grid-template-columns: minmax(0, 1fr) 230px; grid-template-rows: minmax(0, 1fr); flex: 0 0 430px; height: 430px; min-height: 430px; margin: 0 5px 5px; border: 2px solid #777; background: #f7f7f7; }
|
||||
.axis-diagnostics { display: grid; gap: 8px; padding: 10px; min-height: 190px; overflow: auto; background: #f7f7f7; font-size: 12px; }
|
||||
|
||||
Reference in New Issue
Block a user