plotter drawing size and stop with key
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
|
||||
<script src="../../lib/WallPlotter.js"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=stop-s"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -6,7 +6,12 @@ function setup() {
|
||||
const canvas = createCanvas(windowWidth, windowHeight);
|
||||
canvas.parent('sketch-host');
|
||||
|
||||
wp = new WallPlotter();
|
||||
wp = new WallPlotter({
|
||||
penUpGcode: 'M280 P0 S120',
|
||||
penDownGcode: 'M280 P0 S30',
|
||||
penUpDelayMs: 250,
|
||||
penDownDelayMs: 150,
|
||||
});
|
||||
wp.setSpeed(10000, 10000);
|
||||
wp.setTimingMultiplier(1); // tune if machine is slower than speed estimate, the higher the slower
|
||||
wp.enableKeyInputs();
|
||||
@@ -35,16 +40,16 @@ async function drawArt(testOnly) {
|
||||
|
||||
|
||||
wp.move(-area, -area);
|
||||
wp.wait(1000 * 10);
|
||||
//wp.wait(1000 * 10);
|
||||
|
||||
let step = area / 20;
|
||||
let step = area / 4.001;
|
||||
|
||||
for (let y = -area; y < area; y+=step){
|
||||
for (let x = -area; x < area; x+=step){
|
||||
if(random(1) < 0.8){
|
||||
wp.move(x, y);
|
||||
//if(random(1) < 0.8){
|
||||
wp.draw(x, y);
|
||||
wp.draw(x, y+step);
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -26,8 +26,8 @@ All options are optional.
|
||||
| `penDownGcode` | `M280 P0 S50` | Command to lower pen |
|
||||
| `penUpDelayMs` | `150` | Pause after pen up, ms |
|
||||
| `penDownDelayMs` | `50` | Pause after pen down, ms |
|
||||
| `machineXMin` / `machineXMax` | `-850` / `850` | Overlay X extents, mm |
|
||||
| `machineYMin` / `machineYMax` | `-1000` / `1000` | Overlay Y extents, mm |
|
||||
| `machineXMin` / `machineXMax` | `-830` / `830` | Overlay X extents, mm (idler spacing 1660) |
|
||||
| `machineYMin` / `machineYMax` | `-1200` / `800` | Overlay Y; idlers at Y=800 (80 cm above work centre) |
|
||||
| `jogIntervalMs` | `150` | Repeat delay while holding arrows, ms |
|
||||
| `maxLogLines` | `8` | Serial lines shown on the HUD |
|
||||
| `previewMinDelayMs` | `30` | Floor for preview segment wait, ms |
|
||||
@@ -144,9 +144,9 @@ Sketch: `plot()` → `runPath(false)`, `test()` → `runPath(true)`.
|
||||
|
||||
Returns estimated duration in **milliseconds** for the current path buffer, or for `commands` if given. Uses `setSpeed`, queued `speed()`, `wait()`, and `timingMultiplier`. The HUD shows elapsed / est / remain while plotting.
|
||||
|
||||
### `stopPath()`
|
||||
### `stopPath()` / `stopPlot()`
|
||||
|
||||
Abort the current `runPath`. No arguments.
|
||||
`stopPath()` aborts the current `runPath`. `stopPlot()` also sends Marlin `M410` (quick stop) during a live plot. Key **S** calls `stopPlot()` while plotting.
|
||||
|
||||
---
|
||||
|
||||
@@ -156,7 +156,7 @@ All **async**. Coordinate moves on Plot require home (`H`) first.
|
||||
|
||||
### `connect()`
|
||||
|
||||
Web Serial port picker, enable steppers (`M17`), pen up, `M114`.
|
||||
Web Serial port picker, enable steppers (`M17`), pen up, query polargraph area (`M665` → machine overlay bounds), `M114`.
|
||||
|
||||
### `disconnect()`
|
||||
|
||||
@@ -171,9 +171,9 @@ Write one G-code line and wait for Marlin `ok`.
|
||||
- `options.waitOk` (boolean): wait for `ok`. Default `true`.
|
||||
- `options.timeoutMs` (number): `ok` timeout, default `300000`.
|
||||
|
||||
### `penUp()` / `penDown()`
|
||||
### `penUp(force?)` / `penDown(force?)`
|
||||
|
||||
Send pen G-code and wait the pen delay. No-op if already in that state.
|
||||
Send pen G-code and wait the pen delay. By default, no-op if already in that state. Pass `true` to always send (used by `P` and on connect) so you can test the configured angles.
|
||||
|
||||
### `moveTo(x, y)`
|
||||
|
||||
|
||||
+134
-27
@@ -9,6 +9,8 @@ class WallPlotter {
|
||||
static VERSION = 'wait-ok+M400';
|
||||
static MAX_FEED_MM_MIN = 10000;
|
||||
static MIN_FEED_MM_MIN = 1;
|
||||
/** Offline overlay size (mm) — 150×150 cm centred on origin */
|
||||
static PREVIEW_SIZE_MM = 1500;
|
||||
|
||||
constructor(options = {}) {
|
||||
this.baudRate = options.baudRate ?? 115200;
|
||||
@@ -19,10 +21,11 @@ class WallPlotter {
|
||||
this.penDownGcode = options.penDownGcode ?? 'M280 P0 S50';
|
||||
this.penUpDelayMs = options.penUpDelayMs ?? 150;
|
||||
this.penDownDelayMs = options.penDownDelayMs ?? 50;
|
||||
this.machineXMin = options.machineXMin ?? -850;
|
||||
this.machineXMax = options.machineXMax ?? 850;
|
||||
this.machineYMin = options.machineYMin ?? -1000;
|
||||
this.machineYMax = options.machineYMax ?? 1000;
|
||||
this.machineXMin = options.machineXMin ?? -830;
|
||||
this.machineXMax = options.machineXMax ?? 830;
|
||||
this.machineYMin = options.machineYMin ?? -1200;
|
||||
this.machineYMax = options.machineYMax ?? 800;
|
||||
this.previewSizeMm = options.previewSizeMm ?? WallPlotter.PREVIEW_SIZE_MM;
|
||||
this.jogIntervalMs = options.jogIntervalMs ?? 150;
|
||||
this.maxLogLines = options.maxLogLines ?? 8;
|
||||
|
||||
@@ -143,6 +146,21 @@ class WallPlotter {
|
||||
this._pathAbort = true;
|
||||
}
|
||||
|
||||
/** Abort path; if a live plot is running, also quick-stop Marlin (M410). */
|
||||
async stopPlot() {
|
||||
if (!this.isPlotting) return;
|
||||
this.stopPath();
|
||||
if (this.connected && !this._plotTestOnly) {
|
||||
try {
|
||||
await this.send('M410', { waitOk: false, allowWithoutHome: true });
|
||||
} catch (_) {
|
||||
/* port may be busy */
|
||||
}
|
||||
}
|
||||
this.statusText = 'Stopped';
|
||||
this.onLog('plot stopped');
|
||||
}
|
||||
|
||||
_restartPathAfterAbort(mode) {
|
||||
this._pendingAfterAbort = mode;
|
||||
this._pathAbort = true;
|
||||
@@ -526,7 +544,8 @@ class WallPlotter {
|
||||
this.onStatusChange('connected');
|
||||
|
||||
await this.send('M17');
|
||||
await this.penUp();
|
||||
await this.penUp(true);
|
||||
await this.send('M665'); // polargraph draw area → updates machine bounds
|
||||
await this.send('M114');
|
||||
}
|
||||
|
||||
@@ -632,15 +651,15 @@ class WallPlotter {
|
||||
return false;
|
||||
}
|
||||
|
||||
async penUp() {
|
||||
if (!this.isPenDown) return;
|
||||
async penUp(force = false) {
|
||||
if (!force && !this.isPenDown) return;
|
||||
await this.send(this.penUpGcode);
|
||||
await this._delay(this.penUpDelayMs);
|
||||
this.isPenDown = false;
|
||||
}
|
||||
|
||||
async penDown() {
|
||||
if (this.isPenDown) return;
|
||||
async penDown(force = false) {
|
||||
if (!force && this.isPenDown) return;
|
||||
await this.send(this.penDownGcode);
|
||||
await this._delay(this.penDownDelayMs);
|
||||
this.isPenDown = true;
|
||||
@@ -715,6 +734,11 @@ class WallPlotter {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (k === 's') {
|
||||
if (this.isPlotting) await this.stopPlot();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._keysEnabled || !this.connected) return;
|
||||
|
||||
if (k === 'h') {
|
||||
@@ -728,8 +752,8 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
if (k === 'p') {
|
||||
if (this.isPenDown) await this.penUp();
|
||||
else await this.penDown();
|
||||
if (this.isPenDown) await this.penUp(true);
|
||||
else await this.penDown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -806,18 +830,40 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
|
||||
_getDisplayBounds() {
|
||||
if (this.connected) {
|
||||
return {
|
||||
xMin: this.machineXMin,
|
||||
xMax: this.machineXMax,
|
||||
yMin: this.machineYMin,
|
||||
yMax: this.machineYMax,
|
||||
preview: false,
|
||||
};
|
||||
}
|
||||
const half = this.previewSizeMm / 2;
|
||||
return {
|
||||
xMin: -half,
|
||||
xMax: half,
|
||||
yMin: -half,
|
||||
yMax: half,
|
||||
preview: true,
|
||||
};
|
||||
}
|
||||
|
||||
_getPlotLayout() {
|
||||
const margin = 60;
|
||||
const size = min(width, height) - margin * 2;
|
||||
const worldW = this.machineXMax - this.machineXMin;
|
||||
const worldH = this.machineYMax - this.machineYMin;
|
||||
const b = this._getDisplayBounds();
|
||||
const worldW = Math.max(1, b.xMax - b.xMin);
|
||||
const worldH = Math.max(1, b.yMax - b.yMin);
|
||||
// Fit full display rectangle; centre the view on work origin (0,0)
|
||||
const scale = min(size / worldW, size / worldH);
|
||||
return {
|
||||
cx: width / 2,
|
||||
cy: height / 2 - 20,
|
||||
scale,
|
||||
worldCx: (this.machineXMin + this.machineXMax) / 2,
|
||||
worldCy: (this.machineYMin + this.machineYMax) / 2,
|
||||
worldCx: 0,
|
||||
worldCy: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -856,22 +902,54 @@ class WallPlotter {
|
||||
|
||||
_drawPlotArea() {
|
||||
const layout = this._getPlotLayout();
|
||||
const bounds = this._getDisplayBounds();
|
||||
|
||||
push();
|
||||
translate(layout.cx, layout.cy);
|
||||
|
||||
stroke(50);
|
||||
strokeWeight(1);
|
||||
const tl = this._worldToScreen(this.machineXMin, this.machineYMax, layout);
|
||||
const br = this._worldToScreen(this.machineXMax, this.machineYMin, layout);
|
||||
const tl = this._worldToScreen(bounds.xMin, bounds.yMax, layout);
|
||||
const br = this._worldToScreen(bounds.xMax, bounds.yMin, layout);
|
||||
const w = br.x - tl.x;
|
||||
const h = br.y - tl.y;
|
||||
|
||||
noFill();
|
||||
rectMode(CORNER);
|
||||
rect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
|
||||
strokeWeight(1);
|
||||
if (bounds.preview) {
|
||||
stroke(90, 90, 110);
|
||||
drawingContext.setLineDash([8, 6]);
|
||||
} else {
|
||||
stroke(70);
|
||||
drawingContext.setLineDash([]);
|
||||
}
|
||||
rect(tl.x, tl.y, w, h);
|
||||
drawingContext.setLineDash([]);
|
||||
|
||||
stroke(60);
|
||||
line(tl.x, 0, br.x, 0);
|
||||
line(0, tl.y, 0, br.y);
|
||||
|
||||
// Size labels in cm along bottom and right edges
|
||||
const widthCm = (bounds.xMax - bounds.xMin) / 10;
|
||||
const heightCm = (bounds.yMax - bounds.yMin) / 10;
|
||||
const widthLabel = Number.isInteger(widthCm)
|
||||
? `${widthCm} cm`
|
||||
: `${widthCm.toFixed(1)} cm`;
|
||||
const heightLabel = Number.isInteger(heightCm)
|
||||
? `${heightCm} cm`
|
||||
: `${heightCm.toFixed(1)} cm`;
|
||||
noStroke();
|
||||
fill(bounds.preview ? 140 : 160);
|
||||
textFont('monospace', 12);
|
||||
textAlign(CENTER, TOP);
|
||||
text(widthLabel, (tl.x + br.x) / 2, br.y + 8);
|
||||
push();
|
||||
translate(br.x + 8, (tl.y + br.y) / 2);
|
||||
rotate(HALF_PI);
|
||||
textAlign(CENTER, BOTTOM);
|
||||
text(heightLabel, 0, 0);
|
||||
pop();
|
||||
|
||||
const rectBounds = this._getEffectiveDrawBounds();
|
||||
if (rectBounds) {
|
||||
const dtl = this._worldToScreen(rectBounds.left, rectBounds.top, layout);
|
||||
@@ -955,43 +1033,53 @@ class WallPlotter {
|
||||
92
|
||||
);
|
||||
text(`home ${this.homeSet ? 'set' : 'not set'}`, 16, 112);
|
||||
if (this.connected) {
|
||||
text(
|
||||
`machine X ${this.machineXMin.toFixed(0)}…${this.machineXMax.toFixed(0)} Y ${this.machineYMin.toFixed(0)}…${this.machineYMax.toFixed(0)} mm`,
|
||||
16,
|
||||
132
|
||||
);
|
||||
} else {
|
||||
const cm = this.previewSizeMm / 10;
|
||||
text(`preview ${cm} × ${cm} cm (connect for machine bounds)`, 16, 132);
|
||||
}
|
||||
|
||||
if (this.isPlotting) {
|
||||
fill(180, 220, 255);
|
||||
text(
|
||||
`plotting ${this.plotProgress}/${this.previewPath.length}`,
|
||||
16,
|
||||
132
|
||||
152
|
||||
);
|
||||
const elapsedMs = performance.now() - this._plotStartedAt;
|
||||
const remainMs = Math.max(0, this._plotEstimateMs - elapsedMs);
|
||||
text(
|
||||
`elapsed ${this._formatDuration(elapsedMs)} est ${this._formatDuration(this._plotEstimateMs)} remain ${this._formatDuration(remainMs)}`,
|
||||
16,
|
||||
152
|
||||
172
|
||||
);
|
||||
text(
|
||||
`seg wait ${this._lastSegmentMs.toFixed(0)} ms ×${this.timingMultiplier}`,
|
||||
16,
|
||||
172
|
||||
192
|
||||
);
|
||||
fill(220);
|
||||
}
|
||||
|
||||
if (this.connected && !this.homeSet && this._homeWarning) {
|
||||
fill(255, 180, 80);
|
||||
text(`! ${this._homeWarning}`, 16, this.isPlotting ? 192 : 132);
|
||||
text(`! ${this._homeWarning}`, 16, this.isPlotting ? 212 : 152);
|
||||
fill(220);
|
||||
}
|
||||
|
||||
text(
|
||||
`bounds L ${this._formatBound(this.drawBounds.left)} R ${this._formatBound(this.drawBounds.right)} T ${this._formatBound(this.drawBounds.top)} B ${this._formatBound(this.drawBounds.bottom)}`,
|
||||
16,
|
||||
this.isPlotting ? 212 : 152
|
||||
this.isPlotting ? 232 : 172
|
||||
);
|
||||
|
||||
const size = this._getDrawSize();
|
||||
const areaY = this.isPlotting ? 232 : 172;
|
||||
const areaY = this.isPlotting ? 252 : 192;
|
||||
if (size) {
|
||||
text(`area ${size.w.toFixed(1)} × ${size.h.toFixed(1)} mm`, 16, areaY);
|
||||
} else if (
|
||||
@@ -1012,7 +1100,8 @@ class WallPlotter {
|
||||
'+ / - step size',
|
||||
'H set current pos as home (G92) — required first',
|
||||
'0 go to origin (0, 0)',
|
||||
'P toggle pen',
|
||||
'P toggle pen (test up/down angles)',
|
||||
'S stop plot / test',
|
||||
'C connect / disconnect',
|
||||
];
|
||||
for (let i = 0; i < help.length; i++) {
|
||||
@@ -1074,6 +1163,7 @@ class WallPlotter {
|
||||
const next = this._okResolvers.shift();
|
||||
if (next) next.resolve();
|
||||
}
|
||||
this._parseMachineBounds(line);
|
||||
if (line.startsWith('X:')) {
|
||||
const match = line.match(/X:([-\d.]+)\s+Y:([-\d.]+)/);
|
||||
if (match) {
|
||||
@@ -1082,4 +1172,21 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse Marlin polargraph M665 report: M665 S… L… R… T… B… H… */
|
||||
_parseMachineBounds(line) {
|
||||
if (!/\bM665\b/i.test(line)) return;
|
||||
const l = line.match(/\bL(-?[\d.]+)/i);
|
||||
const r = line.match(/\bR(-?[\d.]+)/i);
|
||||
const t = line.match(/\bT(-?[\d.]+)/i);
|
||||
const b = line.match(/\bB(-?[\d.]+)/i);
|
||||
if (!l || !r || !t || !b) return;
|
||||
this.machineXMin = parseFloat(l[1]);
|
||||
this.machineXMax = parseFloat(r[1]);
|
||||
this.machineYMax = parseFloat(t[1]);
|
||||
this.machineYMin = parseFloat(b[1]);
|
||||
this.onLog(
|
||||
`machine bounds X ${this.machineXMin}…${this.machineXMax} Y ${this.machineYMin}…${this.machineYMax}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user