reconnect and resume
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.3.0"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.3.4"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.3.0"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.3.4"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+332
-33
@@ -18,7 +18,7 @@ class WallPlotter {
|
||||
// Static constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static VERSION = '2.3.0';
|
||||
static VERSION = '2.3.4';
|
||||
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 */
|
||||
@@ -29,6 +29,9 @@ class WallPlotter {
|
||||
static OK_WAIT_LOG_MS = 1000;
|
||||
/** Repeat “still waiting” while blocked on `ok` */
|
||||
static OK_WAIT_PING_MS = 5000;
|
||||
/** If a USB write does not complete, the ACM device is usually gone. */
|
||||
static WRITE_TIMEOUT_MS = 3000;
|
||||
static RESUME_STORAGE_KEY = 'wallplotter.resume.v1';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Construction
|
||||
@@ -99,6 +102,7 @@ class WallPlotter {
|
||||
this._plotTestOnly = false;
|
||||
this._pathRunId = 0;
|
||||
this._pathLoopActive = false;
|
||||
this._interrupted = null;
|
||||
this._plotStartedAt = 0;
|
||||
this._plotEstimateMs = 0;
|
||||
this._pausedAccumMs = 0;
|
||||
@@ -132,6 +136,7 @@ class WallPlotter {
|
||||
this._testLabel = 'Test';
|
||||
this._stopLabel = 'Stop';
|
||||
this._lastTestBtnClick = 0;
|
||||
this._lastPlotBtnClick = 0;
|
||||
|
||||
// --- Callbacks ---
|
||||
const userOnLog = options.onLog ?? (() => {});
|
||||
@@ -150,6 +155,7 @@ class WallPlotter {
|
||||
|
||||
this._unloadBound = false;
|
||||
this._installUnloadHandler();
|
||||
this._loadResumeState();
|
||||
}
|
||||
|
||||
get connected() {
|
||||
@@ -293,6 +299,7 @@ class WallPlotter {
|
||||
|
||||
/** Clear the path buffer. Returns this for chaining. */
|
||||
startPath() {
|
||||
this._clearResumeState();
|
||||
this._path = [];
|
||||
return this;
|
||||
}
|
||||
@@ -351,6 +358,7 @@ class WallPlotter {
|
||||
|
||||
/** Abort the current runPath after the in-flight command's `ok`. */
|
||||
stopPath() {
|
||||
this._clearResumeState();
|
||||
this._abortJob();
|
||||
}
|
||||
|
||||
@@ -417,19 +425,25 @@ class WallPlotter {
|
||||
await this._openPort(port);
|
||||
}
|
||||
this.port = port;
|
||||
this._bindSerialEvents(port);
|
||||
|
||||
this.writer = this.port.writable.getWriter();
|
||||
this._startReadLoop();
|
||||
this._link = 'connected';
|
||||
this.homeSet = false;
|
||||
this._homeWarning = 'Home not set — jog to origin and press H';
|
||||
this.onStatusChange('connected');
|
||||
|
||||
try {
|
||||
await this.send('M17');
|
||||
await this.send('M17', { allowWithoutHome: true });
|
||||
await this.penUp(true);
|
||||
await this.send('M665');
|
||||
await this.send('M114');
|
||||
await this.send('M665', { allowWithoutHome: true });
|
||||
await this._syncPoseFromMachine();
|
||||
if (this._interrupted) {
|
||||
this.onLog(
|
||||
`saved path ${this._interrupted.index}/${this._interrupted.commands.length} — Plot resumes`
|
||||
);
|
||||
this.statusText = 'Connected — Plot to resume';
|
||||
this._updatePlotButtons();
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') return;
|
||||
throw err;
|
||||
@@ -438,12 +452,16 @@ class WallPlotter {
|
||||
|
||||
async _acquirePort() {
|
||||
const existing = await navigator.serial.getPorts();
|
||||
if (existing.length === 1) return existing[0];
|
||||
if (existing.length >= 1) return existing[0];
|
||||
return navigator.serial.requestPort();
|
||||
}
|
||||
|
||||
async _openPort(port) {
|
||||
const opened = port.open({ baudRate: this.baudRate });
|
||||
const opened = port.open({
|
||||
baudRate: this.baudRate,
|
||||
bufferSize: 65536,
|
||||
flowControl: 'none',
|
||||
});
|
||||
const timedOut = this._delay(3000).then(() => {
|
||||
const err = new Error(
|
||||
'serial open timeout — port still held after reload. Unplug USB or close other serial tabs.'
|
||||
@@ -453,19 +471,19 @@ class WallPlotter {
|
||||
});
|
||||
try {
|
||||
await Promise.race([opened, timedOut]);
|
||||
if (typeof port.setSignals === 'function') {
|
||||
try {
|
||||
await port.setSignals({ dataTerminalReady: true, requestToSend: false });
|
||||
} catch (_) {
|
||||
/* some stacks ignore control lines */
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
try {
|
||||
await port.close();
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
if (typeof port.forget === 'function') {
|
||||
try {
|
||||
await port.forget();
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -513,8 +531,6 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
this._link = 'disconnected';
|
||||
this.homeSet = false;
|
||||
this._homeWarning = null;
|
||||
this.onStatusChange('disconnected');
|
||||
}
|
||||
|
||||
@@ -531,7 +547,6 @@ class WallPlotter {
|
||||
this.writer = null;
|
||||
this.port = null;
|
||||
this._link = 'disconnected';
|
||||
this.homeSet = false;
|
||||
try {
|
||||
reader?.cancel();
|
||||
} catch (_) {
|
||||
@@ -554,10 +569,114 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
|
||||
_writeLine(command) {
|
||||
if (!this.writer) {
|
||||
return Promise.reject(new Error('Not connected to plotter'));
|
||||
}
|
||||
const bytes = new TextEncoder().encode(command + '\n');
|
||||
return new Promise((resolve, reject) => {
|
||||
let done = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
const err = new Error('serial write timeout — USB port likely gone');
|
||||
err.name = 'TimeoutError';
|
||||
reject(err);
|
||||
}, WallPlotter.WRITE_TIMEOUT_MS);
|
||||
this.writer.write(bytes).then(
|
||||
() => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
clearTimeout(timer);
|
||||
this.onLog('→ ' + command);
|
||||
resolve();
|
||||
},
|
||||
(err) => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_bindSerialEvents(port) {
|
||||
if (this._serialDisconnectBound) {
|
||||
navigator.serial.removeEventListener('disconnect', this._serialDisconnectBound);
|
||||
}
|
||||
this._serialDisconnectBound = (event) => {
|
||||
if (event.target === port || event.target === this.port) {
|
||||
this._onSerialGone('device unplugged or re-enumerated');
|
||||
}
|
||||
};
|
||||
navigator.serial.addEventListener('disconnect', this._serialDisconnectBound);
|
||||
if (port && typeof port.addEventListener === 'function') {
|
||||
port.addEventListener('disconnect', () => {
|
||||
this._onSerialGone('device unplugged or re-enumerated');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onSerialGone(reason) {
|
||||
if (this._link === 'disconnected') return;
|
||||
this._stashInterruptedPath();
|
||||
this.onLog('! USB serial lost — ' + reason);
|
||||
this.onLog(
|
||||
`kept pose X ${this.x.toFixed(1)} Y ${this.y.toFixed(1)} — reconnect will ask Marlin (M114)`
|
||||
);
|
||||
this._abortJob();
|
||||
this._releaseSerialNow();
|
||||
this.statusText = this._interrupted
|
||||
? 'USB disconnected — reconnect, then Plot to resume'
|
||||
: 'USB disconnected — reconnect';
|
||||
this._updatePlotButtons();
|
||||
this._redrawSketch();
|
||||
}
|
||||
|
||||
_stashInterruptedPath() {
|
||||
if (this._job === 'idle' && !this._pathLoopActive) return;
|
||||
const commands = this.previewPath;
|
||||
if (!commands.length) return;
|
||||
if (this.plotProgress >= commands.length) return;
|
||||
this._interrupted = {
|
||||
commands,
|
||||
index: this.plotProgress,
|
||||
testOnly: this._plotTestOnly,
|
||||
startedAt: this._plotStartedAt,
|
||||
pausedAccum: this._pausedAccumMs,
|
||||
estimateMs: this._plotEstimateMs,
|
||||
};
|
||||
this.onLog(
|
||||
`plot paused at ${this.plotProgress}/${commands.length} — Plot resumes this path`
|
||||
);
|
||||
this._persistResumeState();
|
||||
}
|
||||
|
||||
_resumeInterruptedPath() {
|
||||
const hit = this._interrupted;
|
||||
if (!hit) return false;
|
||||
this._interrupted = null;
|
||||
this.onLog(
|
||||
`resuming path at ${hit.index}/${hit.commands.length}`
|
||||
);
|
||||
Promise.resolve(
|
||||
this._executePath(hit.commands, hit.testOnly, hit.index, hit)
|
||||
).catch((err) => {
|
||||
this.statusText = 'Error: ' + err.message;
|
||||
this.onLog('! ' + err.message);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
_installUnloadHandler() {
|
||||
if (this._unloadBound) return;
|
||||
this._unloadBound = true;
|
||||
const release = () => this._releaseSerialNow();
|
||||
const release = () => {
|
||||
this._stashInterruptedPath();
|
||||
this._persistResumeState(true);
|
||||
this._releaseSerialNow();
|
||||
};
|
||||
window.addEventListener('pagehide', release);
|
||||
window.addEventListener('beforeunload', release);
|
||||
window.addEventListener('unload', release);
|
||||
@@ -566,6 +685,99 @@ class WallPlotter {
|
||||
});
|
||||
}
|
||||
|
||||
_resumeSnapshot() {
|
||||
if (this._interrupted) return this._interrupted;
|
||||
if (this._job === 'idle' || this._plotTestOnly) return null;
|
||||
if (!this.previewPath.length || this.plotProgress >= this.previewPath.length) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
commands: this.previewPath,
|
||||
index: this.plotProgress,
|
||||
testOnly: false,
|
||||
startedAt: this._plotStartedAt,
|
||||
pausedAccum: this._pausedAccumMs,
|
||||
estimateMs: this._plotEstimateMs,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
};
|
||||
}
|
||||
|
||||
_persistResumeState(force = false) {
|
||||
const snap = this._resumeSnapshot();
|
||||
if (!snap || snap.testOnly) {
|
||||
if (force) this._clearResumeState();
|
||||
return;
|
||||
}
|
||||
const now = performance.now();
|
||||
if (!force && now - (this._lastResumePersistAt || 0) < 800) return;
|
||||
this._lastResumePersistAt = now;
|
||||
try {
|
||||
sessionStorage.setItem(
|
||||
WallPlotter.RESUME_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
index: snap.index,
|
||||
testOnly: false,
|
||||
startedAt: snap.startedAt,
|
||||
pausedAccum: snap.pausedAccum,
|
||||
estimateMs: snap.estimateMs,
|
||||
x: snap.x ?? this.x,
|
||||
y: snap.y ?? this.y,
|
||||
commands: snap.commands,
|
||||
})
|
||||
);
|
||||
} catch (_) {
|
||||
/* quota / private mode */
|
||||
}
|
||||
}
|
||||
|
||||
_loadResumeState() {
|
||||
let raw = null;
|
||||
try {
|
||||
raw = sessionStorage.getItem(WallPlotter.RESUME_STORAGE_KEY);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
if (!raw) return;
|
||||
try {
|
||||
const snap = JSON.parse(raw);
|
||||
if (!Array.isArray(snap.commands) || !snap.commands.length) return;
|
||||
const index = Math.max(0, Math.min(snap.index ?? 0, snap.commands.length));
|
||||
if (index >= snap.commands.length) {
|
||||
this._clearResumeState();
|
||||
return;
|
||||
}
|
||||
this.previewPath = snap.commands;
|
||||
this.plotProgress = index;
|
||||
this._plotEstimateMs = snap.estimateMs ?? 0;
|
||||
this._plotStartedAt = snap.startedAt ?? 0;
|
||||
this._pausedAccumMs = snap.pausedAccum ?? 0;
|
||||
if (Number.isFinite(snap.x)) this.x = snap.x;
|
||||
if (Number.isFinite(snap.y)) this.y = snap.y;
|
||||
this._interrupted = {
|
||||
commands: snap.commands,
|
||||
index,
|
||||
testOnly: false,
|
||||
startedAt: this._plotStartedAt,
|
||||
pausedAccum: this._pausedAccumMs,
|
||||
estimateMs: this._plotEstimateMs,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
};
|
||||
} catch (_) {
|
||||
this._clearResumeState();
|
||||
}
|
||||
}
|
||||
|
||||
_clearResumeState() {
|
||||
this._interrupted = null;
|
||||
try {
|
||||
sessionStorage.removeItem(WallPlotter.RESUME_STORAGE_KEY);
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write one G-code line; by default wait for Marlin ok.
|
||||
* @param {string} line
|
||||
@@ -602,9 +814,7 @@ class WallPlotter {
|
||||
})
|
||||
: null;
|
||||
try {
|
||||
const written = this.writer
|
||||
.write(new TextEncoder().encode(command + '\n'))
|
||||
.then(() => this.onLog('→ ' + command));
|
||||
const written = this._writeLine(command);
|
||||
if (okPromise) {
|
||||
await Promise.all([
|
||||
written.catch((err) => {
|
||||
@@ -618,6 +828,9 @@ class WallPlotter {
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') throw err;
|
||||
if (/USB|serial write timeout|NetworkError/i.test(err.message)) {
|
||||
this._onSerialGone(err.message);
|
||||
}
|
||||
const timeoutErr = new Error(err.message);
|
||||
timeoutErr.name = 'TimeoutError';
|
||||
this.onLog('! ' + err.message);
|
||||
@@ -712,6 +925,45 @@ class WallPlotter {
|
||||
this._syncStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask Marlin where it thinks it is. USB re-enum often keeps G92;
|
||||
* only the polargraph boot pose (belts at max length, Y ≈ -2600) means unhomed.
|
||||
*/
|
||||
async _syncPoseFromMachine() {
|
||||
const beforeX = this.x;
|
||||
const beforeY = this.y;
|
||||
try {
|
||||
await this.send('M114', { timeoutMs: 3000, allowWithoutHome: true });
|
||||
} catch (_) {
|
||||
/* keep last browser pose */
|
||||
}
|
||||
|
||||
if (this._isUnhomedBootPose(this.x, this.y)) {
|
||||
this.homeSet = false;
|
||||
this._homeWarning = 'Home not set — jog to origin and press H';
|
||||
this.onLog(
|
||||
`Marlin at boot pose X ${this.x.toFixed(1)} Y ${this.y.toFixed(1)} — press H`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.homeSet = true;
|
||||
this._homeWarning = null;
|
||||
this.onLog(
|
||||
`restored Marlin pose X ${this.x.toFixed(1)} Y ${this.y.toFixed(1)} (was ${beforeX.toFixed(1)}, ${beforeY.toFixed(1)})`
|
||||
);
|
||||
this._syncStatus();
|
||||
}
|
||||
|
||||
/** Polargraph default after reset: equal max belts → X≈0, Y≈-2600. */
|
||||
_isUnhomedBootPose(x, y) {
|
||||
const span = (this.machineXMax - this.machineXMin) / 2;
|
||||
const maxBelt = 3500;
|
||||
const expectedY =
|
||||
this.machineYMax - Math.sqrt(Math.max(0, maxBelt * maxBelt - span * span));
|
||||
return Math.abs(x) < 20 && Math.abs(y - expectedY) < 80;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Internals — feed helpers
|
||||
// ===========================================================================
|
||||
@@ -729,7 +981,7 @@ class WallPlotter {
|
||||
// Internals — path execution & timing
|
||||
// ===========================================================================
|
||||
|
||||
async _executePath(commands, testOnly = false) {
|
||||
async _executePath(commands, testOnly = false, fromIndex = 0, timing = null) {
|
||||
if (!testOnly && !this._requireHome()) return;
|
||||
|
||||
const runId = ++this._pathRunId;
|
||||
@@ -740,25 +992,40 @@ class WallPlotter {
|
||||
}
|
||||
if (runId !== this._pathRunId) return;
|
||||
|
||||
this._interrupted = null;
|
||||
this._pathLoopActive = true;
|
||||
this._plotTestOnly = testOnly;
|
||||
this.previewPath = commands;
|
||||
this.plotProgress = 0;
|
||||
this._plotStartedAt = performance.now();
|
||||
this._plotEstimateMs = this._estimatePathDurationMs(commands, this.x, this.y);
|
||||
this._pausedAccumMs = 0;
|
||||
const startAt = Math.max(0, Math.min(fromIndex, commands.length));
|
||||
this.plotProgress = startAt;
|
||||
if (timing) {
|
||||
this._plotStartedAt = timing.startedAt ?? performance.now();
|
||||
this._pausedAccumMs = timing.pausedAccum ?? 0;
|
||||
this._plotEstimateMs = timing.estimateMs ?? this._estimatePathDurationMs(commands, this.x, this.y);
|
||||
} else {
|
||||
this._plotStartedAt = performance.now();
|
||||
this._plotEstimateMs = this._estimatePathDurationMs(commands, this.x, this.y);
|
||||
this._pausedAccumMs = 0;
|
||||
}
|
||||
this._lastSegmentMs = 0;
|
||||
this._setJob('running');
|
||||
|
||||
const savedFeedTravel = this.feedRateTravel;
|
||||
const savedFeedDraw = this.feedRateDraw;
|
||||
for (let i = 0; i < startAt; i++) {
|
||||
const cmd = commands[i];
|
||||
if (cmd.type === 'speed') {
|
||||
if (cmd.travel != null) this.feedRateTravel = cmd.travel;
|
||||
if (cmd.draw != null) this.feedRateDraw = cmd.draw;
|
||||
}
|
||||
}
|
||||
let px = this.x;
|
||||
let py = this.y;
|
||||
|
||||
const stopped = () => this._job === 'aborting' || runId !== this._pathRunId;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
for (let i = startAt; i < commands.length; i++) {
|
||||
if (stopped()) break;
|
||||
await this._waitIfPaused();
|
||||
if (stopped()) break;
|
||||
@@ -768,6 +1035,7 @@ class WallPlotter {
|
||||
if (cmd.travel != null) this.feedRateTravel = cmd.travel;
|
||||
if (cmd.draw != null) this.feedRateDraw = cmd.draw;
|
||||
this.plotProgress = i + 1;
|
||||
this._persistResumeState();
|
||||
this._redrawSketch();
|
||||
continue;
|
||||
}
|
||||
@@ -805,6 +1073,7 @@ class WallPlotter {
|
||||
await this._waitIfPaused();
|
||||
if (stopped()) break;
|
||||
this.plotProgress = i + 1;
|
||||
this._persistResumeState();
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
@@ -817,6 +1086,7 @@ class WallPlotter {
|
||||
py = cmd.y;
|
||||
}
|
||||
this.plotProgress = i + 1;
|
||||
this._persistResumeState();
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name !== 'AbortError' && err.name !== 'TimeoutError') {
|
||||
@@ -837,6 +1107,7 @@ class WallPlotter {
|
||||
this._plotTestOnly = false;
|
||||
this._setJob('idle');
|
||||
this._pathLoopActive = false;
|
||||
if (this.plotProgress >= commands.length) this._clearResumeState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,13 +1349,18 @@ class WallPlotter {
|
||||
try {
|
||||
while (plotter.readLoopActive) {
|
||||
const { value, done } = await plotter.reader.read();
|
||||
if (done) break;
|
||||
if (done) {
|
||||
if (plotter.readLoopActive) {
|
||||
plotter._onSerialGone('serial stream closed');
|
||||
}
|
||||
break;
|
||||
}
|
||||
plotter._rxBuf += new TextDecoder().decode(value);
|
||||
plotter._drainRxBuffer();
|
||||
}
|
||||
} catch (err) {
|
||||
if (plotter.readLoopActive) {
|
||||
plotter.onLog('serial read error: ' + err.message);
|
||||
plotter._onSerialGone(err.message || 'serial read error');
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
@@ -1316,6 +1592,19 @@ class WallPlotter {
|
||||
return;
|
||||
}
|
||||
if (this._job !== 'idle' || !this.connected) return;
|
||||
const now = typeof millis === 'function' ? millis() : Date.now();
|
||||
if (this._interrupted && !this._interrupted.testOnly) {
|
||||
if (now - this._lastPlotBtnClick < 450) {
|
||||
this._lastPlotBtnClick = now;
|
||||
this._clearResumeState();
|
||||
this._triggerSketchFn('plot');
|
||||
return;
|
||||
}
|
||||
this._lastPlotBtnClick = now;
|
||||
this._resumeInterruptedPath();
|
||||
return;
|
||||
}
|
||||
this._lastPlotBtnClick = now;
|
||||
this._triggerSketchFn('plot');
|
||||
}
|
||||
|
||||
@@ -1332,6 +1621,7 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
_restartPath(mode) {
|
||||
this._clearResumeState();
|
||||
if (this._pathLoopActive) this._abortJob();
|
||||
this._triggerSketchFn(mode);
|
||||
}
|
||||
@@ -1345,6 +1635,7 @@ class WallPlotter {
|
||||
if (this._plotBtn) {
|
||||
let label = this._plotLabel;
|
||||
if (this._job === 'paused') label = this._resumeLabel;
|
||||
else if (this._interrupted && this._job === 'idle') label = this._resumeLabel;
|
||||
else if (this._job === 'running' || this._job === 'aborting') {
|
||||
label = this._pauseLabel;
|
||||
}
|
||||
@@ -1529,7 +1820,8 @@ class WallPlotter {
|
||||
const cmd = this.previewPath[i];
|
||||
if (cmd.type !== 'move' && cmd.type !== 'draw') continue;
|
||||
const p = this._worldToScreen(cmd.x, cmd.y, layout);
|
||||
const isFuture = this.isPlotting && i >= this.plotProgress;
|
||||
const isFuture =
|
||||
(this.isPlotting || !!this._interrupted) && i >= this.plotProgress;
|
||||
|
||||
if (px !== null && py !== null) {
|
||||
if (cmd.type === 'draw') {
|
||||
@@ -1601,10 +1893,17 @@ class WallPlotter {
|
||||
text(`preview ${cm} × ${cm} cm (connect for machine bounds)`, 16, 132);
|
||||
}
|
||||
|
||||
if (this.isPlotting) {
|
||||
if (this.isPlotting || this._interrupted) {
|
||||
fill(180, 220, 255);
|
||||
const label = this._interrupted && this._job === 'idle'
|
||||
? 'interrupted'
|
||||
: this._job === 'paused'
|
||||
? 'paused'
|
||||
: this._job === 'aborting'
|
||||
? 'stopped'
|
||||
: 'plotting';
|
||||
text(
|
||||
`${this._job === 'paused' ? 'paused' : this._job === 'aborting' ? 'stopped' : 'plotting'} ${this.plotProgress}/${this.previewPath.length}`,
|
||||
`${label} ${this.plotProgress}/${this.previewPath.length}`,
|
||||
16,
|
||||
152
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user