按规划持续工作

This commit is contained in:
2026-06-09 06:13:28 +08:00
parent 902c6ecaea
commit 2658ee3b72
255 changed files with 12487 additions and 150 deletions

View File

@@ -67,6 +67,22 @@ const eventCountNode = document.getElementById("event-count");
const wasmBadge = document.getElementById("wasm-badge");
const interpBadge = document.getElementById("interp-badge");
const opfsBadge = document.getElementById("opfs-badge");
const runProgressNode = document.getElementById("run-progress");
const runProgressLabelNode = document.getElementById("run-progress-label");
const runMotionNode = document.getElementById("run-motion");
const runLineNode = document.getElementById("run-line");
const runStatementNode = document.getElementById("run-statement");
const axisNodes = {
x: document.getElementById("axis-x"),
y: document.getElementById("axis-y"),
z: document.getElementById("axis-z"),
a: document.getElementById("axis-a"),
b: document.getElementById("axis-b"),
c: document.getElementById("axis-c"),
u: document.getElementById("axis-u"),
v: document.getElementById("axis-v"),
w: document.getElementById("axis-w"),
};
const fields = {
machine: document.getElementById("field-machine"),
@@ -88,6 +104,7 @@ let iniSdk = null;
let interpSdk = null;
let loadedSession = null;
let canonicalEventText = "";
let runPlaybackTimer = null;
function setBadge(node, text, className = "badge") {
node.className = className;
@@ -119,6 +136,109 @@ function setField(name, value) {
fields[name].textContent = value ?? "-";
}
function nextFrame() {
return new Promise((resolve) => requestAnimationFrame(resolve));
}
function formatAxisValue(value) {
return Number.isFinite(value) ? value.toFixed(3) : "0.000";
}
function clearRunPlayback() {
if (runPlaybackTimer) {
clearInterval(runPlaybackTimer);
runPlaybackTimer = null;
}
}
function setRunMonitor(progress, label, axes = {}, motionText = "idle", lineText = "line -", statement = "-") {
runProgressNode.value = Math.max(0, Math.min(100, progress));
runProgressLabelNode.textContent = label;
runMotionNode.textContent = motionText;
runLineNode.textContent = lineText;
runStatementNode.textContent = statement;
for (const [axis, node] of Object.entries(axisNodes)) {
node.textContent = formatAxisValue(axes[axis] ?? 0);
}
}
function readCanonicalNumber(line, name) {
const match = line.match(new RegExp(`(?:^| )${name}=([-+0-9.eE]+)`));
return match ? Number(match[1]) : null;
}
function programLineMap(programText) {
const lines = new Map();
programText.split(/\r?\n/).forEach((line, index) => {
lines.set(index + 1, line.trim() || "(blank)");
});
return lines;
}
function parseRunMotion(resultText, programText) {
const axes = { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, u: 0, v: 0, w: 0 };
const snapshots = [];
const sourceLines = programLineMap(programText);
for (const line of resultText.split("\n")) {
const motion = line.match(/^canon_event=(STRAIGHT_TRAVERSE|STRAIGHT_FEED|ARC_FEED)\b/);
if (!motion) {
continue;
}
for (const axis of Object.keys(axes)) {
const value = readCanonicalNumber(line, axis);
if (value !== null && Number.isFinite(value)) {
axes[axis] = value;
}
}
const sourceLine = readCanonicalNumber(line, "line");
snapshots.push({
type: motion[1],
line: sourceLine,
statement: Number.isFinite(sourceLine) ? (sourceLines.get(sourceLine) ?? "-") : "-",
axes: { ...axes },
});
}
return snapshots;
}
function showRunSnapshot(snapshot, index, total) {
const progress = total > 0 ? Math.round(((index + 1) / total) * 100) : 0;
const lineText = Number.isFinite(snapshot.line) ? `line ${snapshot.line}` : "line -";
setRunMonitor(
progress,
`${progress}%`,
snapshot.axes,
`${snapshot.type} ${lineText} (${index + 1}/${total})`,
lineText,
snapshot.statement,
);
}
function playRunMotion(resultText, programText) {
clearRunPlayback();
const snapshots = parseRunMotion(resultText, programText);
if (snapshots.length === 0) {
setRunMonitor(100, "100%", {}, "complete; no axis motion");
return;
}
let index = 0;
showRunSnapshot(snapshots[index], index, snapshots.length);
runPlaybackTimer = setInterval(() => {
index += 1;
if (index >= snapshots.length) {
clearRunPlayback();
showRunSnapshot(snapshots[snapshots.length - 1], snapshots.length - 1, snapshots.length);
return;
}
showRunSnapshot(snapshots[index], index, snapshots.length);
}, 220);
}
function requireIniSdk() {
if (!iniSdk) {
throw new Error("INI WASM module is not ready yet.");
@@ -287,10 +407,15 @@ document.getElementById("run-gcode").addEventListener("click", async () => {
const interp = requireInterpSdk();
const programText = await loadGcodeProgram(GCODE_FILENAME);
interp.writeTextFile(GCODE_WASM_FILE, programText);
clearRunPlayback();
setField("runStatus", "running");
setRunMonitor(5, "running", {}, "running");
await nextFrame();
const result = interp.runFileWithIni(GCODE_WASM_FILE, loadedSession.ini.wasmPath);
setField("sessionGcode", GCODE_WASM_FILE);
setField("runStatus", "ok");
setCanonicalEvents(result.trim());
playRunMotion(result.trim(), programText);
setLog(
[
"Ran G-code through LinuxCNC interpreter WASM.",
@@ -302,6 +427,8 @@ document.getElementById("run-gcode").addEventListener("click", async () => {
} catch (error) {
setField("runStatus", "failed");
setCanonicalEvents("");
clearRunPlayback();
setRunMonitor(0, "failed", {}, "failed");
setLog(`G-code run failed: ${error.message}`, true);
}
});

View File

@@ -123,6 +123,53 @@
font: 13px/1.5 "SFMono-Regular", "Consolas", monospace;
white-space: pre-wrap;
}
.run-monitor {
border: 1px solid var(--border);
border-radius: 6px;
background: #fbfdff;
padding: 12px;
display: grid;
gap: 10px;
}
.progress-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 10px;
align-items: center;
}
progress {
width: 100%;
height: 14px;
accent-color: var(--accent-2);
}
.run-readout {
font: 13px/1.4 "SFMono-Regular", "Consolas", monospace;
color: var(--muted);
white-space: nowrap;
}
.axis-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.axis-cell {
border: 1px solid #edf2f6;
border-radius: 6px;
padding: 8px;
display: grid;
gap: 4px;
min-width: 0;
}
.axis-cell span:first-child {
color: var(--muted);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
}
.axis-cell span:last-child {
font: 14px/1.2 "SFMono-Regular", "Consolas", monospace;
overflow-wrap: anywhere;
}
.events {
border: 1px solid var(--border);
border-radius: 6px;
@@ -154,6 +201,11 @@
.grid { grid-template-columns: 1fr; }
textarea { min-height: 320px; }
}
@media (max-width: 560px) {
.axis-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.progress-row { grid-template-columns: 1fr; }
.run-readout { white-space: normal; }
}
</style>
</head>
<body>
@@ -226,6 +278,28 @@
<dd id="field-fiveaxis-remap">-</dd>
</dl>
<h2>Run Monitor</h2>
<div class="run-monitor">
<div class="progress-row">
<progress id="run-progress" max="100" value="0"></progress>
<span id="run-progress-label" class="run-readout">0%</span>
</div>
<div id="run-motion" class="run-readout">idle</div>
<div id="run-line" class="run-readout">line -</div>
<div id="run-statement" class="run-readout">-</div>
<div class="axis-grid" id="axis-grid">
<div class="axis-cell"><span>X</span><span id="axis-x">0.000</span></div>
<div class="axis-cell"><span>Y</span><span id="axis-y">0.000</span></div>
<div class="axis-cell"><span>Z</span><span id="axis-z">0.000</span></div>
<div class="axis-cell"><span>A</span><span id="axis-a">0.000</span></div>
<div class="axis-cell"><span>B</span><span id="axis-b">0.000</span></div>
<div class="axis-cell"><span>C</span><span id="axis-c">0.000</span></div>
<div class="axis-cell"><span>U</span><span id="axis-u">0.000</span></div>
<div class="axis-cell"><span>V</span><span id="axis-v">0.000</span></div>
<div class="axis-cell"><span>W</span><span id="axis-w">0.000</span></div>
</div>
</div>
<div class="log" id="log"></div>
<h2>Canonical Events</h2>