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; }
|
||||
|
||||
@@ -196,6 +196,13 @@ export class SimSession {
|
||||
source_hash: program.source_hash,
|
||||
state_hash: program.state_hash,
|
||||
active_gcodes: program.active_gcodes,
|
||||
feed: program.feed,
|
||||
speed: program.speed,
|
||||
units: program.units,
|
||||
distance_mode: program.distance_mode,
|
||||
motion_control: program.motion_control,
|
||||
motion_control_mode: program.motion_control_mode,
|
||||
path_tolerance: program.path_tolerance,
|
||||
} : null;
|
||||
const trace = Array.isArray(statePayload.motion_trace) ? statePayload.motion_trace : [];
|
||||
const motionSample = motionDelta ? (trace.at(-1) ?? {
|
||||
@@ -239,12 +246,14 @@ export class SimSession {
|
||||
};
|
||||
}
|
||||
|
||||
wasmMotionProjection() {
|
||||
wasmMotionProjection(program = null) {
|
||||
const wasmMotion = this.wasmMotion.read();
|
||||
const position = wasmMotion.status === "verified" ? wasmMotion.state.position : { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, u: 0, v: 0, w: 0 };
|
||||
const home = this.wasmHomeLimit.read();
|
||||
const io = this.xyzacIo?.canonical?.();
|
||||
const ioState = io && typeof io === "object" && io.status !== "blocked" ? io : null;
|
||||
const sourceProgram = program ?? this.xyzacProgram?.snapshot();
|
||||
const spindle = ioState?.spindles?.[0] ?? null;
|
||||
const kinematics = this.xyzacKinematics?.snapshot?.();
|
||||
const plannerJoints = wasmMotion.status === "verified" && Array.isArray(wasmMotion.state.joint_commanded) && wasmMotion.state.joint_commanded.length === 5 ? [...wasmMotion.state.joint_commanded] : null;
|
||||
const kinematicsJoints = plannerJoints ?? (kinematics?.joints?.length === 5 ? [...kinematics.joints] : null);
|
||||
@@ -255,8 +264,14 @@ export class SimSession {
|
||||
axes: { x: position.x, y: position.y, z: position.z },
|
||||
joints: kinematicsJoints ?? (home.status === "verified" && Array.isArray(home.state.joints) ? [...home.state.joints] : [position.x, position.y, position.z, position.a ?? 0, position.c ?? 0]),
|
||||
velocity: wasmMotion.status === "verified" ? wasmMotion.state.velocity : 0,
|
||||
velocity_mm_min: Number(wasmMotion.state?.velocity_mm_min ?? 0),
|
||||
feed_command_mm_min: Number(sourceProgram?.feed ?? 0),
|
||||
feed_override: Number(wasmMotion.state?.feed_override ?? 1),
|
||||
spindle_override: Number(ioState?.spindles?.[0]?.override ?? 1),
|
||||
spindle_speed_rpm: Number(spindle?.speed_rpm ?? 0),
|
||||
spindle_command_rpm: Number(spindle?.command_rpm ?? 0),
|
||||
spindle_enabled: Boolean(spindle?.enabled),
|
||||
spindle_at_speed: Boolean(spindle?.at_speed),
|
||||
velocity_scale: Number(wasmMotion.state?.velocity_scale ?? 1),
|
||||
jog_scale: Number(wasmMotion.state?.jog_scale ?? 1),
|
||||
jog_velocity_linear: Number(wasmMotion.state?.jog_velocity_linear ?? 0),
|
||||
@@ -290,6 +305,17 @@ export class SimSession {
|
||||
return { ...unsigned, state_hash: fnv1a64(unsigned) };
|
||||
}
|
||||
|
||||
syncXyzacProgramSpindle(program) {
|
||||
if (!this.xyzacIo || !program || typeof program !== "object") return { status: "skipped" };
|
||||
const mode = Number(program.spindle_mode ?? 5);
|
||||
const rpm = Number(program.speed ?? 0);
|
||||
const enabled = (mode === 3 || mode === 4) && Number.isFinite(rpm) && rpm > 0;
|
||||
const signedRpm = mode === 4 ? -rpm : rpm;
|
||||
const current = this.xyzacIo.canonical()?.spindles?.[0];
|
||||
if (current && Boolean(current.enabled) === enabled && Math.abs(Number(current.command_rpm ?? 0) - (enabled ? signedRpm * Number(current.override ?? 1) : 0)) < 1e-9) return { status: "skipped" };
|
||||
return this.xyzacIo.command("SPINDLE_COMMAND", { spindle: 0, rpm: signedRpm, enabled });
|
||||
}
|
||||
|
||||
loadXyzacSubroutines(programPath) {
|
||||
if (!this.xyzacProgram) return { status: "verified", count: 0 };
|
||||
const requirement = this.xyzacProgram.requiresSubroutines();
|
||||
@@ -323,7 +349,7 @@ export class SimSession {
|
||||
}
|
||||
|
||||
panelSnapshot({ task = this.task.export(), program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : null, input = {}, tick = this.tick, motionTrace = null, includeToolpath = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeConfiguration = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeStableRuntime = input.kind !== "SIM_STEP" || !this.xyzacProgram } = {}) {
|
||||
const motion = this.xyzacProgram ? this.wasmMotionProjection() : this.motion.export();
|
||||
const motion = this.xyzacProgram ? this.wasmMotionProjection(program) : this.motion.export();
|
||||
const wasmMotion = this.wasmMotion.read();
|
||||
const homeLimit = this.wasmHomeLimit.read();
|
||||
const ioState = this.xyzacIo?.canonical?.() ?? {};
|
||||
@@ -1681,6 +1707,8 @@ export class SimSession {
|
||||
}
|
||||
const step = this.xyzacProgram.step();
|
||||
if (step.status !== "verified") throw new Error(step.code);
|
||||
const spindleSync = this.syncXyzacProgramSpindle(step.program);
|
||||
if (spindleSync.status === "error") throw new Error(spindleSync.code ?? "wasm_program_spindle_sync_failed");
|
||||
// Path control is produced by the WASM interpreter and
|
||||
// applied to the WASM trajectory queue before enqueue.
|
||||
const motionControlMode = Number(step.program?.motion_control_mode ?? 0);
|
||||
|
||||
@@ -45,6 +45,8 @@ assert.equal(halfStep.payload.status, "verified");
|
||||
assert.equal(session.wasmMotion.controls.feed_override, 0.5);
|
||||
assert.equal(halfStep.payload.panel_snapshot.overrides.feed, 0.5);
|
||||
assert.ok(halfStep.payload.wasm_motion.state.velocity > 0, JSON.stringify(halfStep.payload.wasm_motion.state));
|
||||
assert.equal(halfStep.payload.motion.velocity_mm_min, halfStep.payload.wasm_motion.state.velocity_mm_min);
|
||||
assert.ok(halfStep.payload.motion.velocity_mm_min > 0);
|
||||
|
||||
const fullOverride = send("SET_OVERRIDE", { feed: 2 });
|
||||
assert.equal(fullOverride.payload.status, "verified");
|
||||
|
||||
@@ -72,7 +72,7 @@ const contractSource = await readFile(new URL("../react/src/xyzac-panel-contract
|
||||
const sessionSource = await readFile(new URL("./sim-session.mjs", import.meta.url), "utf8");
|
||||
const tpSource = await readFile(new URL("./wasm-linuxcnc-tp.mjs", import.meta.url), "utf8");
|
||||
const combined = `${appSource}\n${panelSource}\n${contractSource}`;
|
||||
for (const symbol of ["XyzacTrtPanel", "XyzacProgramPane", "XyzacKinematicsControl", "XyzacManualPanel", "XyzacDiagnostics", "KINEMATICS_SELECT", "VISUAL_CLEAR", "POWER", "switchkinsXyzac", "Operation trace", "Alarms / recovery"]) assert.ok(combined.includes(symbol), symbol);
|
||||
for (const symbol of ["XyzacTrtPanel", "XyzacProgramPane", "XyzacKinematicsControl", "XyzacManualPanel", "XyzacLiveParameters", "XyzacDiagnostics", "Spindle", "Feed actual", "TP velocity", "Live parameters", "KINEMATICS_SELECT", "VISUAL_CLEAR", "POWER", "switchkinsXyzac", "Operation trace", "Alarms / recovery"]) assert.ok(combined.includes(symbol), symbol);
|
||||
for (const symbol of ["wasmSnapshotReady", "manualReady", "sourceJogScale", "sourceVelocityScale", "spindleOverrideSource", "SET_JOG_SCALE", "SET_MAX_VELOCITY", "SET_SPINDLE_OVERRIDE", "WASM PENDING", "LinuxCNC WASM override-limits command unavailable"]) assert.ok(appSource.includes(symbol), symbol);
|
||||
for (const forbidden of [/defaultValue=\"50\"/, /defaultValue=\"100\"/, /setBlockDelete/, /setOptionalStop/, /state\.status === \"running\" \? \"READING\"/]) assert.equal(forbidden.test(appSource), false, String(forbidden));
|
||||
for (const forbidden of [/_sim_/, /_malloc/, /\bHEAP(?:U|F|I)?\d*\b/, /navigator\.storage/, /\bOPFS\b/i, /postMessage\s*\(/]) assert.equal(forbidden.test(combined), false, String(forbidden));
|
||||
|
||||
@@ -17,7 +17,7 @@ function toPose(values) { return Object.fromEntries(["x", "y", "z", "a", "b", "c
|
||||
export class WasmLinuxcncTp {
|
||||
constructor(core, { cycleTime = 0.001, velocityLimit = 35, accelerationLimit = 300, limits = {} } = {}) {
|
||||
this.core = core;
|
||||
this.available = ["sim_linuxcnc_tp_size", "sim_linuxcnc_tp_queue_capacity", "sim_linuxcnc_tp_init", "sim_linuxcnc_tp_read_state", "sim_linuxcnc_tp_step", "sim_linuxcnc_tp_idle", "sim_linuxcnc_tp_add_line", "sim_linuxcnc_tp_set_profile", "sim_linuxcnc_tp_set_feed_override", "sim_linuxcnc_tp_set_velocity_scale", "sim_linuxcnc_tp_set_jog_scale", "sim_linuxcnc_tp_set_axis_limit", "sim_linuxcnc_tp_jog", "sim_linuxcnc_tp_velocity_scale", "sim_linuxcnc_tp_velocity_limit", "sim_linuxcnc_tp_jog_scale", "sim_linuxcnc_tp_jog_velocity", "sim_linuxcnc_tp_feed_override", "sim_linuxcnc_tp_set_dynamics", "sim_linuxcnc_tp_set_paused", "sim_linuxcnc_tp_abort", "sim_linuxcnc_tp_set_goal_position", "sim_linuxcnc_tp_queue_depth", "sim_linuxcnc_tp_queue_segment", "sim_linuxcnc_tp_restore_progress"].every((name) => typeof core?.[`_${name}`] === "function" || typeof core?.[name] === "function");
|
||||
this.available = ["sim_linuxcnc_tp_size", "sim_linuxcnc_tp_queue_capacity", "sim_linuxcnc_tp_init", "sim_linuxcnc_tp_read_state", "sim_linuxcnc_tp_step", "sim_linuxcnc_tp_idle", "sim_linuxcnc_tp_add_line", "sim_linuxcnc_tp_set_profile", "sim_linuxcnc_tp_set_feed_override", "sim_linuxcnc_tp_set_velocity_scale", "sim_linuxcnc_tp_set_jog_scale", "sim_linuxcnc_tp_set_axis_limit", "sim_linuxcnc_tp_jog", "sim_linuxcnc_tp_velocity_scale", "sim_linuxcnc_tp_velocity_limit", "sim_linuxcnc_tp_velocity_mm_min", "sim_linuxcnc_tp_jog_scale", "sim_linuxcnc_tp_jog_velocity", "sim_linuxcnc_tp_feed_override", "sim_linuxcnc_tp_set_dynamics", "sim_linuxcnc_tp_set_paused", "sim_linuxcnc_tp_abort", "sim_linuxcnc_tp_set_goal_position", "sim_linuxcnc_tp_queue_depth", "sim_linuxcnc_tp_queue_segment", "sim_linuxcnc_tp_restore_progress"].every((name) => typeof core?.[`_${name}`] === "function" || typeof core?.[name] === "function");
|
||||
this.cycleTime = cycleTime;
|
||||
this.velocityLimit = velocityLimit;
|
||||
this.accelerationLimit = accelerationLimit;
|
||||
@@ -192,7 +192,8 @@ export class WasmLinuxcncTp {
|
||||
this.controls.feed_override = feedOverride;
|
||||
const position = toPose(doubles);
|
||||
const joints = this.commandedJoints(position, this.activeTag);
|
||||
const state = { position, velocity: doubles[9], acceleration: this.accelerationLimit, progress: doubles[10], tick: ints[0], queue_depth: this.queueDepthOverride === null ? ints[1] : this.queueDepthOverride, active_queue: activeId ? 1 : 0, active_id: activeId, active_tag: this.activeTag, joint_commanded: joints, joint_actual: joints.slice(), motion_type: this.core.HEAP32[this.statePointer / 4 + 25], status: this.core.HEAP32[this.statePointer / 4 + 26], exec_state: this.executionState, velocity_limit: velocityLimit, velocity_scale: velocityScale, jog_velocity_linear: this.jogVelocity("x"), jog_velocity_angular: this.jogVelocity("a"), feed_override: feedOverride, jog_scale: jogScale, source: "linuxcnc-source-tp" };
|
||||
const velocityMmMin = Number(call(this.core, "sim_linuxcnc_tp_velocity_mm_min", "number", ["number"], [this.planner]));
|
||||
const state = { position, velocity: doubles[9], velocity_mm_min: velocityMmMin, acceleration: this.accelerationLimit, progress: doubles[10], tick: ints[0], queue_depth: this.queueDepthOverride === null ? ints[1] : this.queueDepthOverride, active_queue: activeId ? 1 : 0, active_id: activeId, active_tag: this.activeTag, joint_commanded: joints, joint_actual: joints.slice(), motion_type: this.core.HEAP32[this.statePointer / 4 + 25], status: this.core.HEAP32[this.statePointer / 4 + 26], exec_state: this.executionState, velocity_limit: velocityLimit, velocity_scale: velocityScale, jog_velocity_linear: this.jogVelocity("x"), jog_velocity_angular: this.jogVelocity("a"), feed_override: feedOverride, jog_scale: jogScale, source: "linuxcnc-source-tp" };
|
||||
this.lastState = state;
|
||||
return { status: "verified", state };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user