tutorial
This commit is contained in:
@@ -2,15 +2,20 @@
|
||||
|
||||
p5.js library for a Marlin polargraph (wall plotter) over Web Serial, plus a zigzag example.
|
||||
|
||||
**Anleitung** (Download, Linux/Windows/macOS, USB, Marlin/G-code): [tutorial.md](tutorial.md). Hardware: [hardware.md](hardware.md). G-code: [gcode.md](gcode.md).
|
||||
|
||||
```
|
||||
p5-wallplotter/
|
||||
├── tutorial.md # setup + plotter + G-code
|
||||
├── hardware.md # motors, board, geometry
|
||||
├── gcode.md # G-code used by this setup
|
||||
├── lib/
|
||||
│ ├── WallPlotter.js # library
|
||||
│ └── README.md # public API
|
||||
│ ├── README.md # short intro + example
|
||||
│ └── doc.md # full API
|
||||
└── examples/
|
||||
└── zickzack/ # example sketch
|
||||
├── index.html
|
||||
└── sketch.js
|
||||
├── zickzack/ # example sketch
|
||||
└── rechteck/ # rectangle sketch
|
||||
```
|
||||
|
||||
## Run the example
|
||||
@@ -51,14 +56,14 @@ function draw() {
|
||||
}
|
||||
|
||||
async function plot() {
|
||||
await drawArt(false);
|
||||
await drawPlot(false);
|
||||
}
|
||||
|
||||
async function test() {
|
||||
await drawArt(true);
|
||||
await drawPlot(true);
|
||||
}
|
||||
|
||||
async function drawArt(testOnly) {
|
||||
async function drawPlot(testOnly) {
|
||||
wp.startPath();
|
||||
wp.move(-200, -200);
|
||||
wp.draw(200, -200);
|
||||
@@ -66,6 +71,6 @@ async function drawArt(testOnly) {
|
||||
}
|
||||
```
|
||||
|
||||
Full API: [lib/README.md](lib/README.md).
|
||||
Full API: [lib/doc.md](lib/doc.md).
|
||||
|
||||
Baud **115200**. Pen servo: `M280 P0 S90` up, `M280 P0 S50` down. Coordinates in mm, origin at home (`G92`).
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
let wp;
|
||||
|
||||
let area = 100;
|
||||
|
||||
function setup() {
|
||||
const canvas = createCanvas(windowWidth, windowHeight);
|
||||
canvas.parent('sketch-host');
|
||||
|
||||
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();
|
||||
wp.enablePlotButtons();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(28, 28, 32);
|
||||
wp.showInfos();
|
||||
}
|
||||
|
||||
function windowResized() {
|
||||
resizeCanvas(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
async function plot() {
|
||||
await drawArt(false);
|
||||
}
|
||||
|
||||
async function test() {
|
||||
await drawArt(true);
|
||||
}
|
||||
|
||||
async function drawArt(testOnly) {
|
||||
wp.startPath();
|
||||
|
||||
|
||||
wp.move(-area, -area);
|
||||
//wp.wait(1000 * 10);
|
||||
|
||||
let step = area / 40;
|
||||
|
||||
let points = [];
|
||||
let last_points = [];
|
||||
let row = -1;
|
||||
|
||||
for (let y = -area; y <= area; y+=step){
|
||||
|
||||
// clear points
|
||||
if(points.length>0){
|
||||
last_points = deepCopy(points);
|
||||
points = [];
|
||||
}
|
||||
|
||||
let px = -area;
|
||||
let py = y+random(10)
|
||||
if (row==1){
|
||||
py = last_points[0].y;
|
||||
}
|
||||
|
||||
|
||||
wp.move(px, py);
|
||||
points.push(createVector(px,py));
|
||||
|
||||
let index = 1;
|
||||
for (let x = -area+step; x <= area; x+=step){
|
||||
px = x;
|
||||
py = y+random(step);
|
||||
|
||||
if (row==1 && index%2==0){
|
||||
py = last_points[index].y
|
||||
}
|
||||
|
||||
if (row==1 && index%2!=0){
|
||||
py = last_points[index-1].y+random(step*3);
|
||||
}
|
||||
|
||||
if (last_points.length>0 && row==-1 && index%2!=0){
|
||||
py = last_points[index].y
|
||||
}
|
||||
|
||||
if (last_points.length>0 && row==-1 && index%2==0){
|
||||
py = last_points[index-1].y+random(step*1);
|
||||
}
|
||||
|
||||
wp.draw(px, py);
|
||||
points.push(createVector(px,py));
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
row = row*-1;
|
||||
|
||||
}
|
||||
await wp.runPath(testOnly);
|
||||
}
|
||||
|
||||
|
||||
function deepCopy(copy_array){
|
||||
let new_array = []
|
||||
for(let i=0; i<copy_array.length; i++){
|
||||
new_array.push(copy_array[i]);
|
||||
}
|
||||
return new_array;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Wall Plotter — zigzag</title>
|
||||
<title>Wall Plotter — rectangle</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
@@ -46,7 +46,7 @@
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Wall Plotter — zigzag</h1>
|
||||
<h1>Wall Plotter — rectangle</h1>
|
||||
<main id="sketch-host"></main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
let wp;
|
||||
|
||||
function setup() {
|
||||
const canvas = createCanvas(windowWidth, windowHeight);
|
||||
canvas.parent('sketch-host');
|
||||
|
||||
wp = new WallPlotter({
|
||||
penUpGcode: 'M280 P0 S120',
|
||||
penDownGcode: 'M280 P0 S30',
|
||||
penUpDelayMs: 250,
|
||||
penDownDelayMs: 150,
|
||||
});
|
||||
wp.setSpeed(3000, 1500);
|
||||
wp.setTimingMultiplier(1);
|
||||
wp.enableKeyInputs();
|
||||
wp.enablePlotButtons();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(28, 28, 32);
|
||||
wp.showInfos();
|
||||
}
|
||||
|
||||
function windowResized() {
|
||||
resizeCanvas(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
async function plot() {
|
||||
await drawPlot(false);
|
||||
}
|
||||
|
||||
async function test() {
|
||||
await drawPlot(true);
|
||||
}
|
||||
|
||||
async function drawPlot(testOnly) {
|
||||
const s = 200; // 400 × 400 mm around home
|
||||
wp.startPath();
|
||||
wp.move(-s, -s); // pen up to bottom-left
|
||||
wp.draw(s, -s); // bottom edge
|
||||
wp.draw(s, s); // right edge
|
||||
wp.draw(-s, s); // top edge
|
||||
wp.draw(-s, -s); // left edge, close the rectangle
|
||||
wp.move(0, 0); // pen up back to home
|
||||
await wp.runPath(testOnly);
|
||||
}
|
||||
@@ -28,14 +28,14 @@ function windowResized() {
|
||||
}
|
||||
|
||||
async function plot() {
|
||||
await drawArt(false);
|
||||
await drawPlot(false);
|
||||
}
|
||||
|
||||
async function test() {
|
||||
await drawArt(true);
|
||||
await drawPlot(true);
|
||||
}
|
||||
|
||||
async function drawArt(testOnly) {
|
||||
async function drawPlot(testOnly) {
|
||||
wp.startPath();
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# G-code (dieses Setup)
|
||||
|
||||
Befehle, die p5-wallplotter und Marlin Polargraph hier verwenden. Setup: [tutorial.md](tutorial.md). Hardware: [hardware.md](hardware.md).
|
||||
|
||||
| G-code | Funktion hier |
|
||||
|--------|----------------|
|
||||
| `G0` / `G1 X… Y… F…` | Fahren. `F` ist Vorschub in **mm/min** (nicht mm/s). Pen-up = Travel-Speed, Pen-down = Draw-Speed. |
|
||||
| `G92 X0 Y0` | „Hier ist der Ursprung.“ Kein physikalisches Homing mit Endschaltern. |
|
||||
| `G4 P…` | Pause in Millisekunden (`wp.wait()`). |
|
||||
| `M17` | Motoren einschalten. |
|
||||
| `M114` | Position melden (`X:… Y:…`). |
|
||||
| `M280 P0 S…` | Servo 0 auf Winkel `S` Grad. Stift hoch/runter. |
|
||||
| `M400` | Motion-Queue leeren: erst `ok`, wenn die letzte Bewegung fertig ist. Ohne das würde der Browser den nächsten Punkt schicken, während die Gondel noch fährt. |
|
||||
| `M665` | Polargraph-Geometrie lesen/setzen: `L`/`R` X-Grenzen, `T`/`B` Y-Grenzen, `H` max. Riemenlänge, `S` Segmente/s. |
|
||||
| `M906 X… Y…` | Motorstrom in mA (TMC). |
|
||||
|
||||
Protokoll: **eine Zeile, warten auf `ok`**. Die Library schickt nicht den ganzen Job auf einmal. Timeout ohne `ok` pausiert den Plot, damit **Resume** weitermachen kann.
|
||||
|
||||
Geschwindigkeiten in der Library (`setSpeed(travel, draw)`) landen als `F` auf `G1`. Bereich 1–10000 mm/min. Die Preview-Zeit im Test ist eine Schätzung aus Distanz und diesen Feeds; der echte Plot folgt Marlins Tempo plus `M400`.
|
||||
|
||||
Beispiel einer Fahrt mit Stift unten:
|
||||
|
||||
```gcode
|
||||
M280 P0 S30 ; Servo 0 auf 30° → Stift aufs Papier
|
||||
G1 X120 Y-40 F2500 ; Gerade zu (120, −40) mm mit 2500 mm/min
|
||||
M400 ; warten, bis die Bewegung wirklich fertig ist
|
||||
```
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
# Hardware: Wandplotter
|
||||
|
||||
Der Wandplotter ist ein **Polargraph**: eine Gondel hängt an zwei Riemen, die oben links und rechts über Motoren und Umlenkrollen laufen. Es gibt keine X/Y-Schienen. Position entsteht allein aus den beiden Riemenlängen.
|
||||
|
||||
Setup und Software: [tutorial.md](tutorial.md).
|
||||
|
||||
## Baugruppen
|
||||
|
||||
| Teil | Rolle |
|
||||
|------|--------|
|
||||
| **Linker Motor** | NEMA-17-Schrittmotor, Buchse **X** auf der SKR. Wickelt den linken Riemen. Längerer linker Riemen → Gondel nach rechts/unten. |
|
||||
| **Rechter Motor** | Gleicher Typ, Buchse **Y**. Wickelt den rechten Riemen. |
|
||||
| **Umlenkrollen (Idler)** | Oben, Abstand der Mitten **1660 mm**. Die Riemenlänge zählt **von der Rolle zur Gondel**, nicht die Schleife Motor↔Rolle. |
|
||||
| **Riemen / Zahnriemen** | Zwei Längen, max. in der Firmware 3500 mm (`POLARGRAPH_MAX_BELT_LEN`). |
|
||||
| **Gondel** | Hängender Schlitten mit Stifthalter. Gewicht hält die Riemen straff. |
|
||||
| **Servo** | Hebt und senkt den Stift. Marlin: Servo 0, Befehl `M280`. Im Beispiel oft `S120` oben, `S30` unten (an Mechanik anpassen). |
|
||||
| **Steuerung** | BigTreeTech **SKR Mini E3 V3**, STM32G0, USB-CDC zum Rechner. Treiber **TMC2209**, typisch 1200 mA RMS für die X/Y-Motoren. |
|
||||
| **Firmware** | Marlin mit `#define POLARGRAPH`. Kartesische `G1 X… Y…` werden intern in zwei Riemenlängen umgerechnet. |
|
||||
| **Rechner + Browser** | p5-Sketch, Web Serial, eine G-code-Zeile nach der anderen. |
|
||||
|
||||
## Motorzuordnung (Wand → Board)
|
||||
|
||||
| Seite der Wand | Buchse |
|
||||
|----------------|--------|
|
||||
| links | **X** |
|
||||
| rechts | **Y** |
|
||||
|
||||
Z wird für die hängenden Motoren nicht gebraucht. Der „Extruder“ eines 3D-Druckers ist hier der Stift-Servo.
|
||||
|
||||
## Marlin Polargraph (`Configuration.h`)
|
||||
|
||||
Quelle: `MarlinBigTree/Marlin/Configuration.h` und `Configuration_adv.h`. Board: **SKR Mini E3 V3**.
|
||||
|
||||
```c
|
||||
#define POLARGRAPH
|
||||
#if ENABLED(POLARGRAPH)
|
||||
#define POLARGRAPH_MAX_BELT_LEN 3500.0 // mm, Override mit M665 H
|
||||
#define DEFAULT_SEGMENTS_PER_SECOND 5
|
||||
#define PEN_UP_DOWN_MENU
|
||||
#endif
|
||||
|
||||
#define X_BED_SIZE 1660 // Abstand der Umlenkrollen (Mitten)
|
||||
#define Y_BED_SIZE 2000 // Zeichnungs-Höhe unter der Rollenlinie
|
||||
#define X_MIN_POS (-X_BED_SIZE/2) // -830
|
||||
#define X_MAX_POS (X_BED_SIZE/2) // 830
|
||||
#define Y_MAX_POS 800 // Rollenlinie (80 cm über Y=0)
|
||||
#define Y_MIN_POS (Y_MAX_POS - Y_BED_SIZE) // -1200
|
||||
|
||||
#define BED_CENTER_AT_0_0
|
||||
#define MANUAL_X_HOME_POS 0
|
||||
#define MANUAL_Y_HOME_POS -2598.19 // Boot-Pose bei max. gleicher Riemenlänge
|
||||
|
||||
#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 400 } // 16×, 20Z GT2 → 40 mm/U
|
||||
#define DEFAULT_MAX_FEEDRATE { 500, 500, 5 } // mm/s
|
||||
#define DEFAULT_MAX_ACCELERATION { 3000, 3000, 100 }
|
||||
|
||||
#define INVERT_X_DIR false
|
||||
#define INVERT_Y_DIR true
|
||||
|
||||
#define EXTRUDERS 0
|
||||
#define NUM_SERVOS 1
|
||||
#define SERVO_DELAY { 300 }
|
||||
#define DEACTIVATE_SERVOS_AFTER_MOVE
|
||||
```
|
||||
|
||||
`MANUAL_Y_HOME_POS` folgt aus `Y_MAX_POS - sqrt(POLARGRAPH_MAX_BELT_LEN² − (X_BED_SIZE/2)²)` ≈ −2598. Nach dem Einschalten liegt die Maschine dort, nicht an der Blattmitte. Arbeitsursprung setzen mit **`G92 X0 Y0`** in der Blattmitte (80 cm unter den Rollen).
|
||||
|
||||
### Treiber (`Configuration_adv.h`)
|
||||
|
||||
TMC2209, 16× Microsteps, 1200 mA RMS:
|
||||
|
||||
```c
|
||||
#define X_CURRENT 1200
|
||||
#define Y_CURRENT 1200
|
||||
#define X_MICROSTEPS 16
|
||||
#define Y_MICROSTEPS 16
|
||||
```
|
||||
|
||||
`EMERGENCY_PARSER` ist aus.
|
||||
|
||||
### Live nachjustieren
|
||||
|
||||
Zeichnung konisch/schief (erst nach korrektem Home):
|
||||
|
||||
```gcode
|
||||
M665 L-830 R830
|
||||
```
|
||||
|
||||
Motoren zu heiss oder Schrittverlust (Firmware-Default 1200 mA):
|
||||
|
||||
```gcode
|
||||
M906 X1000 Y1000
|
||||
```
|
||||
+25
-210
@@ -2,229 +2,44 @@
|
||||
|
||||
p5 helper for a Marlin polargraph over Web Serial. Coordinates are **mm**. Origin is machine home (`G92`). Y positive is up. Chrome or Edge, page on localhost or HTTPS.
|
||||
|
||||
Load from this package:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
|
||||
<script src="../../lib/WallPlotter.js"></script>
|
||||
<script src="sketch.js"></script>
|
||||
```
|
||||
|
||||
Example sketch: `examples/zickzack/`.
|
||||
|
||||
---
|
||||
|
||||
## `new WallPlotter(options?)`
|
||||
|
||||
All options are optional.
|
||||
|
||||
| Option | Default | What it is |
|
||||
|--------|---------|------------|
|
||||
| `baudRate` | `115200` | Serial baud (must match Marlin) |
|
||||
| `stepSize` | `10` | Keyboard jog step, mm |
|
||||
| `feedRateTravel` | `1500` | Pen-up speed, **mm/min** (G-code `F`) |
|
||||
| `feedRateDraw` | `300` | Pen-down speed, **mm/min** |
|
||||
| `penUpGcode` | `M280 P0 S90` | Command to lift pen |
|
||||
| `penDownGcode` | `M280 P0 S50` | Command to lower pen |
|
||||
| `penUpDelayMs` | `150` | Pause after pen up, ms |
|
||||
| `penDownDelayMs` | `50` | Pause after pen down, ms |
|
||||
| `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` | `40` | Serial lines in the selectable log (bottom-right) |
|
||||
| `previewMinDelayMs` | `30` | Floor for preview segment wait, ms |
|
||||
| `previewFrameMs` | `16` | Preview animation frame time, ms |
|
||||
| `timingMultiplier` | `1` | Scale **Test** preview waits (`2` = twice as slow) |
|
||||
| `onLog(line)` | no-op | Extra callback for each log line |
|
||||
| `onStatusChange(status)` | no-op | `'connected'` or `'disconnected'` |
|
||||
|
||||
---
|
||||
|
||||
## Setup / UI
|
||||
|
||||
### `enableKeyInputs()`
|
||||
|
||||
Keyboard controls. Call from `setup()`. No arguments.
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `C` | Connect / disconnect |
|
||||
| `T` | Run Test (preview) |
|
||||
| `A` | Abort / reset Test to start |
|
||||
| `H` | Set home here (`G92 X0 Y0`) — required before Plot |
|
||||
| `0` | Travel to `(0, 0)` |
|
||||
| `P` | Toggle pen |
|
||||
| `← → ↑ ↓` | Jog one step, pen up |
|
||||
| `Shift + arrows` | Jog one step, pen down |
|
||||
| `+` / `-` | Jog step size 1–100 mm |
|
||||
|
||||
### `enablePlotButtons(options?)`
|
||||
|
||||
Adds Connect, Plot, Test, and Abort buttons. Expects global `plot()` and `test()` in the sketch.
|
||||
|
||||
**Connect** is shown only while disconnected (same as key **C**). While a path is running, Plot becomes **Pause**; while paused it becomes **Resume**. Test becomes **Stop** during a test. **Abort** stops and resets the Test preview to the start (key **A**).
|
||||
|
||||
| Option | Default | What it is |
|
||||
|--------|---------|------------|
|
||||
| `parentId` | `'wallplotter-controls'` | DOM id for the button row |
|
||||
| `connectLabel` | `'Connect'` | Connect button text |
|
||||
| `plotLabel` | `'Plot'` | Plot button text when idle |
|
||||
| `pauseLabel` | `'Pause'` | Plot button while a path is running |
|
||||
| `resumeLabel` | `'Resume'` | Plot button while paused |
|
||||
| `testLabel` | `'Test'` | Test button text |
|
||||
| `stopLabel` | `'Stop'` | Test button while a test is running |
|
||||
| `abortLabel` | `'Abort'` | Reset test/plot preview to the start |
|
||||
|
||||
### `showInfos()`
|
||||
|
||||
Draw HUD, path preview, bounds, and key help. Call every frame from `draw()`. Also runs held-arrow jogging when keys are enabled.
|
||||
|
||||
---
|
||||
|
||||
## Speed
|
||||
|
||||
### `setSpeed(travel, draw)`
|
||||
|
||||
Feed rates in **mm/min**. Used as G-code `F` and for Test preview timing. Clamped to **1–10000**.
|
||||
|
||||
- `travel` (number): pen-up / `move()` speed. Or `{ travel, draw }`.
|
||||
- `draw` (number, optional): pen-down / `draw()` speed. Omit to leave draw rate unchanged.
|
||||
|
||||
Practical range about **100–10000**. Values above **10000** are clamped. Do not use `0`.
|
||||
|
||||
```javascript
|
||||
wp.setSpeed(6000, 2500);
|
||||
wp.setSpeed({ travel: 6000, draw: 2500 });
|
||||
```
|
||||
let wp;
|
||||
|
||||
### `setTimingMultiplier(mult)`
|
||||
function setup() {
|
||||
createCanvas(windowWidth, windowHeight).parent('sketch-host');
|
||||
wp = new WallPlotter();
|
||||
wp.setSpeed(3000, 1500);
|
||||
wp.enableKeyInputs();
|
||||
wp.enablePlotButtons();
|
||||
}
|
||||
|
||||
Scales **Test** preview waits only. Plot is paced by Marlin (`ok` + `M400`).
|
||||
function draw() {
|
||||
background(28, 28, 32);
|
||||
wp.showInfos();
|
||||
}
|
||||
|
||||
- `mult` (number): `1` = match `setSpeed` estimate, `2` = twice as slow, `0.5` = twice as fast. Clamped to ≥ `0.1`.
|
||||
async function plot() {
|
||||
await drawPlot(false);
|
||||
}
|
||||
|
||||
---
|
||||
async function test() {
|
||||
await drawPlot(true);
|
||||
}
|
||||
|
||||
## Path (buffered drawing)
|
||||
|
||||
`move` / `draw` / `wait` / `speed` only **queue**. Nothing is sent until `runPath`.
|
||||
|
||||
### `startPath()`
|
||||
|
||||
Clear the path buffer. Returns `this`.
|
||||
|
||||
### `move(x, y)`
|
||||
|
||||
Queue pen-up travel to `(x, y)` mm. Returns `this`.
|
||||
|
||||
### `draw(x, y)`
|
||||
|
||||
Queue pen-down stroke to `(x, y)` mm. Returns `this`.
|
||||
|
||||
### `wait(ms)`
|
||||
|
||||
Queue a pause. `ms` is milliseconds. On Plot, sends `G4 P<ms>`. Returns `this`.
|
||||
|
||||
### `speed(travel, drawRate)`
|
||||
|
||||
Queue a mid-path speed change (mm/min, clamped 1–10000). Either argument may be omitted. Restored after `runPath`. Returns `this`.
|
||||
|
||||
### `runPath(testOnly)` / `runPath(commands, testOnly?)`
|
||||
|
||||
Run the path. **async**.
|
||||
|
||||
- `testOnly` (boolean): `true` = preview only, no serial. `false` = send G-code. Home must be set for Plot.
|
||||
- `commands` (array, optional): explicit list instead of the `startPath()` buffer.
|
||||
|
||||
Plot: wait for Marlin `ok`, then `M400` (move finished). Test: animate using distance / `setSpeed` × `timingMultiplier`.
|
||||
|
||||
```javascript
|
||||
async function drawPlot(testOnly) {
|
||||
wp.startPath();
|
||||
wp.move(-200, -200);
|
||||
wp.draw(200, -200);
|
||||
wp.wait(500);
|
||||
await wp.runPath(false); // plot
|
||||
await wp.runPath(true); // test
|
||||
await wp.runPath(testOnly);
|
||||
}
|
||||
```
|
||||
|
||||
Sketch: `plot()` → `runPath(false)`, `test()` → `runPath(true)`.
|
||||
Press **C** to connect, **H** to set home, then **Test** or **Plot**.
|
||||
|
||||
### `estimatePathMs(commands?)`
|
||||
|
||||
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()` / `stopPlot()`
|
||||
|
||||
`stopPath()` aborts the current `runPath` after the in-flight command gets its `ok` (it does not cancel that wait — that was dropping the next `ok`).
|
||||
|
||||
`stopPlot()` first press is the same as **Pause**: finish the current stroke, then **Resume** continues. Second press stops the browser from sending more moves. The **in-flight** `G1` still runs — this firmware does not have `EMERGENCY_PARSER`, so `M410` cannot interrupt motion (and on a polargraph it desyncs belts). Unplug power for a hard stop.
|
||||
|
||||
### `pausePlot()` / `resumePlot()` / `abortToStart()`
|
||||
|
||||
**Pause** / first **Stop** wait for the current segment to finish (`M400`), then lift the pen. **Resume** continues from there.
|
||||
|
||||
**Abort** (button or **A**) stops the run and resets the Test preview to the start. Live Plot only stops sending further moves; the machine stays where it is.
|
||||
|
||||
---
|
||||
|
||||
## Serial / machine
|
||||
|
||||
All **async**. Coordinate moves on Plot require home (`H`) first.
|
||||
|
||||
### `connect()`
|
||||
|
||||
Web Serial port picker, enable steppers (`M17`), pen up, query polargraph area (`M665` → machine overlay bounds), `M114`. Reload/close releases the port (needed on macOS). If only one previously allowed port exists, it is reused.
|
||||
|
||||
### `disconnect()`
|
||||
|
||||
Close the serial port.
|
||||
|
||||
### `send(line, options?)`
|
||||
|
||||
Write one G-code line and wait for Marlin `ok`.
|
||||
|
||||
- `line` (string): command without newline, e.g. `'M114'`.
|
||||
- `options.allowWithoutHome` (boolean): allow `G0`/`G1` before home. Default `false`.
|
||||
- `options.waitOk` (boolean): wait for `ok`. Default `true`.
|
||||
- `options.timeoutMs` (number): `ok` timeout. Default **15000**. `M400` / `G4` use a longer timeout based on the move or dwell. After 1s with no `ok`, the log shows `waiting for Marlin ok` and the HUD says **Waiting for plotter…**. During a plot, a timeout **pauses** so **Resume** can continue; **S** / Stop does the same.
|
||||
|
||||
### `penUp(force?)` / `penDown(force?)`
|
||||
|
||||
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)`
|
||||
|
||||
Pen up, travel to `(x, y)` mm immediately (not queued). Requires home.
|
||||
|
||||
### `drawTo(x, y)`
|
||||
|
||||
Pen down, draw to `(x, y)` mm immediately. Requires home.
|
||||
|
||||
### `goToZero()`
|
||||
|
||||
`moveTo(0, 0)`.
|
||||
|
||||
### `step(dx, dy, draw?)`
|
||||
|
||||
Relative move in mm.
|
||||
|
||||
- `dx`, `dy` (number): delta mm
|
||||
- `draw` (boolean, default `false`): `true` → `drawTo`, else `moveTo`
|
||||
|
||||
### `setHomeHere()`
|
||||
|
||||
`G92 X0 Y0`. Sets local `x,y` to 0 and marks home as set.
|
||||
|
||||
---
|
||||
|
||||
## Fields (read)
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `connected` | Serial open |
|
||||
| `homeSet` | Home was set with `H` / `setHomeHere()` |
|
||||
| `x`, `y` | Current position, mm |
|
||||
| `isPenDown` | Pen state |
|
||||
| `isPlotting` | `runPath` in progress |
|
||||
| `isPaused` | `runPath` is paused |
|
||||
| `feedRateTravel` / `feedRateDraw` | Current speeds, mm/min |
|
||||
| `timingMultiplier` | Preview wait scale |
|
||||
| `stepSize` | Jog step, mm |
|
||||
Example sketches: `examples/zickzack/`, `examples/rechteck/`. Full API: [doc.md](doc.md).
|
||||
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
# WallPlotter API
|
||||
|
||||
Public API for `WallPlotter.js`. Quick start: [README.md](README.md).
|
||||
|
||||
---
|
||||
|
||||
## `new WallPlotter(options?)`
|
||||
|
||||
All options are optional.
|
||||
|
||||
| Option | Default | What it is |
|
||||
|--------|---------|------------|
|
||||
| `baudRate` | `115200` | Serial baud (must match Marlin) |
|
||||
| `stepSize` | `10` | Keyboard jog step, mm |
|
||||
| `feedRateTravel` | `1500` | Pen-up speed, **mm/min** (G-code `F`) |
|
||||
| `feedRateDraw` | `300` | Pen-down speed, **mm/min** |
|
||||
| `penUpGcode` | `M280 P0 S90` | Command to lift pen |
|
||||
| `penDownGcode` | `M280 P0 S50` | Command to lower pen |
|
||||
| `penUpDelayMs` | `150` | Pause after pen up, ms |
|
||||
| `penDownDelayMs` | `50` | Pause after pen down, ms |
|
||||
| `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` | `40` | Serial lines in the selectable log (bottom-right) |
|
||||
| `previewMinDelayMs` | `30` | Floor for preview segment wait, ms |
|
||||
| `previewFrameMs` | `16` | Preview animation frame time, ms |
|
||||
| `timingMultiplier` | `1` | Scale **Test** preview waits (`2` = twice as slow) |
|
||||
| `onLog(line)` | no-op | Extra callback for each log line |
|
||||
| `onStatusChange(status)` | no-op | `'connected'` or `'disconnected'` |
|
||||
|
||||
---
|
||||
|
||||
## Setup / UI
|
||||
|
||||
### `enableKeyInputs()`
|
||||
|
||||
Keyboard controls. Call from `setup()`. No arguments.
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `C` | Connect / disconnect |
|
||||
| `T` | Run Test (preview) |
|
||||
| `A` | Abort / reset Test to start |
|
||||
| `H` | Set home here (`G92 X0 Y0`) — required before Plot |
|
||||
| `0` | Travel to `(0, 0)` |
|
||||
| `P` | Toggle pen |
|
||||
| `← → ↑ ↓` | Jog one step, pen up |
|
||||
| `Shift + arrows` | Jog one step, pen down |
|
||||
| `+` / `-` | Jog step size 1–100 mm |
|
||||
|
||||
### `enablePlotButtons(options?)`
|
||||
|
||||
Adds Connect, Plot, Test, and Abort buttons. Expects global `plot()` and `test()` in the sketch.
|
||||
|
||||
**Connect** is shown only while disconnected (same as key **C**). While a path is running, Plot becomes **Pause**; while paused it becomes **Resume**. Test becomes **Stop** during a test. **Abort** stops and resets the Test preview to the start (key **A**).
|
||||
|
||||
| Option | Default | What it is |
|
||||
|--------|---------|------------|
|
||||
| `parentId` | `'wallplotter-controls'` | DOM id for the button row |
|
||||
| `connectLabel` | `'Connect'` | Connect button text |
|
||||
| `plotLabel` | `'Plot'` | Plot button text when idle |
|
||||
| `pauseLabel` | `'Pause'` | Plot button while a path is running |
|
||||
| `resumeLabel` | `'Resume'` | Plot button while paused |
|
||||
| `testLabel` | `'Test'` | Test button text |
|
||||
| `stopLabel` | `'Stop'` | Test button while a test is running |
|
||||
| `abortLabel` | `'Abort'` | Reset test/plot preview to the start |
|
||||
|
||||
### `showInfos()`
|
||||
|
||||
Draw HUD, path preview, bounds, and key help. Call every frame from `draw()`. Also runs held-arrow jogging when keys are enabled.
|
||||
|
||||
---
|
||||
|
||||
## Speed
|
||||
|
||||
### `setSpeed(travel, draw)`
|
||||
|
||||
Feed rates in **mm/min**. Used as G-code `F` and for Test preview timing. Clamped to **1–10000**.
|
||||
|
||||
- `travel` (number): pen-up / `move()` speed. Or `{ travel, draw }`.
|
||||
- `draw` (number, optional): pen-down / `draw()` speed. Omit to leave draw rate unchanged.
|
||||
|
||||
Practical range about **100–10000**. Values above **10000** are clamped. Do not use `0`.
|
||||
|
||||
```javascript
|
||||
wp.setSpeed(6000, 2500);
|
||||
wp.setSpeed({ travel: 6000, draw: 2500 });
|
||||
```
|
||||
|
||||
### `setTimingMultiplier(mult)`
|
||||
|
||||
Scales **Test** preview waits only. Plot is paced by Marlin (`ok` + `M400`).
|
||||
|
||||
- `mult` (number): `1` = match `setSpeed` estimate, `2` = twice as slow, `0.5` = twice as fast. Clamped to ≥ `0.1`.
|
||||
|
||||
---
|
||||
|
||||
## Path (buffered drawing)
|
||||
|
||||
`move` / `draw` / `wait` / `speed` only **queue**. Nothing is sent until `runPath`.
|
||||
|
||||
### `startPath()`
|
||||
|
||||
Clear the path buffer. Returns `this`.
|
||||
|
||||
### `move(x, y)`
|
||||
|
||||
Queue pen-up travel to `(x, y)` mm. Returns `this`.
|
||||
|
||||
### `draw(x, y)`
|
||||
|
||||
Queue pen-down stroke to `(x, y)` mm. Returns `this`.
|
||||
|
||||
### `wait(ms)`
|
||||
|
||||
Queue a pause. `ms` is milliseconds. On Plot, sends `G4 P<ms>`. Returns `this`.
|
||||
|
||||
### `speed(travel, drawRate)`
|
||||
|
||||
Queue a mid-path speed change (mm/min, clamped 1–10000). Either argument may be omitted. Restored after `runPath`. Returns `this`.
|
||||
|
||||
### `runPath(testOnly)` / `runPath(commands, testOnly?)`
|
||||
|
||||
Run the path. **async**.
|
||||
|
||||
- `testOnly` (boolean): `true` = preview only, no serial. `false` = send G-code. Home must be set for Plot.
|
||||
- `commands` (array, optional): explicit list instead of the `startPath()` buffer.
|
||||
|
||||
Plot: wait for Marlin `ok`, then `M400` (move finished). Test: animate using distance / `setSpeed` × `timingMultiplier`.
|
||||
|
||||
```javascript
|
||||
wp.startPath();
|
||||
wp.move(-200, -200);
|
||||
wp.draw(200, -200);
|
||||
wp.wait(500);
|
||||
await wp.runPath(false); // plot
|
||||
await wp.runPath(true); // test
|
||||
```
|
||||
|
||||
Sketch: `plot()` → `runPath(false)`, `test()` → `runPath(true)`.
|
||||
|
||||
### `estimatePathMs(commands?)`
|
||||
|
||||
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()` / `stopPlot()`
|
||||
|
||||
`stopPath()` aborts the current `runPath` after the in-flight command gets its `ok` (it does not cancel that wait — that was dropping the next `ok`).
|
||||
|
||||
`stopPlot()` first press is the same as **Pause**: finish the current stroke, then **Resume** continues. Second press stops the browser from sending more moves. The **in-flight** `G1` still runs — this firmware does not have `EMERGENCY_PARSER`, so `M410` cannot interrupt motion (and on a polargraph it desyncs belts). Unplug power for a hard stop.
|
||||
|
||||
### `pausePlot()` / `resumePlot()` / `abortToStart()`
|
||||
|
||||
**Pause** / first **Stop** wait for the current segment to finish (`M400`), then lift the pen. **Resume** continues from there.
|
||||
|
||||
**Abort** (button or **A**) stops the run and resets the Test preview to the start. Live Plot only stops sending further moves; the machine stays where it is.
|
||||
|
||||
---
|
||||
|
||||
## Serial / machine
|
||||
|
||||
All **async**. Coordinate moves on Plot require home (`H`) first.
|
||||
|
||||
### `connect()`
|
||||
|
||||
Web Serial port picker, enable steppers (`M17`), pen up, query polargraph area (`M665` → machine overlay bounds), `M114`. Reload/close releases the port (needed on macOS). If only one previously allowed port exists, it is reused.
|
||||
|
||||
### `disconnect()`
|
||||
|
||||
Close the serial port.
|
||||
|
||||
### `send(line, options?)`
|
||||
|
||||
Write one G-code line and wait for Marlin `ok`.
|
||||
|
||||
- `line` (string): command without newline, e.g. `'M114'`.
|
||||
- `options.allowWithoutHome` (boolean): allow `G0`/`G1` before home. Default `false`.
|
||||
- `options.waitOk` (boolean): wait for `ok`. Default `true`.
|
||||
- `options.timeoutMs` (number): `ok` timeout. Default **15000**. `M400` / `G4` use a longer timeout based on the move or dwell. After 1s with no `ok`, the log shows `waiting for Marlin ok` and the HUD says **Waiting for plotter…**. During a plot, a timeout **pauses** so **Resume** can continue; **S** / Stop does the same.
|
||||
|
||||
### `penUp(force?)` / `penDown(force?)`
|
||||
|
||||
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)`
|
||||
|
||||
Pen up, travel to `(x, y)` mm immediately (not queued). Requires home.
|
||||
|
||||
### `drawTo(x, y)`
|
||||
|
||||
Pen down, draw to `(x, y)` mm immediately. Requires home.
|
||||
|
||||
### `goToZero()`
|
||||
|
||||
`moveTo(0, 0)`.
|
||||
|
||||
### `step(dx, dy, draw?)`
|
||||
|
||||
Relative move in mm.
|
||||
|
||||
- `dx`, `dy` (number): delta mm
|
||||
- `draw` (boolean, default `false`): `true` → `drawTo`, else `moveTo`
|
||||
|
||||
### `setHomeHere()`
|
||||
|
||||
`G92 X0 Y0`. Sets local `x,y` to 0 and marks home as set.
|
||||
|
||||
---
|
||||
|
||||
## Fields (read)
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `connected` | Serial open |
|
||||
| `homeSet` | Home was set with `H` / `setHomeHere()` |
|
||||
| `x`, `y` | Current position, mm |
|
||||
| `isPenDown` | Pen state |
|
||||
| `isPlotting` | `runPath` in progress |
|
||||
| `isPaused` | `runPath` is paused |
|
||||
| `feedRateTravel` / `feedRateDraw` | Current speeds, mm/min |
|
||||
| `timingMultiplier` | Preview wait scale |
|
||||
| `stepSize` | Jog step, mm |
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
# Tutorial: p5-wallplotter
|
||||
|
||||
Dieses Tutorial erklärt, wie du das Repository lädst, den Beispiel-Sketch lokal startest, den Wandplotter per USB verbindest und was Marlin mit G-code eigentlich macht.
|
||||
|
||||
**Kurzüberblick:** Der Browser zeichnet mit p5.js. Die Library `WallPlotter.js` wandelt Linien in G-code um und schickt ihn über **Web Serial** an die Steuerung. Dort läuft **Marlin** mit Polargraph-Kinematik: Kartesische Koordinaten (`X`/`Y` in Millimetern) werden in zwei Riemenlängen übersetzt, die die Motoren auf- und abwickeln.
|
||||
|
||||
Voraussetzungen:
|
||||
|
||||
- **Chrome** oder **Edge** (Web Serial gibt es nicht in Firefox oder Safari)
|
||||
- Python 3
|
||||
- USB-Kabel zum Plotter
|
||||
- Plotter mit Stromversorgung (Steuerung + Motoren)
|
||||
|
||||
Web Serial funktioniert **nicht** über `file://`. Die Seite muss von `http://localhost` oder über HTTPS kommen.
|
||||
|
||||
---
|
||||
|
||||
## 1. Repository herunterladen
|
||||
|
||||
Das Projekt liegt hier:
|
||||
|
||||
[https://git.collectivemedia.space/martin/p5-wallplotter](https://git.collectivemedia.space/martin/p5-wallplotter)
|
||||
|
||||
Auf der Projektseite **Code → Download ZIP** (oder direkt [archive/main.zip](https://git.collectivemedia.space/martin/p5-wallplotter/archive/main.zip)). Archiv entpacken und in den Ordner `p5-wallplotter` wechseln.
|
||||
|
||||
### Was im Ordner liegt
|
||||
|
||||
```
|
||||
p5-wallplotter/
|
||||
├── lib/
|
||||
│ ├── WallPlotter.js # p5-Library: Serial, G-code, Preview, Tasten
|
||||
│ ├── README.md # Kurzüberblick + Beispiel
|
||||
│ └── doc.md # API-Referenz
|
||||
├── examples/
|
||||
│ ├── zickzack/ # Einstiegsbeispiel
|
||||
│ └── rechteck/ # Rechteck zeichnen
|
||||
├── hardware.md # Motoren, Board, Geometrie
|
||||
├── gcode.md # G-code in diesem Setup
|
||||
└── tutorial.md # diese Anleitung
|
||||
```
|
||||
|
||||
Es gibt kein `npm install`. Der Sketch lädt p5.js vom CDN; der Rest sind statische Dateien.
|
||||
|
||||
---
|
||||
|
||||
## 2. Umgebung starten
|
||||
|
||||
Ziel: einen lokalen HTTP-Server im Projektordner, danach den Sketch in Chrome/Edge öffnen.
|
||||
|
||||
### Linux
|
||||
|
||||
Im Projektordner:
|
||||
|
||||
```bash
|
||||
cd p5-wallplotter
|
||||
python3 -m http.server 8080
|
||||
```
|
||||
|
||||
Browser: [http://localhost:8080/examples/zickzack/](http://localhost:8080/examples/zickzack/)
|
||||
|
||||
Server mit `Ctrl+C` beenden.
|
||||
|
||||
Für den USB-Port braucht dein Benutzer die Serial-Gruppe. Nach der Änderung einmal ab- und anmelden:
|
||||
|
||||
```bash
|
||||
# Debian / Ubuntu
|
||||
sudo usermod -aG dialout "$USER"
|
||||
|
||||
# Arch / CachyOS
|
||||
sudo usermod -aG uucp "$USER"
|
||||
```
|
||||
|
||||
Prüfen, ob das Board erscheint (SKR Mini E3 V3 als USB-CDC):
|
||||
|
||||
```bash
|
||||
ls /dev/ttyACM* /dev/ttyUSB*
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
1. Python von [python.org](https://www.python.org/downloads/) installieren. Beim Setup **Add python.exe to PATH** aktivieren.
|
||||
2. PowerShell oder Eingabeaufforderung im Projektordner:
|
||||
|
||||
```powershell
|
||||
cd p5-wallplotter
|
||||
py -m http.server 8080
|
||||
```
|
||||
|
||||
Falls `py` unbekannt ist: `python -m http.server 8080`.
|
||||
|
||||
3. Chrome oder Edge: [http://localhost:8080/examples/zickzack/](http://localhost:8080/examples/zickzack/)
|
||||
|
||||
Das Board erscheint in der Geräte-Manager-Ansicht unter **Anschlüsse (COM & LPT)** als virtueller COM-Port. Web Serial zeigt denselben Port im Verbindungsdialog. Extra-Treiber sind bei der SKR Mini E3 V3 meist nicht nötig; wenn der Port fehlt, USB-Kabel wechseln (manche Kabel sind nur Ladekabel) und den STM32 Virtual COM Port / WinUSB-Pfad prüfen.
|
||||
|
||||
### macOS
|
||||
|
||||
Im Terminal:
|
||||
|
||||
```bash
|
||||
cd p5-wallplotter
|
||||
python3 -m http.server 8080
|
||||
```
|
||||
|
||||
**Chrome** oder **Edge** öffnen (nicht Safari): [http://localhost:8080/examples/zickzack/](http://localhost:8080/examples/zickzack/)
|
||||
|
||||
Der Port heisst typischerweise `/dev/cu.usbmodem…`. macOS hält einen Serial-Port oft fest, wenn der Tab nicht sauber schliesst. Seite neu laden oder den Tab schliessen, bevor du erneut verbindest — die Library gibt den Port beim Schliessen frei.
|
||||
|
||||
Falls `python3` fehlt: `xcode-select --install` oder Python über Homebrew.
|
||||
|
||||
### Browser-Check
|
||||
|
||||
- Chrome oder Edge, aktuelle Version
|
||||
- `chrome://flags` nicht nötig; Web Serial ist standardmässig an
|
||||
- Beim ersten Verbinden erscheint ein Port-Picker — das ist gewollt (Browser-Sicherheitsmodell)
|
||||
|
||||
---
|
||||
|
||||
## 3. Plotter anschliessen
|
||||
|
||||
### Hardware-Reihenfolge
|
||||
|
||||
1. **Strom** an die Steuerung (SKR Mini E3 V3, typisch 24 V). Motoren brauchen die Versorgungsspannung; USB allein reicht nicht zum Fahren.
|
||||
2. **USB** von der Steuerung zum Rechner (Datenkabel).
|
||||
3. Gondel hängt an beiden Riemen, Stift sitzt im Halter, Papier/Wand ist frei.
|
||||
4. Sketch im Browser öffnen (localhost, siehe oben).
|
||||
5. **Connect** klicken oder Taste **C**.
|
||||
6. Im Dialog den Serial-Port der Steuerung wählen.
|
||||
|
||||
### Home setzen (wichtig)
|
||||
|
||||
Marlin kennt nach dem Einschalten **keine** absolute Wand-Position. Du sagst ihm, wo „jetzt“ der Ursprung ist. Die Pfeiltasten funktionieren erst nach dem ersten Home:
|
||||
|
||||
1. Taste **H** — Home an der **aktuellen** Gondelposition (`G92 X0 Y0`).
|
||||
2. Mit den Pfeiltasten (Stift oben) zur **Mitte des Blattes** fahren.
|
||||
3. Nochmals **H** — das setzt den Ursprung auf die Blattmitte.
|
||||
|
||||
Ohne Home bleibt Plot gesperrt.
|
||||
|
||||
### Not-Aus
|
||||
|
||||
Diese Firmware hat keinen `EMERGENCY_PARSER`. Ein Stopp im Browser bricht nur das *weitere Senden* ab; die bereits laufende `G1`-Fahrt läuft weiter. Für einen harten Stopp: **Strom trennen**.
|
||||
|
||||
---
|
||||
|
||||
## 4. Der Plotter und seine Teile
|
||||
|
||||
Der Wandplotter ist ein **Polargraph**: eine Gondel hängt an zwei Riemen, die oben links und rechts über Motoren und Umlenkrollen laufen. Es gibt keine X/Y-Schienen. Position entsteht allein aus den beiden Riemenlängen.
|
||||
|
||||
Baugruppen, Anschlüsse und Geometrie: [hardware.md](hardware.md).
|
||||
|
||||
---
|
||||
|
||||
## 5. Was Marlin mit G-code macht
|
||||
|
||||
### G-code in einem Satz
|
||||
|
||||
G-code ist die Kommandosprache von CNC- und 3D-Druck-Steuerungen: eine Textzeile, ein Befehl. Der Host (hier der Browser) schreibt Zeilen auf die serielle Schnittstelle. Marlin parst sie, plant Bewegung oder I/O, und antwortet mit `ok`, wenn der Befehl angenommen ist.
|
||||
|
||||
Beispiel einer Fahrt mit Stift unten:
|
||||
|
||||
```gcode
|
||||
M280 P0 S30 ; Servo 0 auf 30° → Stift aufs Papier
|
||||
G1 X120 Y-40 F2500 ; Gerade zu (120, −40) mm mit 2500 mm/min
|
||||
M400 ; warten, bis die Bewegung wirklich fertig ist
|
||||
```
|
||||
|
||||
### Der Weg einer Linie
|
||||
|
||||
```
|
||||
p5 sketch
|
||||
wp.draw(x, y) → nur in eine Warteschlange legen
|
||||
wp.runPath(false) → nacheinander senden
|
||||
│
|
||||
▼
|
||||
USB Serial, 115200 Baud
|
||||
│
|
||||
▼
|
||||
Marlin auf der SKR
|
||||
1. Zeile lesen, parsen
|
||||
2. G1: Ziel in mm merken
|
||||
3. inverse Kinematik: (X, Y) → (linke Riemenlänge, rechte Riemenlänge)
|
||||
4. Planner: Beschleunigung, Segmentierung
|
||||
5. Stepper-ISR: X- und Y-Treiber takten
|
||||
6. "ok" zurück an den Browser
|
||||
│
|
||||
▼
|
||||
WallPlotter wartet auf ok, schickt M400, erst dann den nächsten Punkt
|
||||
```
|
||||
|
||||
**Test** (`runPath(true)`) bleibt im Browser: gleiche Pfadliste, nur Animation. Es geht kein G-code raus.
|
||||
|
||||
### Inverse Kinematik (Polargraph)
|
||||
|
||||
Ein kartesischer Drucker fährt Schlitten auf Schienen. Der Polargraph fährt **Riemenlängen**. Marlin rechnet für jedes Ziel:
|
||||
|
||||
```
|
||||
linke Länge = hypot( X − linke_Rolle , Y − Rollenlinie )
|
||||
rechte Länge = hypot( rechte_Rolle − X , Y − Rollenlinie )
|
||||
```
|
||||
|
||||
In der Firmware entspricht das `HYPOT` in `inverse_kinematics`: X-Stepper = linke Länge, Y-Stepper = rechte Länge. Eine waagerechte Linie auf der Wand ist für die Motoren **keine** gleichmässige Rotation — beide Motoren ändern die Länge gekoppelt. Marlin zerlegt die kartesische Gerade intern in kurze Segmente (`DEFAULT_SEGMENTS_PER_SECOND`), damit die Kurve der Riemelängen der Geraden auf der Wand folgt.
|
||||
|
||||
Deshalb muss Home zur realen Geometrie passen. Falsches `G92` (z. B. Ursprung zu weit unter den Rollen) erzeugt den typischen **Bogen nach oben** bzw. eine konische Zeichnung: Marlin glaubt, die Gondel sei woanders, als sie hängt.
|
||||
|
||||
Befehlsliste: [gcode.md](gcode.md).
|
||||
|
||||
### Was Marlin nicht ist
|
||||
|
||||
Marlin ist keine Zeichen-App. Es weiss nichts von p5 oder deinem Sketch. Es bekommt nur Koordinaten und Servo-Winkel. Pfadplanung, Stiftlogik und die Vorschau liegen in `WallPlotter.js`. Die Firmware ist der Bewegungscontroller: parsen, in Riemenlängen umrechnen, Schritte erzeugen, `ok` sagen.
|
||||
|
||||
---
|
||||
|
||||
## 6. Eigenen Sketch schreiben
|
||||
|
||||
Kopiere den Ordner `examples/zickzack/` und öffne darin `sketch.js`. Den Rest (HTML, Buttons, Verbinden) kannst du lassen. Du änderst nur `drawPlot`: dort steht, was gezeichnet wird.
|
||||
|
||||
Koordinaten sind **Millimeter**, Ursprung = Home, **Y nach oben**.
|
||||
|
||||
Fertiges Rechteck: [examples/rechteck/](examples/rechteck/). Im Browser `http://localhost:8080/examples/rechteck/` öffnen. Nach dem Speichern die Seite neu laden (`F5`).
|
||||
|
||||
### Was `sketch.js` macht
|
||||
|
||||
`setup()` läuft einmal beim Start:
|
||||
|
||||
- `createCanvas` — Zeichenfläche im Browser, so gross wie das Fenster
|
||||
- `parent('sketch-host')` — Canvas in die HTML-Seite einhängen
|
||||
- `new WallPlotter({ … })` — Verbindung zum Plotter. `penUpGcode` / `penDownGcode` sind die Servo-Winkel für Stift hoch und runter
|
||||
- `setSpeed(3000, 1500)` — Tempo in mm/min: zuerst leer fahren (Travel), dann zeichnen (Draw)
|
||||
- `enableKeyInputs()` — Tastatur: C, H, Pfeile, …
|
||||
- `enablePlotButtons()` — Buttons Connect, Plot, Test, Abort
|
||||
|
||||
`draw()` läuft dauernd (p5-Schleife): Hintergrund und HUD (`showInfos`).
|
||||
|
||||
`plot()` und `test()` rufen dieselbe Zeichnung auf. `false` = G-code an den Plotter, `true` = nur Vorschau im Browser.
|
||||
|
||||
### Die Zeichnung: `drawPlot`
|
||||
|
||||
```javascript
|
||||
async function drawPlot(testOnly) {
|
||||
const s = 200; // halbe Seitenlänge: 200 mm → Quadrat 400 × 400 mm
|
||||
wp.startPath(); // alte Linie löschen, neue Liste beginnen
|
||||
wp.move(-s, -s); // Stift hoch, zur unteren linken Ecke (−200, −200)
|
||||
wp.draw(s, -s); // Stift runter, nach rechts unten (200, −200)
|
||||
wp.draw(s, s); // nach rechts oben (200, 200)
|
||||
wp.draw(-s, s); // nach links oben (−200, 200)
|
||||
wp.draw(-s, -s); // zurück zur ersten Ecke, Rechteck zu
|
||||
wp.move(0, 0); // Stift hoch, zurück zur Blattmitte
|
||||
await wp.runPath(testOnly); // Liste abarbeiten: Test oder Plot
|
||||
}
|
||||
```
|
||||
|
||||
`move` und `draw` fahren noch nicht. Sie merken sich nur Punkte. `runPath` arbeitet die Liste der Reihe nach ab.
|
||||
|
||||
| Aufruf | Stift | Wohin |
|
||||
|--------|--------|--------|
|
||||
| `wp.move(x, y)` | oben | hinfahren, keine Linie |
|
||||
| `wp.draw(x, y)` | unten | Linie vom letzten Punkt hierhin |
|
||||
| `wp.wait(ms)` | — | Pause |
|
||||
| `wp.startPath()` | — | Liste leeren |
|
||||
| `wp.runPath(true)` | — | Vorschau (Test) |
|
||||
| `wp.runPath(false)` | — | wirklich plotten |
|
||||
|
||||
Mehr Befehle: [lib/doc.md](lib/doc.md).
|
||||
|
||||
---
|
||||
|
||||
## 7. Tasten, Buttons, HUD
|
||||
|
||||
| Taste | Aktion |
|
||||
|-------|--------|
|
||||
| **C** | Verbinden / trennen (Port-Dialog) |
|
||||
| **H** | Hier ist Home (`G92 X0 Y0`) — zuerst an der Gondel, nach dem Joggen nochmals in der Blattmitte |
|
||||
| **T** | Test (Vorschau) |
|
||||
| **A** | Abort: Test auf Start zurück |
|
||||
| **P** | Stift umschalten |
|
||||
| **0** | Nach `(0, 0)` fahren (Pen-up) |
|
||||
| **← → ↑ ↓** | Joggen, Stift oben |
|
||||
| **Shift + Pfeile** | Joggen, Stift unten |
|
||||
| **+** / **-** | Schrittweite 1–100 mm |
|
||||
| **S** | Plot stoppen / pausieren |
|
||||
|
||||
Buttons: **Connect**, **Plot** (wird Pause / Resume), **Test** (wird Stop), **Abort**.
|
||||
|
||||
Das HUD (unten und als Overlay) zeigt Verbindung, Home, Position, Stift, Serial-Log. Das Log rechts unten kannst du markieren — nützlich, wenn Marlin nicht mit `ok` antwortet.
|
||||
|
||||
---
|
||||
|
||||
## 8. Fehlerbehebung
|
||||
|
||||
| Symptom | Was prüfen |
|
||||
|---------|------------|
|
||||
| Kein Port-Dialog, oder „Serial not supported“ | Chrome oder Edge, nicht Firefox/Safari. Seite über `http://localhost:…`, nicht als Datei öffnen. |
|
||||
| Port erscheint nicht | Strom der Steuerung an? Daten-USB-Kabel? Linux: Gruppe `dialout` oder `uucp`, neu einloggen. `ls /dev/ttyACM*`. Windows: Geräte-Manager → COM-Port. macOS: Tab schliessen und neu öffnen (Port oft blockiert). |
|
||||
| Connect, aber keine Bewegung | Netzteil an der SKR einstecken (typisch 24 V). USB allein speist die Motoren nicht. Dann **H** drücken. Linker Motor an Buchse **X**, rechter an **Y**. |
|
||||
| „Home not set“ | Zuerst **H**, dann zur Blattmitte joggen, nochmals **H**.
|
||||
| Stift bleibt oben oder kratzt | `penUpGcode` / `penDownGcode` an den Servo anpassen (`S`-Winkel). Mit **P** testen. |
|
||||
| Zeichnung bogenförmig / konisch | Home falsch: Gondel 80 cm unter den Rollen, dann `G92 X0 Y0`. Rollenabstand 1660 mm. |
|
||||
| Motoren heiss, Schritte verloren | `M906 X1000 Y1000`. Riemen nicht zu straff. Tempo senken. |
|
||||
| Plot hängt bei „waiting for Marlin ok“ | USB wackelt, Board reset, oder lange `G1` ohne `ok`. **Resume** nach Timeout; sonst USB neu stecken und Home neu setzen. |
|
||||
| Stop im Browser, Gondel fährt weiter | Normal: laufendes `G1` wird nicht abgebrochen. Strom weg für Not-Aus. |
|
||||
| macOS verbindet nur einmal | Seite komplett schliessen, Port freigeben, erneut **C**. |
|
||||
| Beispiel sieht alt aus nach einem Update | Browser-Cache: hart neu laden (`Ctrl+Shift+R`) oder `?v=` an `WallPlotter.js` in der HTML erhöhen. |
|
||||
|
||||
Serial-Log im HUD lesen: Marlin-Fehler stehen dort als Text, nicht als Browser-Alert.
|
||||
|
||||
---
|
||||
|
||||
## 9. Mini-Ablauf zum Merken
|
||||
|
||||
1. ZIP herunterladen und entpacken.
|
||||
2. `python3 -m http.server 8080` (Windows: `py -m http.server 8080`).
|
||||
3. Chrome/Edge: `http://localhost:8080/examples/zickzack/`.
|
||||
4. Plotter mit Strom und USB verbinden.
|
||||
5. **C** → Port wählen.
|
||||
6. **H** (aktuelle Position), zur Blattmitte joggen, nochmals **H**.
|
||||
7. **Test**, dann **Plot**.
|
||||
|
||||
API-Referenz: [lib/doc.md](lib/doc.md). G-code: [gcode.md](gcode.md).
|
||||
Reference in New Issue
Block a user