Add OPFS G-code staging and browser simulation checks
This commit is contained in:
@@ -38,12 +38,17 @@ export function renderFiveAxisScene(canvas, state) {
|
||||
exposePreviewDataset(canvas, state, {
|
||||
pointCount,
|
||||
executedPointCount,
|
||||
feedPointCount: preview.feedPath.geometry.getAttribute("position").count,
|
||||
rapidPointCount: preview.rapidPath.geometry.getAttribute("position").count,
|
||||
arcPointCount: preview.arcPath.geometry.getAttribute("position").count,
|
||||
currentSegmentPointCount: preview.currentSegmentPath.geometry.getAttribute("position").count,
|
||||
sceneObjectCount: countSceneObjects(preview.scene),
|
||||
toolhead: preview.currentToolhead,
|
||||
renderer: "webgl",
|
||||
sceneMode: "program-preview-and-tool-execution",
|
||||
cameraControls: preview.controls.enabled,
|
||||
toolExecutionMarker: preview.toolMarker.visible,
|
||||
pathFitBounds: preview.pathFitBoundsReady,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,8 +73,11 @@ function createScene(canvas) {
|
||||
const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 100);
|
||||
|
||||
const previewPath = createLine(0x808892, 0.56);
|
||||
const feedPath = createLine(0x4fb3ff, 0.92);
|
||||
const executedPath = createLine(0x1ffff4, 1);
|
||||
const rapidPath = createLine(0xffb13b, 0.82);
|
||||
const arcPath = createLine(0xd7ff62, 0.95);
|
||||
const currentSegmentPath = createLine(0xff4fd8, 1);
|
||||
const toolMarker = new THREE.Mesh(
|
||||
new THREE.SphereGeometry(0.065, 18, 12),
|
||||
new THREE.MeshBasicMaterial({ color: 0x1ffff4 }),
|
||||
@@ -78,7 +86,7 @@ function createScene(canvas) {
|
||||
EMPTY_GEOMETRY.clone(),
|
||||
new THREE.LineBasicMaterial({ color: 0x1ffff4, transparent: true, opacity: 0.9 }),
|
||||
);
|
||||
scene.add(previewPath, rapidPath, executedPath, toolAxis, toolMarker);
|
||||
scene.add(previewPath, feedPath, rapidPath, arcPath, executedPath, currentSegmentPath, toolAxis, toolMarker);
|
||||
|
||||
const controls = createToolpathCameraControls(canvas, camera, () => {
|
||||
renderer.render(scene, camera);
|
||||
@@ -90,13 +98,18 @@ function createScene(canvas) {
|
||||
camera,
|
||||
controls,
|
||||
previewPath,
|
||||
feedPath,
|
||||
executedPath,
|
||||
rapidPath,
|
||||
arcPath,
|
||||
currentSegmentPath,
|
||||
toolMarker,
|
||||
toolAxis,
|
||||
currentToolhead: new THREE.Vector3(),
|
||||
lastSelectedView: null,
|
||||
lastCameraRevision: null,
|
||||
lastFitKey: null,
|
||||
pathFitBoundsReady: false,
|
||||
};
|
||||
resizeRenderer(preview);
|
||||
resetCamera(preview, "iso");
|
||||
@@ -133,6 +146,10 @@ function renderFallbackPreview(preview, state) {
|
||||
|
||||
const previewPoints = buildProgramPreviewPoints(state);
|
||||
const executedPoints = buildExecutedProgramPoints(state, previewPoints);
|
||||
const rapidPoints = buildRapidPreviewPoints(state);
|
||||
const feedPoints = buildTypedPreviewPoints(state, "STRAIGHT_FEED");
|
||||
const arcPoints = buildTypedPreviewPoints(state, "ARC_FEED");
|
||||
const currentSegmentPoints = buildCurrentSegmentPoints(state);
|
||||
const pointCount = previewPoints.length;
|
||||
const executedPointCount = executedPoints.length;
|
||||
if (pointCount > 0) {
|
||||
@@ -150,8 +167,24 @@ function renderFallbackPreview(preview, state) {
|
||||
drawFallbackPolyline(ctx, executedPoints, cx, cy, scale);
|
||||
ctx.stroke();
|
||||
}
|
||||
if (currentSegmentPoints.length > 0) {
|
||||
ctx.strokeStyle = "#ff4fd8";
|
||||
ctx.lineWidth = 4;
|
||||
ctx.beginPath();
|
||||
drawFallbackPolyline(ctx, currentSegmentPoints, cx, cy, scale);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
const toolPosition = executionToolPosition(state, previewPoints);
|
||||
const fitPoints = collectFitPoints(previewPoints, executedPoints, currentSegmentPoints, toolPosition);
|
||||
const fitKey = [
|
||||
previewPoints.length,
|
||||
executedPoints.length,
|
||||
currentSegmentPoints.length,
|
||||
previewSourceMode(state),
|
||||
state.programExecutionMotionIndex || 0,
|
||||
state.programExecutionSampleIndex || 0,
|
||||
].join(":");
|
||||
if (toolPosition) {
|
||||
const toolX = cx + toolPosition.x * scale;
|
||||
const toolY = cy - toolPosition.y * scale;
|
||||
@@ -174,6 +207,11 @@ function renderFallbackPreview(preview, state) {
|
||||
sceneMode: "program-preview-and-tool-execution",
|
||||
cameraControls: false,
|
||||
toolExecutionMarker: Boolean(toolPosition),
|
||||
feedPointCount: feedPoints.length,
|
||||
rapidPointCount: rapidPoints.length,
|
||||
arcPointCount: arcPoints.length,
|
||||
currentSegmentPointCount: currentSegmentPoints.length,
|
||||
pathFitBounds: computePointBounds(previewPoints.concat(executedPoints, currentSegmentPoints)) !== null,
|
||||
});
|
||||
canvas.dataset.threeFallbackReason = preview.errorMessage;
|
||||
}
|
||||
@@ -195,6 +233,17 @@ function exposePreviewDataset(canvas, state, preview) {
|
||||
canvas.dataset.threeCameraControls = preview.cameraControls ? "orbit-pan-zoom" : "none";
|
||||
canvas.dataset.threeProgramPreviewSource = previewSourceMode(state);
|
||||
canvas.dataset.threeToolExecutionMarker = preview.toolExecutionMarker ? "true" : "false";
|
||||
canvas.dataset.threeToolpathPreviewSource = toolpathPreviewSource(state);
|
||||
canvas.dataset.threeToolExecutionTraceSource = toolExecutionTraceSource(state);
|
||||
canvas.dataset.threePathFitBounds = preview.pathFitBounds ? "ok" : "pending";
|
||||
canvas.dataset.threeCurrentSegmentHighlight = preview.currentSegmentPointCount > 0 ? "ok" : "pending";
|
||||
canvas.dataset.threeRapidFeedVisualDistinction = preview.rapidPointCount > 0 || preview.feedPointCount > 0 || preview.arcPointCount > 0 ? "ok" : "pending";
|
||||
canvas.dataset.threeNoGcodeSemanticsGeneration = "ok";
|
||||
canvas.dataset.threeRapidPathPoints = String(preview.rapidPointCount || 0);
|
||||
canvas.dataset.threeFeedPathPoints = String(preview.feedPointCount || 0);
|
||||
canvas.dataset.threeArcPathPoints = String(preview.arcPointCount || 0);
|
||||
canvas.dataset.threeCurrentSegmentPoints = String(preview.currentSegmentPointCount || 0);
|
||||
canvas.dataset.threeCurrentSegmentType = currentSegmentType(state);
|
||||
}
|
||||
|
||||
function createLine(color, opacity) {
|
||||
@@ -212,21 +261,29 @@ function updateToolpathPreview(preview, state) {
|
||||
const previewPoints = buildProgramPreviewPoints(state);
|
||||
const executedPoints = buildExecutedProgramPoints(state, previewPoints);
|
||||
const rapidPoints = buildRapidPreviewPoints(state);
|
||||
const feedPoints = buildTypedPreviewPoints(state, "STRAIGHT_FEED");
|
||||
const arcPoints = buildTypedPreviewPoints(state, "ARC_FEED");
|
||||
const currentSegmentPoints = buildCurrentSegmentPoints(state);
|
||||
const toolPosition = executionToolPosition(state, previewPoints);
|
||||
|
||||
updateLineGeometry(preview.previewPath, previewPoints);
|
||||
updateLineGeometry(preview.feedPath, feedPoints);
|
||||
updateLineGeometry(preview.executedPath, executedPoints);
|
||||
updateLineGeometry(preview.rapidPath, rapidPoints);
|
||||
updateLineGeometry(preview.arcPath, arcPoints);
|
||||
updateLineGeometry(preview.currentSegmentPath, currentSegmentPoints);
|
||||
updateToolExecutionMarker(preview, state, toolPosition);
|
||||
|
||||
const cameraRevision = state.preview.cameraRevision ?? 0;
|
||||
if (
|
||||
preview.lastSelectedView !== state.preview.selectedView ||
|
||||
preview.lastCameraRevision !== cameraRevision
|
||||
preview.lastCameraRevision !== cameraRevision ||
|
||||
preview.lastFitKey !== fitKey
|
||||
) {
|
||||
resetCamera(preview, state.preview.selectedView);
|
||||
resetCamera(preview, state.preview.selectedView, fitPoints);
|
||||
preview.lastSelectedView = state.preview.selectedView;
|
||||
preview.lastCameraRevision = cameraRevision;
|
||||
preview.lastFitKey = fitKey;
|
||||
} else {
|
||||
applyCameraControls(preview.controls);
|
||||
}
|
||||
@@ -285,15 +342,32 @@ function buildExecutedProgramPoints(state, previewPoints) {
|
||||
}
|
||||
|
||||
function buildRapidPreviewPoints(state) {
|
||||
return buildTypedPreviewPoints(state, "STRAIGHT_TRAVERSE");
|
||||
}
|
||||
|
||||
function buildTypedPreviewPoints(state, type) {
|
||||
const motion = state.programExecution?.motion;
|
||||
if (!Array.isArray(motion) || state.preview.pathPoints === 0) return [];
|
||||
return limitPoints(
|
||||
motion
|
||||
.filter((event) => event.type === "STRAIGHT_TRAVERSE")
|
||||
.filter((event) => event.type === type)
|
||||
.map((event) => vectorFromAxes(event.axes)),
|
||||
);
|
||||
}
|
||||
|
||||
function buildCurrentSegmentPoints(state) {
|
||||
if (state.preview.pathPoints === 0) return [];
|
||||
const motion = state.programExecution?.motion;
|
||||
if (!Array.isArray(motion) || motion.length === 0) return [];
|
||||
const motionIndex = clampMotionIndex(state, currentMotionIndex(state));
|
||||
const current = motion[motionIndex];
|
||||
const previous = motion[Math.max(motionIndex - 1, 0)];
|
||||
if (!current) return [];
|
||||
const start = motionIndex === 0 ? vectorFromAxes(previous?.axes || current.axes) : vectorFromAxes(previous.axes);
|
||||
const end = vectorFromAxes(current.axes);
|
||||
return start.distanceTo(end) > 0 ? [start, end] : [end];
|
||||
}
|
||||
|
||||
function buildFixturePreviewPoints(pointCount, tcpPosition) {
|
||||
const points = [];
|
||||
for (let index = 0; index < pointCount; index += 1) {
|
||||
@@ -367,6 +441,60 @@ function previewSourceMode(state) {
|
||||
return state.programExecutionSourceMode || "fixture-line-playback";
|
||||
}
|
||||
|
||||
function toolpathPreviewSource(state) {
|
||||
if (state.programExecution?.sourceMode === "linuxcnc-interpreter-wasm") {
|
||||
return "linuxcnc_interpreter_canonical_motion";
|
||||
}
|
||||
if (state.programExecution?.sourceMode === "linuxcnc-machine-file-remap-wasm") {
|
||||
return "linuxcnc_machine_file_remap_canonical_motion";
|
||||
}
|
||||
return "fixture_line_playback_not_promoted";
|
||||
}
|
||||
|
||||
function toolExecutionTraceSource(state) {
|
||||
if (Array.isArray(state.programExecutionTiming?.samples) && state.programExecutionTiming.samples.length > 0) {
|
||||
return "linuxcnc_tp_samples_or_task_motion_hal_feedback";
|
||||
}
|
||||
if (state.programRuntimeFeedback?.sourceMode === "linuxcnc-task-motion-hal-wasm") {
|
||||
return "linuxcnc_tp_samples_or_task_motion_hal_feedback";
|
||||
}
|
||||
return "fixture_line_playback_not_promoted";
|
||||
}
|
||||
|
||||
function currentSegmentType(state) {
|
||||
const motion = state.programExecution?.motion;
|
||||
if (!Array.isArray(motion) || motion.length === 0) return "-";
|
||||
return motion[clampMotionIndex(state, currentMotionIndex(state))]?.type || "-";
|
||||
}
|
||||
|
||||
function currentMotionIndex(state) {
|
||||
const sample = state.programExecutionTiming?.samples?.[Number(state.programExecutionSampleIndex || 0)];
|
||||
if (Number.isFinite(Number(sample?.motionIndex))) return Number(sample.motionIndex);
|
||||
return Number(state.programExecutionMotionIndex || 0);
|
||||
}
|
||||
|
||||
function clampMotionIndex(state, index) {
|
||||
const count = state.programExecution?.motion?.length || 0;
|
||||
if (count <= 0) return 0;
|
||||
return clamp(Math.round(Number(index) || 0), 0, count - 1);
|
||||
}
|
||||
|
||||
function collectFitPoints(...groups) {
|
||||
return groups.flatMap((group) => {
|
||||
if (!group) return [];
|
||||
if (Array.isArray(group)) return group.filter(Boolean);
|
||||
return [group];
|
||||
});
|
||||
}
|
||||
|
||||
function computePointBounds(points) {
|
||||
const valid = points.filter((point) => point && Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z));
|
||||
if (valid.length === 0) return null;
|
||||
const box = new THREE.Box3().setFromPoints(valid);
|
||||
if (box.isEmpty()) return null;
|
||||
return box;
|
||||
}
|
||||
|
||||
function drawFallbackPolyline(ctx, points, cx, cy, scale) {
|
||||
for (let index = 0; index < points.length; index += 1) {
|
||||
const point = points[index];
|
||||
@@ -513,15 +641,30 @@ function panCamera(controls, dx, dy, canvas) {
|
||||
controls.target.addScaledVector(up, dy * speed);
|
||||
}
|
||||
|
||||
function resetCamera(preview, selectedView) {
|
||||
function resetCamera(preview, selectedView, fitPoints = []) {
|
||||
const preset = CAMERA_PRESETS[selectedView] || CAMERA_PRESETS.iso;
|
||||
preview.controls.theta = preset.theta;
|
||||
preview.controls.phi = preset.phi;
|
||||
preview.controls.radius = preset.radius;
|
||||
preview.controls.target.copy(preset.target);
|
||||
preview.pathFitBoundsReady = applyFitBounds(preview.controls, selectedView, fitPoints);
|
||||
applyCameraControls(preview.controls);
|
||||
}
|
||||
|
||||
function applyFitBounds(controls, selectedView, fitPoints) {
|
||||
const bounds = computePointBounds(fitPoints);
|
||||
if (!bounds) return false;
|
||||
const center = new THREE.Vector3();
|
||||
const size = new THREE.Vector3();
|
||||
bounds.getCenter(center);
|
||||
bounds.getSize(size);
|
||||
controls.target.copy(center);
|
||||
const maxSpan = Math.max(size.x, size.y, size.z, 0.8);
|
||||
const fitRadius = clamp(maxSpan * 1.8, 2.2, 28);
|
||||
controls.radius = selectedView === "z" ? Math.max(fitRadius, 4.2) : fitRadius;
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyCameraControls(controls) {
|
||||
const sinPhiRadius = Math.sin(controls.phi) * controls.radius;
|
||||
controls.camera.position.set(
|
||||
|
||||
Reference in New Issue
Block a user