abort plot

This commit is contained in:
Martin Fuchs
2026-09-05 12:59:28 +02:00
parent b802fd2a10
commit 82daebcdd0
4 changed files with 131 additions and 25 deletions
+121 -19
View File
@@ -18,7 +18,7 @@ class WallPlotter {
// Static constants
// ---------------------------------------------------------------------------
static VERSION = '2.3.13';
static VERSION = '2.3.15';
static MIN_FEED_MM_MIN = 1;
static MAX_FEED_MM_MIN = 10000;
/** Offline overlay size when not connected (mm) — 150×150 cm centred on origin */
@@ -133,14 +133,19 @@ class WallPlotter {
this._connectBtn = null;
this._plotBtn = null;
this._testBtn = null;
this._abortBtn = null;
this._connectLabel = 'Connect';
this._plotLabel = 'Plot';
this._pauseLabel = 'Pause';
this._resumeLabel = 'Resume';
this._testLabel = 'Test';
this._stopLabel = 'Stop';
this._abortLabel = 'Abort';
this._lastTestBtnClick = 0;
this._lastPlotBtnClick = 0;
this._pathStartX = 0;
this._pathStartY = 0;
this._wantPathStartReset = false;
// --- Callbacks ---
const userOnLog = options.onLog ?? (() => {});
@@ -222,7 +227,8 @@ class WallPlotter {
/**
* Add Connect / Plot / Test buttons. Expects global sketch functions plot() and test().
* Connect is shown only while disconnected. Plot becomes Pause while running, Resume while paused.
* @param {{parentId?: string, connectLabel?: string, plotLabel?: string, pauseLabel?: string, resumeLabel?: string, testLabel?: string, stopLabel?: string}} [options]
* Abort stops and resets the Test preview to the start.
* @param {{parentId?: string, connectLabel?: string, plotLabel?: string, pauseLabel?: string, resumeLabel?: string, testLabel?: string, stopLabel?: string, abortLabel?: string}} [options]
*/
enablePlotButtons(options = {}) {
if (this._plotButtonsEnabled) return;
@@ -244,6 +250,7 @@ class WallPlotter {
this._resumeLabel = options.resumeLabel ?? 'Resume';
this._testLabel = options.testLabel ?? 'Test';
this._stopLabel = options.stopLabel ?? 'Stop';
this._abortLabel = options.abortLabel ?? 'Abort';
const plotter = this;
const connectBtn = createButton(this._connectLabel);
@@ -260,6 +267,11 @@ class WallPlotter {
testBtn.parent(parent);
testBtn.mousePressed(() => plotter._onTestButton());
this._testBtn = testBtn;
const abortBtn = createButton(this._abortLabel);
abortBtn.parent(parent);
abortBtn.mousePressed(() => plotter.abortToStart());
this._abortBtn = abortBtn;
this._updatePlotButtons();
}
@@ -382,6 +394,34 @@ class WallPlotter {
this.onLog('plot resumed');
}
/**
* Stop the current Test/Plot and reset the preview to the start.
* Test pose returns to where the path began. Live Plot only stops sending
* (the machine stays where it is).
*/
abortToStart() {
this._clearResumeState();
this._wantPathStartReset = true;
this._pathRunId += 1;
if (this._job === 'running' || this._job === 'paused') this._abortJob();
this._applyPathStartReset();
this.onLog('drawing cleared');
}
_applyPathStartReset() {
this.x = this._pathStartX;
this.y = this._pathStartY;
this.isPenDown = false;
this.plotProgress = 0;
this.previewPath = [];
this._path = [];
this._wantPathStartReset = false;
this._plotTestOnly = false;
this._setJob('idle');
this._redrawSketch();
this._updatePlotButtons();
}
/**
* First press: pause after the current stroke (safe to Resume).
* Second press: stop sending more moves. The in-flight G1 still finishes
@@ -1008,6 +1048,10 @@ class WallPlotter {
this._pathLoopActive = true;
this._plotTestOnly = testOnly;
this.previewPath = commands;
if (fromIndex === 0 && !timing) {
this._pathStartX = this.x;
this._pathStartY = this.y;
}
const startAt = Math.max(0, Math.min(fromIndex, commands.length));
this.plotProgress = startAt;
if (timing) {
@@ -1117,9 +1161,13 @@ class WallPlotter {
this.isPenDown = false;
}
this._plotTestOnly = false;
this._setJob('idle');
this._pathLoopActive = false;
if (this.plotProgress >= commands.length) this._clearResumeState();
if (this._wantPathStartReset || this.previewPath.length === 0) {
this._applyPathStartReset();
} else {
this._setJob('idle');
if (this.plotProgress >= commands.length) this._clearResumeState();
}
}
}
@@ -1516,6 +1564,10 @@ class WallPlotter {
await this.stopPlot();
return false;
}
if (k === 'a') {
this.abortToStart();
return false;
}
if (k === 't') {
this._onTestButton();
return false;
@@ -1690,6 +1742,11 @@ class WallPlotter {
this._job !== 'idle' && this._plotTestOnly ? this._stopLabel : this._testLabel
);
}
if (this._abortBtn) {
const active =
this._job !== 'idle' || !!this._interrupted || this.previewPath.length > 0;
this._abortBtn.elt.disabled = !active;
}
}
_triggerSketchFn(name) {
@@ -1750,22 +1807,47 @@ class WallPlotter {
bottom: 0.75rem;
z-index: 20;
max-width: min(52rem, calc(100vw - 1.5rem));
max-height: 42vh;
pointer-events: auto;
background: #141418;
border: none;
border-radius: 6px;
margin: 0;
overflow: hidden;
}
#wallplotter-log-toggle {
display: block;
width: 100%;
background: #141418;
border: none;
color: #aaa;
cursor: pointer;
font-family: system-ui, sans-serif;
font-size: 0.75rem;
letter-spacing: 0.04em;
text-align: left;
padding: 0.4rem 0.65rem;
}
#wallplotter-log-toggle:hover {
color: #eee;
}
#wallplotter-log-body {
max-height: 38vh;
overflow: auto;
user-select: text;
-webkit-user-select: text;
pointer-events: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
line-height: 1.4;
color: #c8c8d0;
background: rgba(18, 18, 22, 0.88);
border: 1px solid #3a3a42;
border-radius: 6px;
padding: 0.45rem 0.6rem;
background: #141418;
border: none;
margin: 0;
padding: 0 0.65rem 0.5rem;
white-space: pre-wrap;
word-break: break-word;
margin: 0;
}
#wallplotter-log.collapsed #wallplotter-log-body {
display: none;
}
`;
document.head.appendChild(style);
@@ -2003,6 +2085,7 @@ class WallPlotter {
'0 go to origin (0, 0)',
'P toggle pen (test up/down angles)',
'T run test (preview)',
'A abort / reset test to start',
'S stop sending (in-flight stroke still finishes)',
'C connect / disconnect',
];
@@ -2014,23 +2097,42 @@ class WallPlotter {
_ensureLogOverlay() {
this._injectPlotButtonStyles();
let el = document.getElementById('wallplotter-log');
if (el) return el;
el = document.createElement('pre');
el.id = 'wallplotter-log';
el.setAttribute('aria-label', 'Serial log');
document.body.appendChild(el);
return el;
let wrap = document.getElementById('wallplotter-log');
let body = document.getElementById('wallplotter-log-body');
if (wrap && body) return body;
if (wrap) wrap.remove();
wrap = document.createElement('div');
wrap.id = 'wallplotter-log';
wrap.setAttribute('aria-label', 'Serial log');
const toggle = document.createElement('button');
toggle.id = 'wallplotter-log-toggle';
toggle.type = 'button';
toggle.textContent = 'Serial';
toggle.addEventListener('click', () => {
wrap.classList.toggle('collapsed');
toggle.textContent = wrap.classList.contains('collapsed') ? 'Serial ▸' : 'Serial ▾';
});
body = document.createElement('pre');
body.id = 'wallplotter-log-body';
wrap.appendChild(toggle);
wrap.appendChild(body);
document.body.appendChild(wrap);
toggle.textContent = 'Serial ▾';
return body;
}
_syncLogOverlay() {
const el = this._ensureLogOverlay();
const wrap = document.getElementById('wallplotter-log');
const text = this.logLines.join('\n');
if (el.textContent !== text) {
el.textContent = text;
el.scrollTop = el.scrollHeight;
}
el.style.display = this.logLines.length ? 'block' : 'none';
if (wrap) wrap.style.display = this.logLines.length ? 'block' : 'none';
}
// ===========================================================================