Fix XYZBC tool execution TCP path

This commit is contained in:
mes123456
2026-07-03 07:50:26 -04:00
parent ffdaa21d8b
commit 6631833c93
6 changed files with 1879 additions and 100 deletions

View File

@@ -47,7 +47,10 @@ import {
normalizeLinuxCncTaskMode,
} from "./linuxcnc-task-policy.js";
import { buildProgramExecutionTiming, timingAtMotionIndex } from "../runtime/execution-timing.js";
import { buildAxisPreviewPathFromProgram } from "../runtime/axis-preview-path.js";
import {
buildAxisExecutionTraceFromProgram,
buildAxisPreviewPathFromProgram,
} from "../runtime/axis-preview-path.js";
const defaultProfile = getFiveAxisProfile("xyzbc-trt");
@@ -109,6 +112,11 @@ function withDefaultWebOpfsRequirement(options = {}) {
return isBrowser ? { ...options, requireOpfs: true } : options;
}
function buildProgramAxisPathFromProgram(options = {}) {
return buildAxisExecutionTraceFromProgram(options)
|| buildAxisPreviewPathFromProgram(options);
}
const initialState = {
machineProfile: "xyzbc-trt",
availableProfiles: fiveAxisProfiles.map(({ id, title, traj, kinematicsModuleId, kinematics }) => ({
@@ -213,6 +221,8 @@ const initialState = {
programRuntimeFeedback: null,
programRuntimeFeedbackHistory: [],
programLineExecution: {},
programUiExecution: null,
linuxCncProcessMonitor: null,
taskHalRuntime: null,
taskHalRuntimeReadiness: null,
taskHalStatus: null,
@@ -349,6 +359,7 @@ initialState.machineProject = createMachineProjectState(initialState);
initialState.programValidation = createProgramValidationState(initialState);
initialState.rightSidebarEntrances = createRightSidebarEntranceState(initialState, initialState.linuxCncTaskPolicy);
initialState.linuxCncParityMatrix = createLinuxCncParityMatrix(initialState);
initialState.linuxCncProcessMonitor = createLinuxCncProcessMonitor(initialState);
const programLines = [
"; zmax zmin r frate n a b c dist",
@@ -381,7 +392,7 @@ export function createSimulationStore(seed = {}) {
programLines: seed.programLines || programLines,
};
const initialAxisPreviewPath = seed.programAxisPreviewPath === undefined
? buildAxisPreviewPathFromProgram({
? buildProgramAxisPathFromProgram({
filename: state.activeProgram,
sourceRel: state.programSourceRel,
content: state.programLines.join("\n"),
@@ -401,6 +412,7 @@ export function createSimulationStore(seed = {}) {
state.programValidation = createProgramValidationState(state);
state.rightSidebarEntrances = createRightSidebarEntranceState(state, state.linuxCncTaskPolicy);
state.linuxCncParityMatrix = createLinuxCncParityMatrix(state);
state.linuxCncProcessMonitor = createLinuxCncProcessMonitor(state);
state.fullExecutionBoundary = createFullLinuxCncExecutionBoundary(state);
const listeners = new Set();
let taskHalStatusLoopTimer = null;
@@ -449,6 +461,7 @@ export function createSimulationStore(seed = {}) {
state = {
...nextState,
linuxCncParityMatrix: createLinuxCncParityMatrix(nextState),
linuxCncProcessMonitor: createLinuxCncProcessMonitor(nextState),
fullExecutionBoundary: createFullLinuxCncExecutionBoundary(nextState),
};
notify();
@@ -714,14 +727,32 @@ export function createSimulationStore(seed = {}) {
motion: firstMotion,
timingSnapshot: firstTiming,
});
const initialUiPatch = applyProgramPlaybackUiPatch({
...state,
programExecution: execution,
programExecutionTiming: timing,
programExecutionSampleIndex: 0,
programExecutionMotionIndex: 0,
activeLine: firstMotion?.line || state.programStartLine,
axisPose: axisPoseFromCanonicalMotion(firstMotion, state.axisPose),
kinsType: firstKinsType,
rtcpState: rtcpStateFromKinsType(firstKinsType),
programRuntimeFeedback: firstFeedback,
}, {
activeLine: firstMotion?.line || state.programStartLine,
axisPose: axisPoseFromCanonicalMotion(firstMotion, state.axisPose),
kinsType: firstKinsType,
rtcpState: rtcpStateFromKinsType(firstKinsType),
motionIndex: 0,
sampleIndex: 0,
runtimeFeedback: firstFeedback,
});
setState({
programExecution: execution,
programExecutionTiming: timing,
programExecutionSourceMode: execution.sourceMode,
machineFileExecution: execution.machineFilePlan ? execution : state.machineFileExecution,
programExecutionMotionIndex: 0,
programExecutionSampleIndex: 0,
programRuntimeFeedback: firstFeedback,
...initialUiPatch,
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, firstFeedback, {
status: "ready",
source: execution.sourceMode,
@@ -729,10 +760,6 @@ export function createSimulationStore(seed = {}) {
programElapsedSeconds: firstTiming.elapsedSeconds,
programRemainingSeconds: firstTiming.remainingSeconds,
interpreterExecutionPending: false,
activeLine: firstMotion?.line || state.programStartLine,
axisPose: axisPoseFromCanonicalMotion(firstMotion, state.axisPose),
kinsType: firstKinsType,
rtcpState: rtcpStateFromKinsType(firstKinsType),
preview: {
...state.preview,
pathPoints: state.programAxisPreviewPath?.sampleCount || Math.max(execution.summary.motionEventCount, 1),
@@ -974,7 +1001,7 @@ export function createSimulationStore(seed = {}) {
programText: selectedFile.text,
sourceRel: selectedFile.sourceRel,
});
const axisPreviewPath = buildAxisPreviewPathFromProgram({
const axisPreviewPath = buildProgramAxisPathFromProgram({
filename: selectedFile.sourceRel,
sourceRel: selectedFile.sourceRel,
content: selectedFile.text,
@@ -999,6 +1026,7 @@ export function createSimulationStore(seed = {}) {
runState: "idle",
programAxisPreviewPath: axisPreviewPath,
programRuntimeFeedback: null,
programUiExecution: null,
programLineExecution: {},
preview: {
...state.preview,
@@ -1508,7 +1536,7 @@ export function createSimulationStore(seed = {}) {
const toolRuntimeState = createToolRuntimeState(toolDbSimulation, {
fallbackToolLength: state.toolPreview?.length,
});
const axisPreviewPath = buildAxisPreviewPathFromProgram({
const axisPreviewPath = buildProgramAxisPathFromProgram({
filename: loadedProgram.activeProgram,
sourceRel: loadedProgram.programSourceRel,
content: action.content,
@@ -1563,6 +1591,15 @@ export function createSimulationStore(seed = {}) {
break;
}
const playback = nextProgramRuntimeSamplePlayback(state, 5);
const playbackPatch = applyProgramPlaybackUiPatch(state, {
activeLine: playback.activeLine,
axisPose: playback.axisPose,
kinsType: playback.kinsType,
rtcpState: playback.rtcpState,
motionIndex: playback.motionIndex,
sampleIndex: playback.sampleIndex,
runtimeFeedback: playback.runtimeFeedback,
});
setState({
machine: {
...state.machine,
@@ -1573,13 +1610,7 @@ export function createSimulationStore(seed = {}) {
taskPaused: false,
},
runState: playback.complete ? "complete" : "running",
activeLine: playback.activeLine,
axisPose: playback.axisPose,
kinsType: playback.kinsType,
rtcpState: playback.rtcpState,
programExecutionMotionIndex: playback.motionIndex,
programExecutionSampleIndex: playback.sampleIndex,
programRuntimeFeedback: playback.runtimeFeedback,
...playbackPatch,
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, playback.runtimeFeedback, {
status: playback.complete ? "done" : "running",
source: playback.runtimeFeedback?.sourceMode,
@@ -1747,6 +1778,15 @@ export function createSimulationStore(seed = {}) {
break;
}
const playback = nextProgramRuntimeSamplePlayback(state, 1);
const playbackPatch = applyProgramPlaybackUiPatch(state, {
activeLine: playback.activeLine,
axisPose: playback.axisPose,
kinsType: playback.kinsType,
rtcpState: playback.rtcpState,
motionIndex: playback.motionIndex,
sampleIndex: playback.sampleIndex,
runtimeFeedback: playback.runtimeFeedback,
});
setState({
machine: {
...state.machine,
@@ -1759,13 +1799,7 @@ export function createSimulationStore(seed = {}) {
taskPaused: true,
},
runState: "stepping",
activeLine: playback.activeLine,
axisPose: playback.axisPose,
kinsType: playback.kinsType,
rtcpState: playback.rtcpState,
programExecutionMotionIndex: playback.motionIndex,
programExecutionSampleIndex: playback.sampleIndex,
programRuntimeFeedback: playback.runtimeFeedback,
...playbackPatch,
programElapsedSeconds: playback.timing.elapsedSeconds,
programRemainingSeconds: playback.timing.remainingSeconds,
feed: {
@@ -1784,6 +1818,15 @@ export function createSimulationStore(seed = {}) {
break;
}
const playback = nextProgramRuntimeSamplePlayback(state, 5);
const playbackPatch = applyProgramPlaybackUiPatch(state, {
activeLine: playback.activeLine,
axisPose: playback.axisPose,
kinsType: playback.kinsType,
rtcpState: playback.rtcpState,
motionIndex: playback.motionIndex,
sampleIndex: playback.sampleIndex,
runtimeFeedback: playback.runtimeFeedback,
});
setState({
machine: {
...state.machine,
@@ -1792,13 +1835,7 @@ export function createSimulationStore(seed = {}) {
taskPaused: false,
},
runState: "running",
activeLine: playback.activeLine,
axisPose: playback.axisPose,
kinsType: playback.kinsType,
rtcpState: playback.rtcpState,
programExecutionMotionIndex: playback.motionIndex,
programExecutionSampleIndex: playback.sampleIndex,
programRuntimeFeedback: playback.runtimeFeedback,
...playbackPatch,
programElapsedSeconds: playback.timing.elapsedSeconds,
programRemainingSeconds: playback.timing.remainingSeconds,
feed: {
@@ -1815,6 +1852,7 @@ export function createSimulationStore(seed = {}) {
setState({ operatorMessage: gate.operatorMessage });
break;
}
const homeAxisPose = homeAxisPoseForState(state);
if (state.taskHalRuntime?.loaded) {
setState({
machine: {
@@ -1827,7 +1865,7 @@ export function createSimulationStore(seed = {}) {
taskPaused: false,
},
runState: "idle",
axisPose: initialAxisPose,
axisPose: homeAxisPose,
programRuntimeFeedback: null,
operatorMessage: "task/HAL home requested",
});
@@ -1835,6 +1873,7 @@ export function createSimulationStore(seed = {}) {
{ type: "EMC_JOINT_HOME", joint: -1 },
], {
operatorMessage: "task/HAL machine homed",
preserveAxisPose: homeAxisPose,
preserveMachine: {
...state.machine,
manualPanel: "manual",
@@ -1854,7 +1893,7 @@ export function createSimulationStore(seed = {}) {
taskPaused: false,
},
runState: "idle",
axisPose: initialAxisPose,
axisPose: homeAxisPose,
programRuntimeFeedback: null,
operatorMessage: "machine homed to fixture origin",
});
@@ -2125,6 +2164,7 @@ export function createSimulationStore(seed = {}) {
state = {
...derivedState,
linuxCncParityMatrix: createLinuxCncParityMatrix(derivedState),
linuxCncProcessMonitor: createLinuxCncProcessMonitor(derivedState),
fullExecutionBoundary: createFullLinuxCncExecutionBoundary(derivedState),
};
notify();
@@ -2954,6 +2994,183 @@ function createProgramValidationState(state = {}) {
};
}
function createLinuxCncProcessMonitor(state = {}) {
const feedback = state.programRuntimeFeedback || {};
const uiExecution = state.programUiExecution || createProgramUiExecution(state);
const status = state.taskHalStatus || {};
const ui = status.ui || {};
const motion = status.motionStatus?.motion || {};
const halPins = status.halSnapshot?.pins || {};
const toolRuntime = state.toolRuntimeState || {};
const toolDb = state.toolDbSimulation || {};
const currentTool = toolRuntime.currentTool || toolRuntime.activeToolOffset || toolRuntime.pathTool || null;
const path = state.programAxisPreviewPath || null;
const previewTool = path?.samples?.find((sample) => sample?.tool)?.tool || null;
const runtimePathTool = toolRuntime.pathTool || null;
const pathTool = Number(runtimePathTool?.id || 0) > 0 || Number(runtimePathTool?.diameter || 0) > 0
? runtimePathTool
: previewTool;
const sampleCount = Number(path?.sampleCount || path?.samples?.length || 0);
const sampleIndex = clampNumber(
Number(state.programExecutionSampleIndex ?? feedback.sampleIndex ?? 0),
0,
Math.max(sampleCount - 1, 0),
);
const motionCount = Number(state.programExecution?.summary?.motionEventCount || state.programExecution?.motion?.length || 0);
const motionIndex = clampNumber(
Number(state.programExecutionMotionIndex ?? feedback.motionIndex ?? 0),
0,
Math.max(motionCount - 1, 0),
);
const sourceLine = Number(feedback.line || state.activeLine || 0);
const activeGcode = uiExecution?.statement || (sourceLine > 0
? state.programLines?.[sourceLine - Number(state.programStartLine || 1)] || ""
: "");
const currentVelocity = Number(feedback.currentVelocityMmPerMin ?? state.feed?.currentVelocity ?? ui.currentVelocity ?? 0);
const requestedVelocity = Number(feedback.requestedVelocityMmPerMin ?? motion.requestedVel * 60 ?? currentVelocity);
const feedRate = Number(state.feed?.feedRate || currentVelocity || 0);
const spindleCommandRpm = Number(state.spindle?.rpm || 0);
const spindleActualRpm = state.spindle?.enabled
? spindleCommandRpm * (Number(state.spindle?.override || 100) / 100)
: 0;
const toolChange = {
toolInSpindle: Number(toolRuntime.toolInSpindle || toolDb.toolInSpindle || 0),
toolFromPocket: Number(toolRuntime.toolFromPocket || toolDb.toolFromPocket || 0),
currentPocket: Number(toolRuntime.currentPocket || toolDb.currentPocket || 0),
preparedTool: Number(toolDb.preparedTool || 0),
preparedPocket: Number(toolDb.preparedPocket || 0),
activeToolNumber: Number(toolRuntime.activeToolNumber || pathTool?.id || currentTool?.toolNumber || 0),
activePocket: Number(toolRuntime.activePocket || pathTool?.pocket || currentTool?.pocket || 0),
diameter: Number(pathTool?.diameter ?? currentTool?.diameter ?? 0),
lengthOffsetZ: Number(toolRuntime.kinematics?.toolOffsetZ ?? currentTool?.offset?.z ?? pathTool?.length ?? 0),
activeOffsetApplied: Boolean(toolRuntime.activeOffsetApplied),
iocontrol: {
toolPrepare: Boolean(toolDb.iocontrol?.toolPrepare),
toolPrepared: Boolean(toolDb.iocontrol?.toolPrepared),
toolChange: Boolean(toolDb.iocontrol?.toolChange),
toolChanged: Boolean(toolDb.iocontrol?.toolChanged),
toolPrepNumber: Number(toolDb.iocontrol?.toolPrepNumber || 0),
toolPrepPocket: Number(toolDb.iocontrol?.toolPrepPocket || 0),
},
emcioStatus: toolDb.emcioStatus?.status || "UNKNOWN",
};
return {
apiName: "web-rtcp-5axis-linuxcnc-process-monitor",
semanticBoundary: "linuxcnc_task_motion_hal_interpreter_tool_status_monitor",
sourceReferences: {
task: "linuxcnc/src/emc/task/emctaskmain.cc",
motion: "linuxcnc/src/emc/motion/control.c",
interpreter: "linuxcnc/src/emc/rs274ngc",
axisGui: "linuxcnc/src/emc/usr_intf/axis/scripts/axis.py",
halPins: "linuxcnc/src/emc/usr_intf/halui.cc + src/emc/iotask/ioControl.cc",
toolChange: "linuxcnc/src/emc/task/taskclass.cc + src/emc/tooldata/tooldata_common.cc",
},
control: {
powerOn: Boolean(state.machine?.powerOn),
estopActive: Boolean(state.machine?.estopActive),
taskState: state.machine?.taskState || "estop-reset",
taskMode: state.machine?.mode || "manual",
interpState: state.machine?.interpState || "idle",
runState: state.runState || "idle",
allHomed: Boolean(state.machine?.allHomed),
taskPaused: Boolean(state.machine?.taskPaused),
buttonParityCount: 57,
policy: state.linuxCncTaskPolicy || null,
},
path: {
activeProgram: state.activeProgram || null,
programSourceRel: state.programSourceRel || state.machineFileStaging?.selectedGcodeSourceRel || null,
previewSource: path?.source || state.programValidation?.previewSource || state.programExecutionSourceMode,
executionSource: feedback.sourceMode || state.programExecutionSourceMode,
activeLine: sourceLine,
sourceFile: uiExecution?.sourceFile || null,
sourceLine: Number(uiExecution?.line || sourceLine || 0),
activeGcode,
uiExecution,
motionIndex,
motionCount,
sampleIndex,
sampleCount,
elapsedSeconds: Number(state.programElapsedSeconds || feedback.timeSeconds || 0),
remainingSeconds: Number(state.programRemainingSeconds || 0),
queueDepth: Number(feedback.queueDepth || ui.motionQueueDepth || 0),
activeDepth: Number(feedback.activeDepth || 0),
lineExecution: state.programLineExecution?.[sourceLine] || null,
},
axes: {
linearUnits: state.profile?.traj?.linearUnits || feedback.linearUnits || "mm",
rotaryUnits: "deg",
positionSource: feedback.sourceMode || state.frameSourceMode || "ui-state",
dro: { ...(state.dro || {}) },
joint: { ...pickExecutionAxes(state.axisPose || {}) },
tcp: {
x: Number(state.tcpPose?.x || 0),
y: Number(state.tcpPose?.y || 0),
z: Number(state.tcpPose?.z || 0),
},
distanceToGo: {
x: Number(state.dro?.dtgX || 0),
y: Number(state.dro?.dtgY || 0),
z: Number(state.dro?.dtgZ || 0),
scalar: Number(feedback.distanceToGo || 0),
},
toolAxisVector: { ...(state.toolAxisVector || {}) },
kinsType: state.kinsType || "identity",
rtcpState: state.rtcpState || "off",
},
spindle: {
enabled: Boolean(state.spindle?.enabled),
direction: state.spindle?.direction || "stop",
commandRpm: spindleCommandRpm,
actualRpm: spindleActualRpm,
overridePercent: Number(state.spindle?.override || 0),
halPins: {
on: Number(halPins["spindle.0.on"]?.value ?? (state.spindle?.enabled ? 1 : 0)),
forward: Number(halPins["spindle.0.forward"]?.value ?? (state.spindle?.direction === "forward" ? 1 : 0)),
reverse: Number(halPins["spindle.0.reverse"]?.value ?? (state.spindle?.direction === "reverse" ? 1 : 0)),
speedOut: Number(halPins["spindle.0.speed-out"]?.value ?? spindleActualRpm),
},
},
feed: {
currentVelocityMmPerMin: currentVelocity,
requestedVelocityMmPerMin: requestedVelocity,
cuttingVelocityMmPerMin: isCuttingMotion(feedback) ? currentVelocity : 0,
feedRate,
feedOverridePercent: Number(state.feed?.feedOverride || 0),
rapidOverridePercent: Number(state.feed?.rapidOverride || 0),
feedMode: feedback.feedMode || "units-per-minute",
},
coolant: {
flood: Boolean(state.coolant?.flood),
mist: Boolean(state.coolant?.mist),
halPins: {
flood: Number(halPins["iocontrol.0.coolant-flood"]?.value ?? (state.coolant?.flood ? 1 : 0)),
mist: Number(halPins["iocontrol.0.coolant-mist"]?.value ?? (state.coolant?.mist ? 1 : 0)),
},
},
toolChange,
runtime: {
interpreterReady: state.interpreterRuntimeReadiness?.loaded === true,
kinematicsReady: state.kinematicsRuntimeReadiness?.loaded === true,
taskHalReady: state.taskHalRuntimeReadiness?.taskRuntimeReady === true
&& state.taskHalRuntimeReadiness?.motionRuntimeReady === true
&& state.taskHalRuntimeReadiness?.halRuntimeReady === true,
taskHalLoopActive: state.taskHalStatusLoop?.active === true,
taskHalTickCount: Number(state.taskHalStatusLoop?.tickCount || 0),
taskCycle: Number(feedback.taskCycle || ui.taskCycle || 0),
servoCycle: Number(feedback.cycle || ui.servoCycle || 0),
halChangedPinCount: Number(feedback.halChangedPinCount || ui.halChangedPinCount || 0),
fallbackReason: state.taskHalFallbackReason || null,
},
};
}
function isCuttingMotion(feedback = {}) {
const type = String(feedback.type || "").toUpperCase();
return type.includes("FEED") || type.includes("ARC") || type === "TASK_MOTION";
}
function createRightSidebarEntranceState(state = {}, taskPolicy = createLinuxCncTaskPolicyStatus(state)) {
const manualActive = state.machine?.mode === "manual" && state.machine?.manualPanel !== "jog";
const jogActive = state.machine?.mode === "manual" && state.machine?.manualPanel === "jog";
@@ -3276,12 +3493,25 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
? "jogging"
: "idle";
const runtimeFeedback = createTaskHalRuntimeFeedback(state, status, axisPose, activeLine);
const taskHalSampleIndex = resolveRuntimeSampleIndexForLine(state, activeLine, runtimeFeedback.sampleIndex);
const taskHalPlaybackPatch = applyProgramPlaybackUiPatch(state, {
activeLine,
axisPose,
kinsType,
rtcpState: rtcpStateFromKinsType(kinsType),
motionIndex: runtimeFeedback.motionIndex,
sampleIndex: taskHalSampleIndex,
runtimeFeedback: {
...runtimeFeedback,
sampleIndex: taskHalSampleIndex,
},
});
const loopActive = state.taskHalStatusLoop?.active === true
&& loopSequence !== null
&& Number(state.taskHalStatusLoop.sequence) === Number(loopSequence)
&& (runState === "running" || runState === "mdi");
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
const feedbackHistory = [runtimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
const feedbackHistory = [taskHalPlaybackPatch.programRuntimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
const lineStatus = runState === "complete"
? "done"
: runState === "running" || runState === "mdi"
@@ -3293,10 +3523,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
taskHalExecutionPending: false,
taskHalFallbackReason: null,
pendingJogCommand: null,
activeLine,
axisPose,
kinsType,
rtcpState: rtcpStateFromKinsType(kinsType),
...taskHalPlaybackPatch,
programExecutionSourceMode: "linuxcnc-task-motion-hal-wasm",
machine: {
...state.machine,
@@ -3325,9 +3552,8 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
...state.feed,
currentVelocity,
},
programRuntimeFeedback: runtimeFeedback,
programRuntimeFeedbackHistory: feedbackHistory,
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, runtimeFeedback, {
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, taskHalPlaybackPatch.programRuntimeFeedback, {
status: lineStatus,
source: "linuxcnc-task-motion-hal-wasm",
}),
@@ -3494,6 +3720,195 @@ function createTaskHalRuntimeFeedback(state, status, axisPose, activeLine) {
};
}
function resolveRuntimeSampleIndexForLine(state = {}, activeLine, fallbackSampleIndex = 0) {
const samples = state.programAxisPreviewPath?.samples;
if (!Array.isArray(samples) || samples.length === 0) {
return Number(fallbackSampleIndex) || 0;
}
const line = Number(activeLine);
if (!Number.isFinite(line)) {
return clampNumber(fallbackSampleIndex, 0, samples.length - 1);
}
const current = clampNumber(state.programExecutionSampleIndex || 0, 0, samples.length - 1);
for (let index = current; index < samples.length; index += 1) {
if (Number(samples[index]?.line) >= line) return index;
}
return samples.length - 1;
}
function applyProgramPlaybackUiPatch(state = {}, {
activeLine,
axisPose,
kinsType,
rtcpState,
motionIndex,
sampleIndex,
runtimeFeedback,
} = {}) {
const sample = currentProgramPathSample(state, sampleIndex);
const sampleAxisPose = axisPoseFromProgramPathSample(sample, axisPose || state.axisPose, state.profile);
const sampleKinsType = sample?.activeKinematics
? normalizeSampleKinsType(state, sample.activeKinematics)
: kinsType;
const sampleLine = Number(sample?.line || activeLine || state.activeLine || 0);
const sampleMotionIndex = Number.isFinite(Number(sample?.segmentIndex))
? Number(sample.segmentIndex)
: Number(motionIndex || 0);
const sampleSampleIndex = Number.isFinite(Number(sample?.sampleIndex))
? Number(sample.sampleIndex)
: Number(sampleIndex || 0);
const toolAxisVector = toolAxisVectorFromSample(sample, state.toolAxisVector);
const enrichedFeedback = enrichRuntimeFeedbackWithSample(runtimeFeedback, sample, {
...state,
activeLine: sampleLine,
axisPose: sampleAxisPose,
kinsType: sampleKinsType || state.kinsType,
toolAxisVector,
});
const patchState = {
...state,
activeLine: sampleLine,
axisPose: sampleAxisPose,
kinsType: sampleKinsType || state.kinsType,
rtcpState: sampleKinsType ? rtcpStateFromKinsType(sampleKinsType) : rtcpState || state.rtcpState,
toolAxisVector,
programExecutionMotionIndex: sampleMotionIndex,
programExecutionSampleIndex: sampleSampleIndex,
programRuntimeFeedback: enrichedFeedback,
};
return {
activeLine: patchState.activeLine,
axisPose: patchState.axisPose,
kinsType: patchState.kinsType,
rtcpState: patchState.rtcpState,
toolAxisVector,
programExecutionMotionIndex: patchState.programExecutionMotionIndex,
programExecutionSampleIndex: patchState.programExecutionSampleIndex,
programRuntimeFeedback: enrichedFeedback,
programUiExecution: createProgramUiExecution(patchState),
};
}
function createProgramUiExecution(state = {}) {
const sample = currentProgramPathSample(state, state.programExecutionSampleIndex);
const feedback = state.programRuntimeFeedback || {};
const sampleCount = Number(state.programAxisPreviewPath?.sampleCount || state.programAxisPreviewPath?.samples?.length || 0);
const lineExecutionTrace = state.programAxisPreviewPath?.lineExecutionTrace || [];
const traceEntry = sample
? lineExecutionTrace.find((entry) => (
Number(entry.segmentIndex) === Number(sample.segmentIndex)
&& entry.sourceFile === sample.sourceFile
&& Number(entry.line) === Number(sample.line)
)) || null
: null;
const gcodeSteps = state.programAxisPreviewPath?.gcodeExecutionProcess?.executionSteps || [];
const gcodeStep = traceEntry
? gcodeSteps.find((step) => Number(step.result?.traceExecutionIndex) === Number(traceEntry.executionIndex)) || null
: null;
const sourceFile = sample?.sourceFile || traceEntry?.sourceFile || feedback.sourceFile || null;
const sourceLine = Number(sample?.line || traceEntry?.line || feedback.line || state.activeLine || 0);
const sampleIndex = Number(sample?.sampleIndex ?? state.programExecutionSampleIndex ?? 0);
return {
apiName: "web-rtcp-5axis-ui-gcode-live-execution",
status: state.runState || "idle",
source: sample
? "programAxisPreviewPath.samples"
: feedback.sourceMode || state.programExecutionSourceMode || "ui-state",
samplePeriodMs: Number(state.programAxisPreviewPath?.samplePeriodMs || 0),
sampleIndex,
sampleCount,
executedSampleCount: sampleCount > 0 ? Math.min(sampleIndex + 1, sampleCount) : 0,
timeMs: Number(sample?.timeMs ?? feedback.timeSeconds * 1000 ?? 0),
activeProgram: state.activeProgram || null,
sourceFile,
line: sourceLine,
statement: sample?.statement || traceEntry?.statement || gcodeStep?.statement || "",
operation: traceEntry?.operation || gcodeStep?.result?.operation || null,
executionIndex: traceEntry?.executionIndex ?? null,
gcodeStepIndex: gcodeStep?.stepIndex ?? null,
segmentIndex: sample?.segmentIndex ?? traceEntry?.segmentIndex ?? null,
motionType: sample?.motionType || traceEntry?.motionType || feedback.motionType || feedback.type || null,
activeKinematics: sample?.activeKinematics || traceEntry?.activeKinematicsAfter || state.kinsType,
joint: sample?.joint || pickExecutionAxes(state.axisPose || {}),
tcp: sample?.tcp || {
x: Number(state.tcpPose?.x || state.axisPose?.x || 0),
y: Number(state.tcpPose?.y || state.axisPose?.y || 0),
z: Number(state.tcpPose?.z || state.axisPose?.z || 0),
},
toolAxis: sample?.toolAxis || toolAxisSampleFromVector(state.toolAxisVector),
tool: sample?.tool || currentPathTool(state),
machineState: sample?.machineState || null,
semanticBoundary: "ui_updates_from_real_expanded_linuxcnc_gcode_sample_stream",
};
}
function currentProgramPathSample(state = {}, sampleIndex = 0) {
const samples = state.programAxisPreviewPath?.samples;
if (!Array.isArray(samples) || samples.length === 0) return null;
const index = clampNumber(sampleIndex, 0, samples.length - 1);
return samples[index] || null;
}
function axisPoseFromProgramPathSample(sample, fallbackPose = {}, profile = defaultProfile) {
const joint = sample?.joint || {};
return clampAxisPoseToProfile({
...fallbackPose,
x: numberOrFallback(joint.x, fallbackPose.x, 0),
y: numberOrFallback(joint.y, fallbackPose.y, 0),
z: numberOrFallback(joint.z, fallbackPose.z, 0),
b: numberOrFallback(joint.b, fallbackPose.b, 0),
c: numberOrFallback(joint.c, fallbackPose.c, 0),
}, profile || defaultProfile);
}
function normalizeSampleKinsType(state = {}, value) {
if (value === "tcp-xyzbc" || value === "tcp-xyzac" || value === "identity" || value === "userk") {
return value;
}
if (value === "tcp") return tcpKinsTypeForProfile(state.profile) || value;
return value || null;
}
function toolAxisVectorFromSample(sample, fallbackVector = {}) {
const axis = sample?.toolAxis || {};
return {
x: numberOrFallback(axis.x, axis.i, fallbackVector.x, 0),
y: numberOrFallback(axis.y, axis.j, fallbackVector.y, 0),
z: numberOrFallback(axis.z, axis.k, fallbackVector.z, 1),
};
}
function toolAxisSampleFromVector(vector = {}) {
return {
i: Number(vector.x || 0),
j: Number(vector.y || 0),
k: Number(vector.z ?? 1),
};
}
function enrichRuntimeFeedbackWithSample(feedback = {}, sample = null, state = {}) {
if (!sample) return feedback;
const tcp = sample.tcp || {
x: Number(state.tcpPose?.x ?? state.axisPose?.x ?? feedback?.axisPose?.x ?? 0),
y: Number(state.tcpPose?.y ?? state.axisPose?.y ?? feedback?.axisPose?.y ?? 0),
z: Number(state.tcpPose?.z ?? state.axisPose?.z ?? feedback?.axisPose?.z ?? 0),
};
return {
...(feedback || {}),
sampleIndex: Number(sample.sampleIndex || 0),
motionIndex: Number(sample.segmentIndex || feedback?.motionIndex || 0),
line: Number(sample.line || feedback?.line || 0),
sourceFile: sample.sourceFile || feedback?.sourceFile || null,
statement: sample.statement || feedback?.statement || "",
motionType: sample.motionType || feedback?.motionType || null,
activeKinematics: sample.activeKinematics || feedback?.activeKinematics || state.kinsType,
axisPose: axisPoseFromProgramPathSample(sample, state.axisPose || feedback?.axisPose || {}, state.profile),
tcp,
toolAxis: sample.toolAxis || feedback?.toolAxis || null,
machineState: sample.machineState || feedback?.machineState || null,
};
}
function createProgramLineExecutionPatch(previous = {}, feedback = null, {
status = "running",
source = null,
@@ -3637,6 +4052,24 @@ function clampAxisPoseToProfile(axisPose, profile = defaultProfile) {
return next;
}
function homeAxisPoseForState(state = {}) {
if (state.profile?.id === "xyzac-trt" || state.profile?.id === "xyzbc-trt") {
return clampAxisPoseToProfile({
...initialAxisPose,
x: 43,
y: -32.15,
z: -11.306,
}, state.profile);
}
const pose = { ...initialAxisPose };
for (const joint of state.profile?.jointConfig || []) {
const axis = String(joint.axis || "").toLowerCase();
if (!axis) continue;
pose[axis] = Number(joint.home || 0);
}
return clampAxisPoseToProfile(pose, state.profile || defaultProfile);
}
function executeMdiCommand(state, rawCommand) {
const command = normalizeMdiCommand(rawCommand);
if (!command) {
@@ -3937,6 +4370,61 @@ function nextProgramRuntimeSamplePlayback(state, step) {
};
}
const pathSamples = state.programAxisPreviewPath?.samples;
if (Array.isArray(pathSamples) && pathSamples.length > 0) {
const sampleIndex = Math.min(
Number(state.programExecutionSampleIndex || 0) + Math.max(Number(step) || 1, 1),
pathSamples.length - 1,
);
const sample = pathSamples[sampleIndex];
const motionIndex = Number.isFinite(Number(sample?.segmentIndex)) ? Number(sample.segmentIndex) : 0;
const axisPose = axisPoseFromProgramPathSample(sample, state.axisPose, state.profile);
const kinsType = sample?.activeKinematics
? normalizeSampleKinsType(state, sample.activeKinematics)
: state.kinsType;
const elapsedSeconds = Number(sample?.timeMs || 0) / 1000;
const currentVelocity = Number(sample?.machineState?.feed?.actualMmPerMin || sample?.feed || 0);
const runtimeFeedback = {
apiName: "web-rtcp-5axis-program-runtime-feedback",
sourceMode: "web-axis-source-execution-expanded-ngcgui-subroutines",
semanticBoundary: "linuxcnc_xyzbc_switchkins_sample_stream_feedback",
sampleIndex,
motionIndex,
sourceFile: sample?.sourceFile || null,
statement: sample?.statement || "",
line: sample?.line || state.activeLine,
type: sample?.motionType || null,
motionType: sample?.motionType || null,
linearUnits: state.profile?.traj?.linearUnits || "mm",
timeSeconds: elapsedSeconds,
axisPose,
currentVelocityMmPerMin: currentVelocity,
requestedVelocityMmPerMin: currentVelocity,
distanceToGo: sampleIndex >= pathSamples.length - 1 ? 0 : 1,
dtg: { x: 0, y: 0, z: 0 },
queueDepth: 0,
activeDepth: sampleIndex >= pathSamples.length - 1 ? 0 : 1,
cycle: sampleIndex,
};
return {
motionIndex,
sampleIndex,
activeLine: sample?.line || state.activeLine,
axisPose,
kinsType,
rtcpState: rtcpStateFromKinsType(kinsType),
timing: {
elapsedSeconds,
remainingSeconds: Math.max(((pathSamples.length - 1) * Number(state.programAxisPreviewPath?.samplePeriodMs || 0)) / 1000 - elapsedSeconds, 0),
currentVelocity,
segmentDurationSeconds: Number(state.programAxisPreviewPath?.samplePeriodMs || 0) / 1000,
segmentDistanceMm: 0,
},
runtimeFeedback,
complete: sampleIndex >= pathSamples.length - 1,
};
}
const playback = nextProgramPlayback(state, step);
return {
...playback,

View File

@@ -16,6 +16,7 @@ const CAMERA_PRESETS = {
};
const MAX_TOOLPATH_POINTS = Number.POSITIVE_INFINITY;
const EMPTY_GEOMETRY = new THREE.BufferGeometry().setFromPoints([]);
const LOCAL_TOOL_AXIS = new THREE.Vector3(0, 0, 1);
export function renderFiveAxisScene(canvas, state) {
let preview = scenes.get(canvas);
@@ -134,6 +135,7 @@ function createScene(canvas) {
toolMarker,
toolAxis,
currentToolhead: new THREE.Vector3(),
currentToolGlyphAxis: new THREE.Vector3(0, 0, 1),
currentVismachModelState: null,
lastSelectedView: null,
lastCameraRevision: null,
@@ -254,6 +256,7 @@ function exposePreviewDataset(canvas, state, preview) {
canvas.dataset.threeLinearUnits = linearUnitsLabel(resolveSceneLinearUnits(state));
canvas.dataset.threeLinearUnitScaleToMeters = String(linearUnitsToMetersFactor(resolveSceneLinearUnits(state)));
canvas.dataset.threeToolAxis = JSON.stringify(toRoundedVector(state.toolAxisVector));
canvas.dataset.threeToolGlyphAxis = JSON.stringify(toRoundedVector(preview.toolGlyphAxis || preview.currentToolGlyphAxis || state.toolAxisVector));
canvas.dataset.threeTcpPose = JSON.stringify(toRoundedPose(state.tcpPose));
canvas.dataset.threeRtcpState = state.rtcpState;
canvas.dataset.threeSelectedView = state.preview.selectedView;
@@ -360,13 +363,13 @@ function createMachineReferenceModel() {
new THREE.MeshBasicMaterial({ color: 0xf1f5f9 }),
);
holderBody.rotation.x = Math.PI / 2;
holderBody.position.z = 0.032;
holderBody.position.z = 0.046;
const cutter = new THREE.Mesh(
new THREE.ConeGeometry(0.006, 0.025, 18),
new THREE.MeshBasicMaterial({ color: 0xfff176 }),
);
cutter.rotation.x = Math.PI;
cutter.position.z = 0.008;
cutter.rotation.x = -Math.PI / 2;
cutter.position.z = 0.0125;
toolHolder.add(holderBody, cutter);
toolOffsetAxis.add(toolHolder);
@@ -426,14 +429,14 @@ function createAxisReferencePreviewModel() {
new THREE.ConeGeometry(0.0017, 0.0048, 4),
new THREE.MeshBasicMaterial({ color: 0xe7eef7 }),
);
cone.rotation.x = Math.PI;
cone.position.z = 0.0047;
cone.rotation.x = -Math.PI / 2;
cone.position.z = 0.0024;
const holder = new THREE.Mesh(
new THREE.CylinderGeometry(0.0011, 0.0011, 0.0065, 8),
new THREE.MeshBasicMaterial({ color: 0xbfc8d0 }),
);
holder.rotation.x = Math.PI / 2;
holder.position.z = 0.008;
holder.position.z = 0.00805;
tool.add(cone, holder);
root.add(xAxis, yAxis, zAxis, dimensions, tool, ...labels);
@@ -576,7 +579,10 @@ function updateMachineReferenceModel(preview, state, toolPosition, axisReference
preview.axisReference.root.visible = axisReferenceMode;
if (axisReferenceMode) {
const referenceToolPosition = new THREE.Vector3(0, 0, 0.006);
const toolVector = toToolVector(state.toolAxisVector);
preview.axisReference.tool.position.copy(referenceToolPosition);
alignToolGlyphToAxis(preview.axisReference.tool, toolVector);
preview.currentToolGlyphAxis.copy(toolVector);
}
}
if (axisReferenceMode) {
@@ -601,7 +607,17 @@ function updateMachineReferenceModel(preview, state, toolPosition, axisReference
const tcpPosition = toolPosition || toPreviewVector(state.tcpPose || state.axisPose, state);
const toolVector = toToolVector(state.toolAxisVector);
model.toolHolder.lookAt(tcpPosition.clone().add(toolVector));
model.toolHolder.position.copy(tcpPosition);
alignToolGlyphToAxis(model.toolHolder, toolVector);
preview.currentToolGlyphAxis.copy(toolVector);
}
function alignToolGlyphToAxis(object, toolVector) {
const axis = toolVector.clone().normalize();
if (!Number.isFinite(axis.x) || !Number.isFinite(axis.y) || !Number.isFinite(axis.z) || axis.lengthSq() === 0) {
axis.copy(LOCAL_TOOL_AXIS);
}
object.quaternion.setFromUnitVectors(LOCAL_TOOL_AXIS, axis);
}
function updateLineGeometry(line, points) {
@@ -745,6 +761,14 @@ function buildFixturePreviewPoints(pointCount, tcpPosition) {
}
function executionToolPosition(state, previewPoints) {
const uiTcp = state.programUiExecution?.tcp;
if (uiTcp && hasLinearAxes(uiTcp)) {
return vectorFromAxes(uiTcp, state, state.programRuntimeFeedback?.linearUnits || "mm");
}
const feedbackTcp = state.programRuntimeFeedback?.tcp;
if (feedbackTcp && hasLinearAxes(feedbackTcp)) {
return vectorFromAxes(feedbackTcp, state, state.programRuntimeFeedback?.linearUnits || "mm");
}
const feedbackAxes = state.programRuntimeFeedback?.axisPose || state.programRuntimeFeedback;
if (feedbackAxes && hasLinearAxes(feedbackAxes)) {
return vectorFromAxes(feedbackAxes, state, state.programRuntimeFeedback?.linearUnits);
@@ -811,7 +835,7 @@ function previewSourceMode(state) {
}
function toolpathPreviewSource(state) {
if (state.programAxisPreviewPath?.source === "web-axis-preview-expanded-ngcgui-subroutines") {
if (isAxisReferencePreview(state)) {
return "axis_preview_expanded_ngcgui_subroutines";
}
if (state.programExecution?.sourceMode === "linuxcnc-interpreter-wasm") {
@@ -824,7 +848,9 @@ function toolpathPreviewSource(state) {
}
function isAxisReferencePreview(state) {
return state.programAxisPreviewPath?.source === "web-axis-preview-expanded-ngcgui-subroutines";
return state.programAxisPreviewPath?.source === "web-axis-preview-expanded-ngcgui-subroutines"
|| state.programAxisPreviewPath?.source === "web-axis-source-execution-expanded-ngcgui-subroutines"
|| Boolean(state.programAxisPreviewPath?.gcodeExecutionProcess);
}
function toolExecutionTraceSource(state) {