Keep XYZAC tool motion smooth and expose rotary DRO values
This commit is contained in:
@@ -87,8 +87,8 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
let frame = 0;
|
||||
let liveTick = -1;
|
||||
let liveTrace: THREE.Vector3[] = [];
|
||||
let pathKey = "";
|
||||
let liveKey = "";
|
||||
let pathSegments: Segment[] | null = null;
|
||||
let segmentById = new Map<number, Segment>();
|
||||
let bounds = { center: new THREE.Vector3(), radius: .12 };
|
||||
let playbackQueue: Array<{ tick: number; pose: Point; joints: number[]; activeSegmentId: number; activeProgress: number }> = [];
|
||||
let playbackStart = 0;
|
||||
@@ -98,6 +98,10 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
let renderedJoints: number[] = [];
|
||||
let renderedActiveSegment = 0;
|
||||
let renderedProgress = 0;
|
||||
let liveHistoryLine: THREE.Line | null = null;
|
||||
let activeLiveLine: THREE.Line | null = null;
|
||||
let lastHistoryTick = -1;
|
||||
let lastActiveKey = "";
|
||||
let lastPreset = "";
|
||||
const presetCamera = (selected: Props["view"], selectedZoom: number) => {
|
||||
const distance = bounds.radius * 2.9 / Math.max(.5, selectedZoom);
|
||||
@@ -131,12 +135,11 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
const segments = state.toolpath as Segment[];
|
||||
const layer = layersRef.current;
|
||||
const now = performance.now();
|
||||
if (state.tick < queuedLastTick) {
|
||||
playbackQueue = [];
|
||||
playbackStart = 0;
|
||||
queuedLastTick = -1;
|
||||
}
|
||||
if (!state.motionTrace.length && state.tick > queuedLastTick) {
|
||||
// React can deliver a full snapshot just behind an already queued
|
||||
// motion-delta response. Keep the rendered pose monotonic until the
|
||||
// newer samples arrive; otherwise the tool visibly jumps backward.
|
||||
const staleState = state.tick < queuedLastTick;
|
||||
if (!staleState && !state.motionTrace.length && state.tick > queuedLastTick) {
|
||||
playbackQueue = [];
|
||||
playbackStart = 0;
|
||||
queuedLastTick = state.tick;
|
||||
@@ -146,7 +149,7 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
renderedActiveSegment = state.activeSegmentId ?? 0;
|
||||
renderedProgress = state.activeProgress;
|
||||
}
|
||||
for (const sample of state.motionTrace) {
|
||||
for (const sample of staleState ? [] : state.motionTrace) {
|
||||
if (sample.tick > queuedLastTick) {
|
||||
playbackQueue.push(sample);
|
||||
queuedLastTick = sample.tick;
|
||||
@@ -156,16 +159,18 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
if (playbackQueue.length) {
|
||||
const index = Math.min(playbackQueue.length - 1, Math.max(0, Math.floor((now - playbackStart) / 10)));
|
||||
const sample = playbackQueue[index];
|
||||
renderedTick = sample.tick;
|
||||
renderedPose = sample.pose;
|
||||
renderedJoints = sample.joints;
|
||||
renderedActiveSegment = sample.activeSegmentId;
|
||||
renderedProgress = sample.activeProgress;
|
||||
if (sample.tick >= renderedTick) {
|
||||
renderedTick = sample.tick;
|
||||
renderedPose = sample.pose;
|
||||
renderedJoints = sample.joints;
|
||||
renderedActiveSegment = sample.activeSegmentId;
|
||||
renderedProgress = sample.activeProgress;
|
||||
}
|
||||
if (index > 32) {
|
||||
playbackQueue.splice(0, index);
|
||||
playbackStart += index * 10;
|
||||
}
|
||||
} else {
|
||||
} else if (!staleState) {
|
||||
renderedTick = state.tick;
|
||||
renderedPose = state.livePose;
|
||||
renderedJoints = state.joints;
|
||||
@@ -175,9 +180,14 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
// The planned path is immutable for a loaded program. Rebuilding all
|
||||
// geometries on every animation frame starves worker snapshots and
|
||||
// makes an otherwise continuous WASM trajectory appear to stutter.
|
||||
const nextPathKey = segments.map((segment) => `${segment.id}:${segment.kind ?? ""}:${segment.samples?.length ?? 0}:${segment.start?.x ?? 0},${segment.start?.y ?? 0},${segment.start?.z ?? 0}:${segment.end?.x ?? 0},${segment.end?.y ?? 0},${segment.end?.z ?? 0}`).join("|");
|
||||
if (nextPathKey !== pathKey) {
|
||||
// Toolpath arrays are immutable between PROGRAM_LOAD/RESTART. Rebuild
|
||||
// geometry only when WASM publishes a new array; scanning thousands of
|
||||
// segments on every animation frame causes visible execution stalls.
|
||||
if (segments !== pathSegments) {
|
||||
pathSegments = segments;
|
||||
segmentById = new Map(segments.flatMap((segment) => Number.isSafeInteger(segment.id) ? [[Number(segment.id), segment] as [number, Segment]] : []));
|
||||
clearLines(pathGroup);
|
||||
clearLines(liveGroup);
|
||||
bounds = fit(segments);
|
||||
// Long programs can contain thousands of linear blocks. Batch their
|
||||
// vertices so preview loading does not block the UI on one WebGL
|
||||
@@ -209,12 +219,18 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
addSegments(batched.rapid, rapid, true);
|
||||
addSegments(batched.completed, completed);
|
||||
for (const sampled of sampledLines) addLine(sampled.points, sampled.rapid ? rapid : completed, sampled.rapid);
|
||||
pathKey = nextPathKey;
|
||||
liveKey = "";
|
||||
liveHistoryLine = null;
|
||||
activeLiveLine = null;
|
||||
lastHistoryTick = -1;
|
||||
lastActiveKey = "";
|
||||
}
|
||||
const presetKey = `${viewRef.current}:${zoomRef.current}:${bounds.radius}:${bounds.center.x}:${bounds.center.y}:${bounds.center.z}`;
|
||||
if (presetKey !== lastPreset) { presetCamera(viewRef.current, zoomRef.current); lastPreset = presetKey; }
|
||||
if (renderedTick < liveTick) liveTrace = [];
|
||||
if (renderedTick < liveTick) {
|
||||
liveTrace = [];
|
||||
lastHistoryTick = -1;
|
||||
if (liveHistoryLine) { liveHistoryLine.geometry.dispose(); liveGroup.remove(liveHistoryLine); liveHistoryLine = null; }
|
||||
}
|
||||
if (renderedTick !== liveTick) {
|
||||
const current = sourcePoint(renderedPose);
|
||||
const previous = liveTrace.at(-1);
|
||||
@@ -222,15 +238,18 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
liveTick = renderedTick;
|
||||
}
|
||||
element.dataset.xyzacLiveTraceCount = String(liveTrace.length);
|
||||
const nextLiveKey = `${renderedTick}:${renderedActiveSegment}:${renderedProgress}:${liveTrace.length}`;
|
||||
if (nextLiveKey !== liveKey) {
|
||||
clearLines(liveGroup);
|
||||
if (liveTrace.length > 1) {
|
||||
const historyLine = new THREE.Line(new THREE.BufferGeometry().setFromPoints(liveTrace), liveHistory);
|
||||
historyLine.renderOrder = 19;
|
||||
liveGroup.add(historyLine);
|
||||
}
|
||||
const activeSegment = segments.find((segment) => Boolean(segment.active || segment.id === renderedActiveSegment));
|
||||
const historyDue = liveTrace.length > 1 && (lastHistoryTick < 0 || renderedTick - lastHistoryTick >= 4);
|
||||
if (historyDue) {
|
||||
if (liveHistoryLine) { liveHistoryLine.geometry.dispose(); liveGroup.remove(liveHistoryLine); }
|
||||
liveHistoryLine = new THREE.Line(new THREE.BufferGeometry().setFromPoints(liveTrace), liveHistory);
|
||||
liveHistoryLine.renderOrder = 19;
|
||||
liveGroup.add(liveHistoryLine);
|
||||
lastHistoryTick = renderedTick;
|
||||
}
|
||||
const nextActiveKey = `${renderedTick}:${renderedActiveSegment}:${renderedProgress}`;
|
||||
if (nextActiveKey !== lastActiveKey) {
|
||||
if (activeLiveLine) { activeLiveLine.geometry.dispose(); liveGroup.remove(activeLiveLine); activeLiveLine = null; }
|
||||
const activeSegment = segmentById.get(renderedActiveSegment) ?? segments.find((segment) => Boolean(segment.active));
|
||||
if (activeSegment) {
|
||||
const start = activeSegment.start ?? {}; const end = activeSegment.end ?? {};
|
||||
const points = Array.isArray(activeSegment.samples) && activeSegment.samples.length > 1 ? activeSegment.samples.map(sourcePoint) : [sourcePoint(start), sourcePoint(end)];
|
||||
@@ -242,13 +261,13 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
const lastPoint = travelled.at(-1);
|
||||
if (!lastPoint || lastPoint.distanceTo(livePoint) > 1e-9) travelled.push(livePoint);
|
||||
if (travelled.length > 1) {
|
||||
const liveLine = new THREE.Line(new THREE.BufferGeometry().setFromPoints(travelled), live);
|
||||
liveLine.renderOrder = 20;
|
||||
liveGroup.add(liveLine);
|
||||
activeLiveLine = new THREE.Line(new THREE.BufferGeometry().setFromPoints(travelled), live);
|
||||
activeLiveLine.renderOrder = 20;
|
||||
liveGroup.add(activeLiveLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
liveKey = nextLiveKey;
|
||||
lastActiveKey = nextActiveKey;
|
||||
}
|
||||
pathGroup.visible = layer.showPath; liveGroup.visible = layer.showLive;
|
||||
grid.scale.setScalar(Math.max(.2, bounds.radius * 2.4));
|
||||
|
||||
Reference in New Issue
Block a user