Update XYZBC web simulation evidence

This commit is contained in:
wangdequan
2026-07-03 08:49:13 -04:00
parent 6631833c93
commit 33d4b21332
2851 changed files with 141373 additions and 22 deletions

View File

@@ -8,6 +8,8 @@ import { loadLinuxCncIniConfig } from "./runtime/linuxcnc-ini-runtime.js";
import { createLinuxCncTaskHalRuntime } from "./runtime/linuxcnc-task-hal-runtime.js";
import { createLinuxCncTaskHalWorkerRuntime } from "./runtime/linuxcnc-task-hal-worker-client.js";
installTextDecoderResizableArrayBufferCompat();
const app = document.querySelector("#app");
if (!app) {
@@ -77,6 +79,31 @@ window.webRtcp5AxisSimulation = {
store.dispatch({ type: "BOOT_READY" });
function installTextDecoderResizableArrayBufferCompat() {
const decoderPrototype = globalThis.TextDecoder?.prototype;
if (!decoderPrototype || decoderPrototype.__webRtcpResizableArrayBufferCompat) return;
const nativeDecode = decoderPrototype.decode;
Object.defineProperty(decoderPrototype, "__webRtcpResizableArrayBufferCompat", {
value: true,
configurable: false,
});
decoderPrototype.decode = function decodeResizableArrayBufferCompat(input, options) {
try {
return nativeDecode.call(this, input, options);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!message.includes("resizable")) throw error;
if (input?.buffer) {
return nativeDecode.call(this, Uint8Array.from(input), options);
}
if (input instanceof ArrayBuffer) {
return nativeDecode.call(this, Uint8Array.from(new Uint8Array(input)), options);
}
throw error;
}
};
}
async function attachProfileIniConfig(store, profile) {
try {
const iniConfig = await loadLinuxCncIniConfig(profile);

View File

@@ -811,7 +811,8 @@ function resampleAxisPreviewSegments(segments, samplePeriodMs, tool) {
const samples = [];
let timeMs = 0;
let sampleIndex = 0;
for (const segment of segments) {
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex += 1) {
const segment = segments[segmentIndex];
const durationMs = Math.max(samplePeriodMs, Math.ceil(axisPreviewSegmentDurationMs(segment)));
const stepCount = Math.max(1, Math.ceil(durationMs / samplePeriodMs));
for (let step = 0; step < stepCount; step += 1) {
@@ -819,7 +820,10 @@ function resampleAxisPreviewSegments(segments, samplePeriodMs, tool) {
samples.push(pathSampleFromAxisPreviewPose({
sampleIndex,
timeMs,
sourceFile: segment.sourceFile,
line: segment.line,
statement: segment.statement,
segmentIndex,
motionType: segment.motionType,
activeKinematics: segment.activeKinematics,
pose: poseOnAxisPreviewSegment(segment, ratio),
@@ -835,7 +839,10 @@ function resampleAxisPreviewSegments(segments, samplePeriodMs, tool) {
samples.push(pathSampleFromAxisPreviewPose({
sampleIndex,
timeMs,
sourceFile: last.sourceFile,
line: last.line,
statement: last.statement,
segmentIndex: segments.length - 1,
motionType: last.motionType,
activeKinematics: last.activeKinematics,
pose: poseOnAxisPreviewSegment(last, 1),
@@ -876,7 +883,10 @@ function poseOnAxisPreviewSegment(segment, ratio) {
function pathSampleFromAxisPreviewPose({
sampleIndex,
timeMs,
sourceFile,
line,
statement,
segmentIndex,
motionType,
activeKinematics,
pose,
@@ -893,7 +903,10 @@ function pathSampleFromAxisPreviewPose({
return {
sampleIndex,
timeMs,
sourceFile: sourceFile || null,
line: Number(line) || 0,
statement: statement || "",
segmentIndex: Number.isFinite(Number(segmentIndex)) ? Number(segmentIndex) : null,
motionType,
activeKinematics,
tool,

View File

@@ -1,4 +1,7 @@
const DEFAULT_SDK_MODULE_URL = "../../../../wasm-port/runtime/sdk/src/sim-config-staging.js";
const DEFAULT_SDK_MODULE_URLS = [
new URL("../../wasm-port/runtime/sdk/src/sim-config-staging.js", import.meta.url).href,
new URL("../../../../wasm-port/runtime/sdk/src/sim-config-staging.js", import.meta.url).href,
];
const DEFAULT_MANIFEST_URLS = [
new URL("../../../../wasm-port/tools/source-manifest.txt", import.meta.url).href,
new URL("../../wasm-port/tools/source-manifest.txt", import.meta.url).href,
@@ -27,14 +30,16 @@ export async function createMachineFileStagingPlan({
profile,
iniText = null,
manifestText = null,
sdkModuleUrl = DEFAULT_SDK_MODULE_URL,
sdkModuleUrl = null,
manifestUrl = null,
wasmDir = null,
} = {}) {
if (!profile?.iniPath) {
throw new Error("machine file staging requires a profile with iniPath");
}
const { planSimConfigStaging } = await import(sdkModuleUrl);
const { planSimConfigStaging } = await importFirstAvailableModule(
sdkModuleUrl ? [sdkModuleUrl] : DEFAULT_SDK_MODULE_URLS,
);
const resolvedManifestText = manifestText ?? await readTextFromCandidateUrls(
manifestUrl ? [manifestUrl] : DEFAULT_MANIFEST_URLS,
);
@@ -543,6 +548,18 @@ function sourceOverrideUrlsFor(sourceRel) {
return [];
}
async function importFirstAvailableModule(urls) {
const errors = [];
for (const url of urls) {
try {
return await import(url);
} catch (error) {
errors.push(`${url}: ${error instanceof Error ? error.message : String(error)}`);
}
}
throw new Error(`Unable to import machine file staging SDK: ${errors.join(" | ")}`);
}
function basename(path) {
return String(path).split("/").filter(Boolean).at(-1);
}

View File

@@ -463,7 +463,8 @@ button:disabled {
}
.axis-preview-wrap {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) 286px;
min-width: 0;
min-height: 0;
overflow: hidden;
@@ -471,6 +472,13 @@ button:disabled {
background: #000;
}
.axis-preview-stage {
position: relative;
min-width: 0;
min-height: 0;
overflow: hidden;
}
.axis-preview-canvas {
width: 100%;
height: 100%;
@@ -488,6 +496,224 @@ button:disabled {
pointer-events: none;
}
.axis-process-monitor {
display: grid;
grid-template-rows: auto repeat(5, auto);
align-content: start;
gap: 5px;
min-width: 0;
min-height: 0;
overflow: auto;
padding: 6px;
border-left: 2px solid #777;
background: #111111;
color: #eeeeee;
font: 12px/1.22 Arial, Helvetica, sans-serif;
}
.axis-process-monitor header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 5px;
min-width: 0;
padding-bottom: 4px;
border-bottom: 1px solid #444;
}
.axis-gcode-live {
display: grid;
gap: 3px;
min-width: 0;
padding: 5px 8px;
border-bottom: 2px solid #9e9e9e;
background: #111111;
color: #f2f2f2;
font: 13px/1.25 "Courier New", monospace;
}
.axis-gcode-live header,
.axis-gcode-live-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
min-width: 0;
}
.axis-gcode-live span,
.axis-gcode-live b,
.axis-gcode-live code {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.axis-gcode-live b {
color: #1ffff4;
}
.axis-gcode-live-meta {
color: #c8c8c8;
font-size: 12px;
}
.axis-gcode-process {
display: grid;
gap: 4px;
min-width: 0;
margin-top: 2px;
padding-top: 4px;
border-top: 1px solid #3d3d3d;
}
.axis-gcode-process header,
.axis-gcode-flow {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
overflow: hidden;
color: #c8c8c8;
font-size: 12px;
white-space: nowrap;
}
.axis-gcode-flow span {
min-width: 0;
overflow: hidden;
padding: 1px 4px;
border: 1px solid #3f5d66;
background: #0f2328;
color: #d9fbff;
text-overflow: ellipsis;
}
.axis-gcode-flow b {
flex: 0 0 auto;
color: #8a8a8a;
}
.axis-gcode-result-grid {
display: grid;
grid-template-columns: 42px minmax(0, 1fr) 42px minmax(0, 1fr);
gap: 3px 7px;
min-width: 0;
color: #a7a7a7;
font-size: 12px;
}
.axis-gcode-result-grid b {
color: #ffffff;
font-weight: 400;
}
.axis-gcode-step-list {
display: grid;
gap: 1px;
min-width: 0;
max-height: 86px;
overflow: hidden;
margin: 0;
padding: 0;
list-style: none;
font-size: 12px;
}
.axis-gcode-step-list li {
display: grid;
grid-template-columns: 28px minmax(0, 1fr) minmax(82px, 0.26fr);
gap: 5px;
min-width: 0;
padding: 1px 3px;
border-left: 2px solid transparent;
}
.axis-gcode-step-list li.active {
border-left-color: #1ffff4;
background: #172d32;
}
.axis-gcode-step-list span {
color: #888888;
text-align: right;
}
.axis-gcode-step-list code,
.axis-gcode-step-list b {
min-width: 0;
overflow: hidden;
font-weight: 400;
text-overflow: ellipsis;
white-space: nowrap;
}
.axis-gcode-step-list b {
color: #f4d35e;
}
.axis-process-monitor header strong,
.axis-process-monitor header span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.axis-process-monitor header span {
color: #f4d35e;
text-transform: uppercase;
}
.axis-monitor-group {
display: grid;
gap: 2px;
min-width: 0;
padding: 5px;
border: 1px solid #4a4a4a;
background: #191919;
}
.axis-monitor-group h3 {
margin: 0 0 2px;
color: #9fd3ff;
font: 700 12px/1 Arial, Helvetica, sans-serif;
}
.axis-monitor-row {
display: grid;
grid-template-columns: 64px minmax(0, 1fr);
gap: 5px;
min-width: 0;
}
.axis-monitor-row span {
color: #a7a7a7;
}
.axis-monitor-row b {
min-width: 0;
overflow: hidden;
color: #ffffff;
font: 700 12px/1.2 "Courier New", monospace;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
.axis-monitor-code {
min-width: 0;
overflow: hidden;
padding: 3px;
border: 1px inset #555;
background: #050505;
color: #e6e6e6;
font: 11px/1.18 "Courier New", monospace;
text-overflow: ellipsis;
white-space: nowrap;
}
.axis-pyvcp-pane {
grid-area: pyvcp;
min-width: 0;
@@ -625,6 +851,14 @@ button:disabled {
.axis-program-list {
font-size: 22px;
}
.axis-preview-wrap {
grid-template-columns: minmax(0, 1fr) 250px;
}
.axis-process-monitor {
font-size: 11px;
}
}
@media (max-height: 760px) {
@@ -658,4 +892,13 @@ button:disabled {
.axis-override-box {
grid-template-rows: repeat(6, 26px) 24px 54px;
}
.axis-process-monitor {
gap: 3px;
padding: 4px;
}
.axis-monitor-group {
padding: 4px;
}
}

View File

@@ -322,8 +322,11 @@ function renderMainTabs(element, state, dispatch) {
<button type="button">centering</button>
</div>
<div class="axis-preview-wrap">
<div class="axis-preview-readout" data-preview-readout></div>
<canvas class="axis-preview-canvas" data-five-axis-canvas="true" aria-label="AXIS XYZBC TRT preview"></canvas>
<div class="axis-preview-stage">
<div class="axis-preview-readout" data-preview-readout></div>
<canvas class="axis-preview-canvas" data-five-axis-canvas="true" aria-label="AXIS XYZBC TRT preview"></canvas>
</div>
<aside class="axis-process-monitor" data-linuxcnc-process-monitor></aside>
</div>
`;
element.dataset.mounted = "true";
@@ -334,6 +337,7 @@ function renderMainTabs(element, state, dispatch) {
readout.dataset.kinsType = state.kinsType;
renderFiveAxisScene(element.querySelector("[data-five-axis-canvas]"), state);
renderLinuxCncProcessMonitor(element.querySelector("[data-linuxcnc-process-monitor]"), state);
}
function renderPyvcp(element, state, dispatch) {
@@ -360,10 +364,37 @@ function renderPyvcp(element, state, dispatch) {
function renderProgram(element, state) {
const currentLine = currentGcodeExecutionLine(state);
const live = state.programUiExecution || {};
const process = state.programAxisPreviewPath?.gcodeExecutionProcess || {};
const gcodeStep = live.gcodeStepIndex === null || live.gcodeStepIndex === undefined
? null
: process.executionSteps?.[Number(live.gcodeStepIndex)] || null;
element.dataset.liveSourceFile = live.sourceFile || "";
element.dataset.liveSourceLine = String(live.line || currentLine || 0);
element.dataset.liveSampleIndex = String(live.sampleIndex || 0);
element.dataset.liveExecutedSamples = String(live.executedSampleCount || 0);
element.dataset.gcodeExecutionStatus = process.status || "";
element.dataset.gcodeExecutionStepCount = String(process.executionStepCount || 0);
element.dataset.gcodeMotionStepCount = String(process.summary?.motionStepCount || 0);
element.dataset.gcodeParameterStepCount = String(process.summary?.parameterAssignmentStepCount || 0);
element.dataset.gcodeCallDepth = String(gcodeStep?.callDepth || 0);
const lines = state.programLines?.length
? state.programLines.slice(0, 80)
: [";"];
element.innerHTML = `
<section class="axis-gcode-live" data-gcode-live>
<header>
<span>${escapeHtml(live.sourceFile || state.activeProgram || "-")}:${formatNumber(live.line || currentLine, 0)}</span>
<b>${escapeHtml(live.operation || live.motionType || state.runState || "-")}</b>
</header>
<div class="axis-gcode-live-meta">
<span>sample ${formatNumber((live.sampleIndex || 0) + 1, 0)} / ${formatNumber(live.sampleCount || 0, 0)}</span>
<span>step ${live.gcodeStepIndex === null || live.gcodeStepIndex === undefined ? "-" : formatNumber(live.gcodeStepIndex, 0)}</span>
<span>${escapeHtml(live.activeKinematics || state.kinsType || "-")}</span>
</div>
<code>${escapeHtml(live.statement || "-")}</code>
${renderGcodeExecutionProcess(live, gcodeStep, process)}
</section>
<ol class="axis-program-list">
${lines.map((line, index) => {
const lineNo = state.programStartLine + index;
@@ -376,16 +407,147 @@ function renderProgram(element, state) {
if (activeRow) activeRow.scrollIntoView({ block: "center" });
}
function renderGcodeExecutionProcess(live, gcodeStep, process) {
const summary = process?.summary || {};
const motion = gcodeStep?.result?.motion || null;
const machine = live.machineState || gcodeStep?.result?.machineStateAfter || null;
const callStack = Array.isArray(gcodeStep?.callStack) ? gcodeStep.callStack : [];
const recentSteps = recentExecutedGcodeSteps(process, live.gcodeStepIndex);
return `
<section class="axis-gcode-process" data-gcode-process>
<header>
<span>expanded ${formatNumber(process?.executionStepCount || 0, 0)} steps</span>
<span>${formatNumber(summary.motionStepCount || 0, 0)} motion / ${formatNumber(summary.parameterAssignmentStepCount || 0, 0)} params</span>
</header>
<div class="axis-gcode-flow" data-gcode-call-stack>
${callStack.length > 0
? callStack.map((entry) => `
<span>${escapeHtml(entry.sourceFile)}:${formatNumber(entry.line, 0)} ${escapeHtml(entry.call)}</span>
`).join("<b>></b>")
: "<span>xyzbc_switchkins.ngc:2 o&lt;xyzbc_switchkins_sub&gt;</span>"}
</div>
<div class="axis-gcode-result-grid">
<span>Joint</span><b>${formatJointPose(live.joint || motion?.endJoint)}</b>
<span>TCP</span><b>${formatTcpPose(live.tcp || motion?.endTcp)}</b>
<span>Tool</span><b>${formatToolAxis(live.toolAxis || motion?.endToolAxis)}</b>
<span>Feed</span><b>${formatNumber(machine?.feed?.actualMmPerMin || motion?.feed || 0, 1)} mm/min ${machine?.cutting?.active ? "cutting" : "rapid"}</b>
</div>
<ol class="axis-gcode-step-list" data-gcode-step-list>
${recentSteps.map((step) => `
<li class="${Number(step.stepIndex) === Number(live.gcodeStepIndex) ? "active" : ""}" data-gcode-step="${formatNumber(step.stepIndex, 0)}">
<span>${formatNumber(step.stepIndex, 0)}</span>
<code>${escapeHtml(step.sourceFile)}:${formatNumber(step.line, 0)} ${escapeHtml(step.statement || step.result?.operation || "")}</code>
<b>${escapeHtml(step.result?.operation || step.sourceLineKind || "")}</b>
</li>
`).join("")}
</ol>
</section>
`;
}
function recentExecutedGcodeSteps(process = {}, currentStepIndex = null) {
const steps = Array.isArray(process.executionSteps) ? process.executionSteps : [];
if (steps.length === 0) return [];
const current = Number.isFinite(Number(currentStepIndex))
? Number(currentStepIndex)
: Math.max(0, steps.findIndex((step) => step.executed !== false));
const start = Math.max(0, current - 2);
return steps.slice(start, start + 5);
}
function formatJointPose(pose = {}) {
if (!pose) return "-";
return `X${formatNumber(pose.x, 3)} Y${formatNumber(pose.y, 3)} Z${formatNumber(pose.z, 3)} B${formatNumber(pose.b, 3)} C${formatNumber(pose.c, 3)}`;
}
function formatTcpPose(pose = {}) {
if (!pose) return "-";
return `X${formatNumber(pose.x, 3)} Y${formatNumber(pose.y, 3)} Z${formatNumber(pose.z, 3)}`;
}
function formatToolAxis(axis = {}) {
if (!axis) return "-";
return `I${formatNumber(axis.i ?? axis.x, 3)} J${formatNumber(axis.j ?? axis.y, 3)} K${formatNumber(axis.k ?? axis.z, 3)}`;
}
function renderStatusbar(element, state) {
const toolLabel = formatToolStatus(state);
const monitor = state.linuxCncProcessMonitor || {};
element.innerHTML = `
<div>${state.machine.powerOn ? "ON" : state.machine.estopActive ? "ESTOP" : "OFF"}</div>
<div>${escapeHtml(toolLabel)}</div>
<div>Position: Joint</div>
<div>F ${formatNumber(monitor.feed?.currentVelocityMmPerMin || 0, 1)} S ${formatNumber(monitor.spindle?.actualRpm || 0, 0)} ${monitor.coolant?.flood ? "FLOOD" : monitor.coolant?.mist ? "MIST" : "M9"}</div>
`;
element.dataset.toolStatus = toolLabel;
}
function renderLinuxCncProcessMonitor(element, state) {
if (!element) return;
const monitor = state.linuxCncProcessMonitor || {};
const axes = monitor.axes || {};
const path = monitor.path || {};
const feed = monitor.feed || {};
const spindle = monitor.spindle || {};
const coolant = monitor.coolant || {};
const tool = monitor.toolChange || {};
const runtime = monitor.runtime || {};
element.dataset.sourceMode = path.executionSource || state.programExecutionSourceMode || "";
element.dataset.taskHalReady = String(runtime.taskHalReady === true);
element.dataset.activeLine = String(path.activeLine || state.activeLine || 0);
element.dataset.liveSourceFile = path.uiExecution?.sourceFile || "";
element.dataset.liveSourceLine = String(path.uiExecution?.line || path.activeLine || 0);
element.dataset.liveSampleIndex = String(path.uiExecution?.sampleIndex || 0);
element.innerHTML = `
<header>
<strong>LinuxCNC Task / Motion / HAL</strong>
<span>${escapeHtml(monitor.control?.runState || state.runState)}</span>
</header>
<section class="axis-monitor-group" data-monitor-group="position">
<h3>Position</h3>
${monitorAxisRows(axes.joint || state.axisPose || {})}
<div class="axis-monitor-row"><span>TCP</span><b>${formatNumber(axes.tcp?.x, 3)} ${formatNumber(axes.tcp?.y, 3)} ${formatNumber(axes.tcp?.z, 3)}</b></div>
<div class="axis-monitor-row"><span>DTG</span><b>${formatNumber(axes.distanceToGo?.x, 3)} ${formatNumber(axes.distanceToGo?.y, 3)} ${formatNumber(axes.distanceToGo?.z, 3)}</b></div>
</section>
<section class="axis-monitor-group" data-monitor-group="path">
<h3>Tool Path</h3>
<div class="axis-monitor-row"><span>Line</span><b>${formatNumber(path.activeLine, 0)} / ${formatNumber(state.lineCount || state.programLines?.length || 0, 0)}</b></div>
<div class="axis-monitor-row"><span>Source</span><b>${escapeHtml(path.uiExecution?.sourceFile || "-")}:${formatNumber(path.uiExecution?.line || 0, 0)}</b></div>
<div class="axis-monitor-row"><span>Motion</span><b>${formatNumber(path.motionIndex + 1, 0)} / ${formatNumber(path.motionCount, 0)}</b></div>
<div class="axis-monitor-row"><span>Sample</span><b>${formatNumber(path.sampleIndex + 1, 0)} / ${formatNumber(path.sampleCount, 0)}</b></div>
<div class="axis-monitor-code">${escapeHtml(path.activeGcode || "-")}</div>
</section>
<section class="axis-monitor-group" data-monitor-group="cutting">
<h3>Cutting</h3>
<div class="axis-monitor-row"><span>Feed</span><b>${formatNumber(feed.currentVelocityMmPerMin, 1)} mm/min</b></div>
<div class="axis-monitor-row"><span>Cut</span><b>${formatNumber(feed.cuttingVelocityMmPerMin, 1)} mm/min</b></div>
<div class="axis-monitor-row"><span>F cmd</span><b>${formatNumber(feed.feedRate, 1)} ${formatNumber(feed.feedOverridePercent, 0)}%</b></div>
<div class="axis-monitor-row"><span>Spindle</span><b>${spindle.enabled ? escapeHtml(spindle.direction) : "stop"} ${formatNumber(spindle.actualRpm, 0)} rpm</b></div>
<div class="axis-monitor-row"><span>Coolant</span><b>${coolant.flood ? "flood" : coolant.mist ? "mist" : "off"}</b></div>
</section>
<section class="axis-monitor-group" data-monitor-group="tool">
<h3>Tool Change</h3>
<div class="axis-monitor-row"><span>Tool</span><b>T${formatNumber(tool.activeToolNumber || tool.toolInSpindle, 0)} P${formatNumber(tool.activePocket || tool.toolFromPocket, 0)}</b></div>
<div class="axis-monitor-row"><span>Offset</span><b>Z ${formatNumber(tool.lengthOffsetZ, 3)} D ${formatNumber(tool.diameter, 3)}</b></div>
<div class="axis-monitor-row"><span>IO</span><b>${escapeHtml(tool.emcioStatus || "UNKNOWN")}</b></div>
</section>
<section class="axis-monitor-group" data-monitor-group="runtime">
<h3>Runtime</h3>
<div class="axis-monitor-row"><span>Task/HAL</span><b>${runtime.taskHalReady ? "ready" : "blocked"}</b></div>
<div class="axis-monitor-row"><span>Loop</span><b>${runtime.taskHalLoopActive ? "active" : "idle"} #${formatNumber(runtime.taskHalTickCount, 0)}</b></div>
<div class="axis-monitor-row"><span>Queue</span><b>${formatNumber(path.queueDepth, 0)} / ${formatNumber(path.activeDepth, 0)}</b></div>
</section>
`;
}
function monitorAxisRows(axisPose) {
return ["x", "y", "z", "a", "b", "c"].map((axis) => `
<div class="axis-monitor-row">
<span>${axis.toUpperCase()}</span>
<b>${formatNumber(axisPose?.[axis], axis === "x" || axis === "y" || axis === "z" ? 3 : 4)}</b>
</div>
`).join("");
}
function toolButton(buttonId, action, title, active = false) {
const icon = getGmoccapyIcon(buttonId, active ? "active" : "inactive");
const iconMarkup = icon.path

View File

@@ -578,10 +578,11 @@ function updateMachineReferenceModel(preview, state, toolPosition, axisReference
if (preview.axisReference) {
preview.axisReference.root.visible = axisReferenceMode;
if (axisReferenceMode) {
const referenceToolPosition = new THREE.Vector3(0, 0, 0.006);
const referenceToolPosition = toolPosition || toPreviewVector(state.tcpPose || state.axisPose, state);
const toolVector = toToolVector(state.toolAxisVector);
preview.axisReference.tool.position.copy(referenceToolPosition);
alignToolGlyphToAxis(preview.axisReference.tool, toolVector);
preview.currentToolhead.copy(referenceToolPosition);
preview.currentToolGlyphAxis.copy(toolVector);
}
}