Fix repeated motion-id preview highlight

This commit is contained in:
2026-09-01 20:24:47 +08:00
parent aa71c17b1e
commit 3dcd2d681f
2 changed files with 22 additions and 6 deletions

View File

@@ -4,7 +4,7 @@ import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { projectXyzacVisual, type XyzacVisualSource, type XyzacVisualState } from "./xyzac-visual-adapter";
type Point = Record<string, number>;
type Segment = { id?: number; active?: boolean; kind?: string; motion_type?: number; kinematics_mode?: number; turn?: number; center?: Point; normal?: Point; samples?: Point[]; start?: Point; end?: Point };
type Segment = { id?: number; source_line?: number | null; active?: boolean; kind?: string; motion_type?: number; kinematics_mode?: number; turn?: number; center?: Point; normal?: Point; samples?: Point[]; start?: Point; end?: Point };
type Props = { visual: XyzacVisualState; view?: "z" | "z2" | "x" | "y" | "y2" | "p"; zoom?: number; rotateMode?: boolean; showPath?: boolean; showLive?: boolean; showLimits?: boolean; showVelocity?: boolean; showOffsets?: boolean; showExtents?: boolean };
const DEG = Math.PI / 180;
@@ -82,13 +82,18 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
};
const completed = new THREE.LineBasicMaterial({ color: 0xd8dfe3, depthTest: false });
const rapid = new THREE.LineDashedMaterial({ color: 0xa85d5d, dashSize: .025, gapSize: .018, depthTest: false });
const live = new THREE.LineBasicMaterial({ color: 0xffd166, depthTest: false });
// LinuxCNC uses yellow for jog backplot. Coordinated program execution is
// red (live plot), while an unambiguous current program block is cyan.
const live = new THREE.LineBasicMaterial({ color: 0x00ffff, depthTest: false });
const liveHistory = new THREE.LineBasicMaterial({ color: 0xff6b4a, depthTest: false });
let frame = 0;
let liveTick = -1;
let liveTrace: THREE.Vector3[] = [];
let pathSegments: Segment[] | null = null;
let segmentById = new Map<number, Segment>();
// Native TP active_id is the source line/motion id and may repeat (for
// example, each call of the same helix subroutine). Preserve every
// occurrence instead of letting a Map overwrite all but one geometry.
let segmentCandidatesBySourceLine = 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;
@@ -203,7 +208,14 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
// 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]] : []));
segmentCandidatesBySourceLine = new Map();
for (const segment of segments) {
const sourceLine = Number(segment.source_line);
if (!Number.isSafeInteger(sourceLine) || sourceLine <= 0) continue;
const candidates = segmentCandidatesBySourceLine.get(sourceLine) ?? [];
candidates.push(segment);
segmentCandidatesBySourceLine.set(sourceLine, candidates);
}
clearLines(pathGroup);
clearLines(liveGroup);
bounds = fit(segments);
@@ -268,7 +280,11 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
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));
const candidates = segmentCandidatesBySourceLine.get(renderedActiveSegment) ?? [];
// A repeated source line has no unique preview geometry. LinuxCNC's
// current-line indicator remains authoritative in the program view;
// do not invent a yellow/cyan path by selecting an arbitrary repeat.
const activeSegment = candidates.length === 1 ? candidates[0] : null;
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)];