@thi.ng/axidraw 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2022-12-06T17:16:38Z
3
+ - **Last updated**: 2022-12-16T12:52:25Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,35 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/axidraw@0.2.0) (2022-12-10)
13
+
14
+ #### 🚀 Features
15
+
16
+ - major updates/additions ([eb41c28](https://github.com/thi-ng/umbrella/commit/eb41c28))
17
+ - extract polyline() as standalone fn
18
+ - add complete() syntax sugar
19
+ - update UP/DOWN commands to accept opt. pen level/position
20
+ - add RESET command
21
+ - extract various draw commands into separate methods, simplify draw()
22
+ - update draw() w/ FSM to pause/resume/cancel processing
23
+ - add AxiDrawState FSM enum
24
+ - add AxiDrawControl class, use as default controller
25
+ - update AxiDrawOpts w/ new options
26
+ - update connect() to throw error if unsuccessful
27
+ - add SIGINT signal handler to handle Ctrl+C
28
+ - update .draw() to auto-wrap command seq ([60aaad2](https://github.com/thi-ng/umbrella/commit/60aaad2))
29
+ - add PolylineOpts, update polyline() ([c8a271f](https://github.com/thi-ng/umbrella/commit/c8a271f))
30
+
31
+ #### 🩹 Bug fixes
32
+
33
+ - fix polyline(), only apply custom speed for drawing ([c43b6f5](https://github.com/thi-ng/umbrella/commit/c43b6f5))
34
+ - update draw calls to disable cmd wrapping ([4cd5e53](https://github.com/thi-ng/umbrella/commit/4cd5e53))
35
+ - fix waiting for start/stop/home commands ([42bf4eb](https://github.com/thi-ng/umbrella/commit/42bf4eb))
36
+
37
+ #### ⏱ Performance improvements
38
+
39
+ - remove obsolete UP command (and delay) in polyline() ([f71c64b](https://github.com/thi-ng/umbrella/commit/f71c64b))
40
+
12
41
  ## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/axidraw@0.1.0) (2022-12-06)
13
42
 
14
43
  #### 🚀 Features
package/README.md CHANGED
@@ -1,35 +1,43 @@
1
1
  <!-- This file is generated - DO NOT EDIT! -->
2
2
 
3
- # ![axidraw](https://media.thi.ng/umbrella/banners-20220914/thing-axidraw.svg?25c834ce)
3
+ # ![@thi.ng/axidraw](https://media.thi.ng/umbrella/banners-20220914/thing-axidraw.svg?25c834ce)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@thi.ng/axidraw.svg)](https://www.npmjs.com/package/@thi.ng/axidraw)
6
6
  ![npm downloads](https://img.shields.io/npm/dm/@thi.ng/axidraw.svg)
7
- [![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella)
7
+ [![Mastodon Follow](https://img.shields.io/mastodon/follow/109331703950160316?domain=https%3A%2F%2Fmastodon.thi.ng&style=social)](https://mastodon.thi.ng/@toxi)
8
8
 
9
9
  This project is part of the
10
10
  [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo.
11
11
 
12
12
  - [About](#about)
13
13
  - [Declarative vs. imperative](#declarative-vs-imperative)
14
- - [No SVG support](#no-svg-support)
14
+ - [Units, limits & clipping](#units-limits--clipping)
15
+ - [Path planning](#path-planning)
16
+ - [thi.ng/geom support](#thinggeom-support)
17
+ - [SVG support](#svg-support)
15
18
  - [Serial port support](#serial-port-support)
19
+ - [Draw control](#draw-control)
16
20
  - [Status](#status)
17
21
  - [Installation](#installation)
18
22
  - [Dependencies](#dependencies)
19
23
  - [API](#api)
24
+ - [Example usage](#example-usage)
25
+ - [Basics](#basics)
26
+ - [geom-axidraw example](#geom-axidraw-example)
20
27
  - [Authors](#authors)
21
28
  - [License](#license)
22
29
 
23
30
  ## About
24
31
 
25
- Minimal AxiDraw plotter/drawing machine controller for Node.js.
32
+ Minimal AxiDraw plotter/drawing machine controller for Node.js
26
33
 
27
34
  This package provides a super-lightweight alternative to control an [AxiDraw
28
35
  plotter](https://axidraw.com/) directly from Node.js, using a small custom set
29
36
  of medium/high-level drawing commands. Structurally, these custom commands are
30
- [thi.ng/hiccup-like](https://github.com/thi-ng/umbrella/blob/develop/packages/hiccup/)
37
+ [thi.ng/hiccup](https://github.com/thi-ng/umbrella/blob/develop/packages/hiccup/)-like
31
38
  S-expressions, which can be easily serialized to/from JSON and are translated to
32
- [EBB commands](https://evil-mad.github.io/EggBot/ebb.html) for the plotter.
39
+ the native [EBB commands](https://evil-mad.github.io/EggBot/ebb.html) for the
40
+ plotter.
33
41
 
34
42
  ### Declarative vs. imperative
35
43
 
@@ -42,28 +50,84 @@ following the pattern of other packages in the
42
50
  until the very last moment before being sent to the machine for physical
43
51
  output...
44
52
 
45
- ### No SVG support
46
-
47
- This package does **not** provide conversion from SVG or any other geometry
48
- format conversions. Whilst not containing a full SVG parser (only single paths),
49
- the family of
53
+ ### Units, limits & clipping
54
+
55
+ This package performs **no bounds checking nor clipping** and expects all given
56
+ coordinates to be valid and within machine limits. Coordinates can be given in
57
+ any unit, but if not using millimeters (default), a conversion factor to inches
58
+ (`unitsPerInch`) **MUST** be provided as part of the [options
59
+ object](https://docs.thi.ng/umbrella/axidraw/interfaces/AxiDrawOpts.html) given
60
+ to the `AxiDraw` constructor. Clipping can be handled by the geom or
61
+ geom-axidraw packages (see below)...
62
+
63
+ ### Path planning
64
+
65
+ Path planning is considered a higher level operation than what's addressed by
66
+ this package and is therefore out of scope. The
67
+ [thi.ng/geom-axidraw](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-axidraw)
68
+ provides some configurable point & shape sorting functions, but this is an
69
+ interim solution and a full path/route planning facility is currently still
70
+ outstanding and awaiting to be ported from other projects.
71
+
72
+ ### thi.ng/geom support
73
+
74
+ The [thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom)
75
+ package provides numerous shape types & operations to generate & transform
76
+ geometry. Additionally,
77
+ [thi.ng/geom-axidraw](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-axidraw)
78
+ can act as bridge API and provides the polymorphic
79
+ [`asAxiDraw()`](https://docs.thi.ng/umbrella/geom-axidraw/functions/asAxiDraw.html)
80
+ function to convert single shapes or entire shape groups/hierarchies directly
81
+ into the draw commands used by this (axidraw) package. See package readme for
82
+ more details and examples.
83
+
84
+ ### SVG support
85
+
86
+ This package does **not** provide any direct conversions from SVG or any other
87
+ geometry format. But again, whilst not containing a full SVG parser (at current
88
+ only single paths can be parsed), the family of
50
89
  [thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom)
51
- packages provides numerous other shape types & operations which can be directly
90
+ packages provides numerous shape types & operations which can be directly
52
91
  utilized to output generated geometry together with this package...
53
92
 
54
- The only built-in conversion provided is the [`AxiDraw.polyline()`]() method to
55
- convert an array of points (representing a polyline) into an array of drawing
56
- commands. All other conversions are out of scope for this package (& for now).
93
+ The only built-in conversion provided here is the
94
+ [`polyline()`](https://docs.thi.ng/umbrella/axidraw/functions/polyline.html)
95
+ utility function to convert an array of points (representing a polyline) to an
96
+ array of drawing commands (with various config options). All other conversions
97
+ are out of scope for this package (& for now).
57
98
 
58
99
  ### Serial port support
59
100
 
60
101
  We're using the [serialport](https://serialport.io/) NPM package to submit data
61
- directly to the drawing machine. That pacakge includes native bindings for
102
+ directly to the drawing machine. That package includes native bindings for
62
103
  Linux, MacOS and Windows.
63
104
 
64
- The `AxiDraw.connect()` function (see example below) attempts to find the
65
- drawing machine by matching a given regexp with available port names. The
66
- default regexp might only work on Mac, but YMMV!
105
+ The
106
+ [`AxiDraw.connect()`](https://docs.thi.ng/umbrella/axidraw/classes/AxiDraw.html#connect)
107
+ function (see example below) attempts to find the drawing machine by matching a
108
+ given regexp with available port names. The default regexp might only work on
109
+ Mac, but YMMV!
110
+
111
+ At some point it would also be worth looking into
112
+ [WebSerial](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API)
113
+ support to enable plotting directly from the browser. Right now this package is
114
+ only aimed at Node.js though...
115
+
116
+ ### Draw control
117
+
118
+ The main draw function provided by this package is async and can supports custom
119
+ implementations to pause, resume or cancel the processing of further drawing
120
+ commands. By the default
121
+ [`AxiDrawControl`](https://docs.thi.ng/umbrella/axidraw/classes/AxiDrawControl.html)
122
+ is used as default implementation.
123
+
124
+ If a control is provided, it will be checked prior to processing each individual
125
+ command. Drawing will be paused if the control state is in paused state and the
126
+ control will be rechecked every N milliseconds for updates (configurable). In
127
+ paused state, the pen will be automatically lifted (if it wasn't already) and
128
+ when resuming it will be sent down again (if it was originally down). Draw
129
+ commands are only sent to the machine if no control is provided at all or if the
130
+ control is in the "continue" state.
67
131
 
68
132
  ## Status
69
133
 
@@ -87,14 +151,11 @@ ES module import:
87
151
 
88
152
  For Node.js REPL:
89
153
 
90
- ```text
91
- # with flag only for < v16
92
- node --experimental-repl-await
93
-
94
- > const axidraw = await import("@thi.ng/axidraw");
154
+ ```js
155
+ const axidraw = await import("@thi.ng/axidraw");
95
156
  ```
96
157
 
97
- Package sizes (brotli'd, pre-treeshake): ESM: 1.01 KB
158
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.67 KB
98
159
 
99
160
  ## Dependencies
100
161
 
@@ -109,50 +170,90 @@ Package sizes (brotli'd, pre-treeshake): ESM: 1.01 KB
109
170
 
110
171
  [Generated API docs](https://docs.thi.ng/umbrella/axidraw/)
111
172
 
112
- ```ts tangle:export/readme.js
113
- import { AxiDraw } from "@thi.ng/axidraw";
114
- import { circle, vertices } from "@thi.ng/geom";
173
+ ### Example usage
174
+
175
+ #### Basics
176
+
177
+ ```js tangle:export/readme-basic.js
178
+ import { AxiDraw, polyline } from "@thi.ng/axidraw";
115
179
 
116
180
  (async () => {
117
181
 
118
182
  // instantiate w/ default options (see docs for info)
119
- // default paper size is DIN A4
120
183
  const axi = new AxiDraw();
121
184
 
122
- // connect to 1st serial port matching given regexp
123
- await axi.connect(/^\/dev\/tty\.usbmodem/);
185
+ // connect to 1st serial port matching given pre-string or regexp
186
+ // (the port used here is the default arg)
187
+ await axi.connect("/dev/tty.usbmodem");
124
188
  // true
125
189
 
126
- // compute 60 points on a circle at (100,50) w/ radius 30
127
- // (all units in mm)
128
- const verts = vertices(circle([100, 50], 30), { num: 60, last: true });
129
- // [
130
- // [ 130, 50 ],
131
- // [ 129.8356568610482, 53.1358538980296 ],
132
- // [ 129.34442802201417, 56.23735072453278 ],
133
- // ...
134
- // ]
190
+ // vertices defining a polyline of a 100x100 mm square (top left at 20,20)
191
+ const verts = [[20, 20], [120, 20], [120, 120], [20, 120], [20, 20]];
135
192
 
136
- // convert to drawing commands (w/ default opts)
137
- const path = axi.polyline(verts)
193
+ // convert to drawing commands (w/ custom speed, 25%)
194
+ // see docs for config options
195
+ const path = polyline(verts, { speed: 0.25 })
138
196
  // [
139
- // [ 'u' ],
140
- // [ 'm', [ 130, 50 ], 1 ],
141
- // [ 'd' ],
142
- // [ 'm', [ 129.8356568610482, 53.1358538980296 ], 1 ],
143
- // [ 'm', [ 129.34442802201417, 56.23735072453278 ], 1 ],
144
- // ...
197
+ // ["m", [20, 20]],
198
+ // ["d"],
199
+ // ["m", [120, 20], 0.25],
200
+ // ["m", [120, 120], 0.25],
201
+ // ["m", [20, 120], 0.25],
202
+ // ["m", [20, 20], 0.25],
203
+ // ["u"]
145
204
  // ]
146
205
 
147
- // draw/send seq of commands (incl. start/end sequence, configurable)
148
- await axi.draw([["start"], ...path, ["stop"]]);
206
+ // draw/send seq of commands
207
+ // by default the given commands will be wrapped with a start/end
208
+ // command sequence, configurable via options given to AxiDraw ctor)...
209
+ await axi.draw(path);
149
210
 
150
211
  })();
151
212
  ```
152
213
 
214
+ ### geom-axidraw example
215
+
216
+ Result shown here: https://mastodon.thi.ng/@toxi/109473655772673067
217
+
218
+ ```js tangle:export/readme-geom.js
219
+ import { AxiDraw } from "@thi.ng/axidraw";
220
+ import { asCubic, group, pathFromCubics, star } from "@thi.ng/geom";
221
+ import { asAxiDraw } from "@thi.ng/geom-axidraw";
222
+ import { map, range } from "@thi.ng/transducers";
223
+
224
+ (async () => {
225
+ // create group of bezier-interpolated star polygons,
226
+ // with each path using a slightly different configuration
227
+ const geo = group({ translate: [100, 100] }, [
228
+ ...map(
229
+ (t) =>
230
+ pathFromCubics(
231
+ asCubic(star(90, 6, [t, 1]), {
232
+ breakPoints: true,
233
+ scale: 0.66,
234
+ })
235
+ ),
236
+ range(0.3, 1.01, 0.05)
237
+ ),
238
+ ]);
239
+
240
+ // connect to plotter
241
+ const axi = new AxiDraw();
242
+ await axi.connect();
243
+ // convert geometry to drawing commands & send to plotter
244
+ await axi.draw(asAxiDraw(geo, { samples: 40 }));
245
+ })();
246
+ ```
247
+
248
+ Other selected toots/tweets:
249
+
250
+ - https://mastodon.thi.ng/@toxi/109474947869078797
251
+ - https://mastodon.thi.ng/@toxi/109483553358349473
252
+ - more to come...
253
+
153
254
  ## Authors
154
255
 
155
- Karsten Schmidt
256
+ - [Karsten Schmidt](https://thi.ng)
156
257
 
157
258
  If this project contributes to an academic publication, please cite it as:
158
259
 
@@ -167,4 +268,4 @@ If this project contributes to an academic publication, please cite it as:
167
268
 
168
269
  ## License
169
270
 
170
- &copy; 2022 Karsten Schmidt // Apache Software License 2.0
271
+ &copy; 2022 Karsten Schmidt // Apache License 2.0
package/api.d.ts CHANGED
@@ -1,28 +1,35 @@
1
+ import type { IDeref } from "@thi.ng/api";
1
2
  import type { ILogger } from "@thi.ng/logger";
2
3
  import type { ReadonlyVec } from "@thi.ng/vectors";
3
4
  /** Start command sequence (configurable via {@link AxiDrawOpts}) */
4
- export declare type StartCommand = ["start"];
5
+ export type StartCommand = ["start"];
5
6
  /** Stop command sequence (configurable via {@link AxiDrawOpts}) */
6
- export declare type StopCommand = ["stop"];
7
+ export type StopCommand = ["stop"];
7
8
  /** Return plotter to initial XY position */
8
- export declare type HomeCommand = ["home"];
9
+ export type HomeCommand = ["home"];
10
+ /** Reset curr position as home (0,0) */
11
+ export type ResetCommand = ["reset"];
9
12
  /** Turn XY motors on/off */
10
- export declare type MotorCommand = ["on" | "off"];
13
+ export type MotorCommand = ["on" | "off"];
11
14
  /** Pen config, min/down position, max/up position (in %) */
12
- export declare type PenConfigCommand = ["pen", number?, number?];
15
+ export type PenConfigCommand = ["pen", number?, number?];
13
16
  /**
14
- * Pen up/down, optional delay (in ms), if omitted values used from
15
- * {@link AxiDrawOpts}.
17
+ * Pen up/down, optional delay (in ms), optional custom level/position. If
18
+ * omitted, default values used from {@link AxiDrawOpts}. Using -1 as delay also
19
+ * uses default.
16
20
  */
17
- export declare type PenUpDownCommand = ["u" | "d", number?];
21
+ export type PenUpDownCommand = ["u" | "d", number?, number?];
18
22
  /**
19
23
  * Move to abs pos (in worldspace coords, default mm), optional speed factor
20
24
  * (default: 1)
21
25
  */
22
- export declare type MoveXYCommand = ["m", ReadonlyVec, number?];
26
+ export type MoveXYCommand = ["m", ReadonlyVec, number?];
23
27
  /** Explicit delay (in ms) */
24
- export declare type WaitCommand = ["w", number];
25
- export declare type DrawCommand = StartCommand | StopCommand | HomeCommand | MotorCommand | PenConfigCommand | PenUpDownCommand | MoveXYCommand | WaitCommand;
28
+ export type WaitCommand = ["w", number];
29
+ export type DrawCommand = StartCommand | StopCommand | HomeCommand | ResetCommand | MotorCommand | PenConfigCommand | PenUpDownCommand | MoveXYCommand | WaitCommand;
30
+ /**
31
+ * Global plotter drawing configuration. Also see {@link DEFAULT_OPTS}.
32
+ */
26
33
  export interface AxiDrawOpts {
27
34
  /**
28
35
  * Conversion factor from geometry worldspace units to inches.
@@ -58,13 +65,13 @@ export interface AxiDrawOpts {
58
65
  /**
59
66
  * Delay after pen up
60
67
  *
61
- * @defaultValue 300
68
+ * @defaultValue 150
62
69
  */
63
70
  delayUp: number;
64
71
  /**
65
72
  * Delay after pen down
66
73
  *
67
- * @defaultValue 300
74
+ * @defaultValue 150
68
75
  */
69
76
  delayDown: number;
70
77
  /**
@@ -89,13 +96,92 @@ export interface AxiDrawOpts {
89
96
  * Logger instance
90
97
  */
91
98
  logger: ILogger;
99
+ /**
100
+ * Optional implementation to pause, resume or cancel the processing of
101
+ * drawing commands (see {@link AxiDrawControl} for default impl).
102
+ *
103
+ * @remarks
104
+ * If a control is provided, it will be checked prior to processing each
105
+ * individual command. Drawing will be paused if the control state is in
106
+ * {@link AxiDrawState.PAUSE} state and the control will be rechecked every
107
+ * {@link AxiDrawOpts.refresh} milliseconds for updates. In paused state,
108
+ * the pen will be automatically lifted (if it wasn't already) and when
109
+ * resuming it will be sent down again (if it was originally down).
110
+ *
111
+ * Draw commands are only sent to the machine if no control is provided at
112
+ * all or if the control is in the {@link AxiDrawState.CONTINUE} state.
113
+ */
114
+ control?: IDeref<AxiDrawState>;
115
+ /**
116
+ * Refresh interval for checking the control FSM in paused state.
117
+ *
118
+ * @defaultValue 1000
119
+ */
120
+ refresh: number;
121
+ /**
122
+ * If true (default), installs SIGINT handler to lift pen when the Node.js
123
+ * process is terminated.
124
+ */
125
+ sigint: boolean;
92
126
  }
93
127
  export declare const START: StartCommand;
94
128
  export declare const STOP: StopCommand;
95
129
  export declare const HOME: HomeCommand;
130
+ export declare const RESET: ResetCommand;
96
131
  export declare const PEN: PenConfigCommand;
97
132
  export declare const UP: PenUpDownCommand;
98
133
  export declare const DOWN: PenUpDownCommand;
99
134
  export declare const ON: MotorCommand;
100
135
  export declare const OFF: MotorCommand;
136
+ /**
137
+ * FSM state enum for (interactive) control for processing of drawing commands.
138
+ * See {@link AxiDraw.draw} and {@link AxiDrawControl} for details.
139
+ */
140
+ export declare enum AxiDrawState {
141
+ /**
142
+ * Draw command processing can continue as normal.
143
+ */
144
+ CONTINUE = 0,
145
+ /**
146
+ * Draw command processing is suspended indefinitely.
147
+ */
148
+ PAUSE = 1,
149
+ /**
150
+ * Draw command processing is cancelled.
151
+ */
152
+ CANCEL = 2
153
+ }
154
+ /**
155
+ * Drawing behavior options for a single polyline.
156
+ */
157
+ export interface PolylineOpts {
158
+ /**
159
+ * Speed factor (multiple of globally configured draw speed). Depending on
160
+ * pen used, slower speeds might result in thicker strokes.
161
+ *
162
+ * @defaultValue 1
163
+ */
164
+ speed: number;
165
+ /**
166
+ * Pen down (Z) position (%) for this particular shape/polyline. Will be
167
+ * reset to globally configured default at the end of the shape.
168
+ */
169
+ down: number;
170
+ /**
171
+ * Delay for pen down command at the start of this particular
172
+ * shape/polyline.
173
+ */
174
+ delayDown: number;
175
+ /**
176
+ * Delay for pen up command at the end this particular shape/polyline.
177
+ */
178
+ delayUp: number;
179
+ /**
180
+ * If enabled, no pen up/down commands will be included.
181
+ * {@link PolylineOpts.speed} is the only other option considered then.
182
+ *
183
+ * @defaultValue false
184
+ */
185
+ onlyGeo: boolean;
186
+ }
101
187
  //# sourceMappingURL=api.d.ts.map
package/api.js CHANGED
@@ -1,8 +1,28 @@
1
1
  export const START = ["start"];
2
2
  export const STOP = ["stop"];
3
3
  export const HOME = ["home"];
4
+ export const RESET = ["reset"];
4
5
  export const PEN = ["pen"];
5
6
  export const UP = ["u"];
6
7
  export const DOWN = ["d"];
7
8
  export const ON = ["on"];
8
9
  export const OFF = ["off"];
10
+ /**
11
+ * FSM state enum for (interactive) control for processing of drawing commands.
12
+ * See {@link AxiDraw.draw} and {@link AxiDrawControl} for details.
13
+ */
14
+ export var AxiDrawState;
15
+ (function (AxiDrawState) {
16
+ /**
17
+ * Draw command processing can continue as normal.
18
+ */
19
+ AxiDrawState[AxiDrawState["CONTINUE"] = 0] = "CONTINUE";
20
+ /**
21
+ * Draw command processing is suspended indefinitely.
22
+ */
23
+ AxiDrawState[AxiDrawState["PAUSE"] = 1] = "PAUSE";
24
+ /**
25
+ * Draw command processing is cancelled.
26
+ */
27
+ AxiDrawState[AxiDrawState["CANCEL"] = 2] = "CANCEL";
28
+ })(AxiDrawState || (AxiDrawState = {}));
package/axidraw.d.ts CHANGED
@@ -1,33 +1,47 @@
1
- import type { Fn0 } from "@thi.ng/api";
1
+ import type { IReset } from "@thi.ng/api";
2
2
  import { ReadonlyVec, Vec } from "@thi.ng/vectors";
3
3
  import { SerialPort } from "serialport";
4
4
  import { AxiDrawOpts, DrawCommand } from "./api.js";
5
5
  export declare const DEFAULT_OPTS: AxiDrawOpts;
6
- export declare class AxiDraw {
6
+ export declare class AxiDraw implements IReset {
7
7
  serial: SerialPort;
8
8
  opts: AxiDrawOpts;
9
9
  isConnected: boolean;
10
+ isPenDown: boolean;
11
+ penLimits: [number, number];
10
12
  pos: Vec;
13
+ targetPos: Vec;
11
14
  constructor(opts?: Partial<AxiDrawOpts>);
15
+ reset(): this;
12
16
  /**
13
17
  * Async function. Attempts to connect to the drawing machine via given
14
18
  * (partial) serial port path/name, returns true if successful.
15
19
  *
20
+ * @remarks
21
+ * First matching port will be used. If `path` is a sting, a port name must
22
+ * only start with it in order to be considered a match.
23
+ *
24
+ * An error is thrown if no matching port could be found.
25
+ *
16
26
  * @param path
17
27
  */
18
- connect(path?: RegExp): Promise<boolean>;
28
+ connect(path?: string | RegExp): Promise<void>;
19
29
  /**
20
30
  * Async function. Converts sequence of {@link DrawCommand}s into actual EBB
21
- * commands and sends them via configured serial port to the AxiDraw. The
22
- * optional `cancel` predicate is checked prior to each individual command
23
- * and processing is stopped if that function returns a truthy result.
24
- *
25
- * Returns number of milliseconds taken for drawing.
31
+ * commands and sends them via configured serial port to the AxiDraw. If
32
+ * `wrap` is enabled (default), the given commands will be automatically
33
+ * wrapped with start/stop commands via {@link complete}. Returns total
34
+ * number of milliseconds taken for drawing (incl. any pauses caused by the
35
+ * control).
26
36
  *
27
37
  * @remarks
28
38
  * This function is async and if using `await` will only return once all
29
39
  * commands have been processed or cancelled.
30
40
  *
41
+ * The `control` implementation/ provided as part of {@link AxiDrawOpts} can
42
+ * be used to pause, resume or cancel the drawing (see
43
+ * {@link AxiDrawOpts.control} for details).
44
+ *
31
45
  * Reference:
32
46
  * - http://evil-mad.github.io/EggBot/ebb.html
33
47
  *
@@ -42,23 +56,35 @@ export declare class AxiDraw {
42
56
  * ```
43
57
  *
44
58
  * @param commands
45
- * @param cancel
59
+ * @param wrap
46
60
  */
47
- draw(commands: Iterable<DrawCommand>, cancel?: Fn0<boolean>): Promise<number>;
61
+ draw(commands: Iterable<DrawCommand>, wrap?: boolean): Promise<number>;
48
62
  /**
49
- * Takes an array of 2D points and converts them into an array of
50
- * {@link DrawCommand}s. The optional `speed` factor can be used to control
51
- * draw speed (default: 1).
63
+ * Syntax sugar for drawing a **single** command only, otherwise same as
64
+ * {@link AxiDraw.draw}.
65
+ *
66
+ * @param cmd
67
+ */
68
+ draw1(cmd: DrawCommand): Promise<number>;
69
+ motorsOn(): void;
70
+ motorsOff(): void;
71
+ penConfig(down?: number, up?: number): void;
72
+ penUp(delay?: number, level?: number): number;
73
+ penDown(delay?: number, level?: number): number;
74
+ moveTo(p: ReadonlyVec, tempo?: number): number;
75
+ home(): number;
76
+ protected onSignal(): Promise<void>;
77
+ protected send(msg: string): void;
78
+ /**
79
+ * Sends pen up/down config
52
80
  *
53
81
  * @remarks
54
- * Unless `onlyGeo` is explicitly enabled, the resulting command sequence
55
- * will also contain necessary pen up/down commands.
82
+ * Reference:
83
+ * - https://github.com/evil-mad/AxiDraw-Processing/blob/80d81a8c897b8a1872b0555af52a8d1b5b13cec4/AxiGen1/AxiGen1.pde#L213
56
84
  *
57
- * @param pts
58
- * @param speed
59
- * @param onlyGeo
85
+ * @param id
86
+ * @param x
60
87
  */
61
- polyline(pts: ReadonlyVec[], speed?: number, onlyGeo?: boolean): DrawCommand[];
62
- protected send(msg: string): void;
88
+ protected sendPenConfig(id: number, x: number): void;
63
89
  }
64
90
  //# sourceMappingURL=axidraw.d.ts.map
package/axidraw.js CHANGED
@@ -1,60 +1,90 @@
1
+ import { isString } from "@thi.ng/checks";
1
2
  import { delayed } from "@thi.ng/compose";
2
- import { assert, unsupported } from "@thi.ng/errors";
3
+ import { assert, ioerror, unsupported } from "@thi.ng/errors";
3
4
  import { ConsoleLogger } from "@thi.ng/logger";
4
- import { abs2, mulN2, set2, sub2 } from "@thi.ng/vectors";
5
+ import { abs2, mulN2, set2, sub2, zero, ZERO2, } from "@thi.ng/vectors";
5
6
  import { SerialPort } from "serialport";
6
- import { DOWN, HOME, OFF, ON, PEN, UP, } from "./api.js";
7
+ import { AxiDrawState, HOME, OFF, ON, PEN, UP, } from "./api.js";
8
+ import { AxiDrawControl } from "./control.js";
9
+ import { complete } from "./polyline.js";
7
10
  export const DEFAULT_OPTS = {
8
11
  logger: new ConsoleLogger("axidraw"),
12
+ control: new AxiDrawControl(),
13
+ refresh: 1000,
9
14
  unitsPerInch: 25.4,
10
15
  stepsPerInch: 2032,
11
16
  speed: 4000,
12
17
  up: 60,
13
18
  down: 30,
14
- delayUp: 300,
15
- delayDown: 300,
19
+ delayUp: 150,
20
+ delayDown: 150,
16
21
  preDelay: 0,
17
22
  start: [ON, PEN, UP],
18
23
  stop: [UP, HOME, OFF],
24
+ sigint: true,
19
25
  };
20
26
  export class AxiDraw {
21
27
  constructor(opts = {}) {
22
28
  this.isConnected = false;
29
+ this.isPenDown = false;
23
30
  this.pos = [0, 0];
31
+ this.targetPos = [0, 0];
24
32
  this.opts = { ...DEFAULT_OPTS, ...opts };
33
+ this.penLimits = [this.opts.down, this.opts.up];
34
+ }
35
+ reset() {
36
+ zero(this.pos);
37
+ zero(this.targetPos);
38
+ return this;
25
39
  }
26
40
  /**
27
41
  * Async function. Attempts to connect to the drawing machine via given
28
42
  * (partial) serial port path/name, returns true if successful.
29
43
  *
44
+ * @remarks
45
+ * First matching port will be used. If `path` is a sting, a port name must
46
+ * only start with it in order to be considered a match.
47
+ *
48
+ * An error is thrown if no matching port could be found.
49
+ *
30
50
  * @param path
31
51
  */
32
- async connect(path = /^\/dev\/tty\.usbmodem/) {
52
+ async connect(path = "/dev/tty.usbmodem") {
53
+ const isStr = isString(path);
33
54
  for (let port of await SerialPort.list()) {
34
- if (path.test(port.path)) {
55
+ if ((isStr && port.path.startsWith(path)) ||
56
+ (!isStr && path.test(port.path))) {
35
57
  this.opts.logger.info(`using device: ${port.path}...`);
36
58
  this.serial = new SerialPort({
37
59
  path: port.path,
38
60
  baudRate: 38400,
39
61
  });
40
62
  this.isConnected = true;
41
- return true;
63
+ if (this.opts.sigint) {
64
+ this.opts.logger.debug("installing signal handler...");
65
+ process.on("SIGINT", this.onSignal.bind(this));
66
+ }
67
+ return;
42
68
  }
43
69
  }
44
- return false;
70
+ ioerror(`no matching device for ${path}`);
45
71
  }
46
72
  /**
47
73
  * Async function. Converts sequence of {@link DrawCommand}s into actual EBB
48
- * commands and sends them via configured serial port to the AxiDraw. The
49
- * optional `cancel` predicate is checked prior to each individual command
50
- * and processing is stopped if that function returns a truthy result.
51
- *
52
- * Returns number of milliseconds taken for drawing.
74
+ * commands and sends them via configured serial port to the AxiDraw. If
75
+ * `wrap` is enabled (default), the given commands will be automatically
76
+ * wrapped with start/stop commands via {@link complete}. Returns total
77
+ * number of milliseconds taken for drawing (incl. any pauses caused by the
78
+ * control).
53
79
  *
54
80
  * @remarks
55
81
  * This function is async and if using `await` will only return once all
56
82
  * commands have been processed or cancelled.
57
83
  *
84
+ * The `control` implementation/ provided as part of {@link AxiDrawOpts} can
85
+ * be used to pause, resume or cancel the drawing (see
86
+ * {@link AxiDrawOpts.control} for details).
87
+ *
58
88
  * Reference:
59
89
  * - http://evil-mad.github.io/EggBot/ebb.html
60
90
  *
@@ -69,103 +99,152 @@ export class AxiDraw {
69
99
  * ```
70
100
  *
71
101
  * @param commands
72
- * @param cancel
102
+ * @param wrap
73
103
  */
74
- async draw(commands, cancel) {
104
+ async draw(commands, wrap = true) {
75
105
  assert(this.isConnected, "AxiDraw not yet connected, need to call .connect() first");
76
106
  let t0 = Date.now();
77
- if (!cancel)
78
- cancel = () => false;
79
- const { opts: config, pos } = this;
80
- const { stepsPerInch, unitsPerInch, speed, preDelay } = config;
81
- // scale factor: worldspace units -> motor steps
82
- const scale = stepsPerInch / unitsPerInch;
83
- let targetPos = [0, 0];
84
- let delta = [0, 0];
85
- for (let $cmd of commands) {
86
- if (cancel())
87
- break;
107
+ const { control, logger, preDelay, refresh } = this.opts;
108
+ for (let $cmd of wrap ? complete(commands) : commands) {
109
+ if (control) {
110
+ let state = control.deref();
111
+ if (state === AxiDrawState.PAUSE) {
112
+ const penDown = this.isPenDown;
113
+ if (penDown)
114
+ this.penUp();
115
+ do {
116
+ await delayed(0, refresh);
117
+ } while ((state = control.deref()) === AxiDrawState.PAUSE);
118
+ if (state === AxiDrawState.CONTINUE && penDown) {
119
+ this.penDown();
120
+ }
121
+ }
122
+ if (state === AxiDrawState.CANCEL) {
123
+ this.penUp();
124
+ break;
125
+ }
126
+ }
88
127
  const [cmd, a, b] = $cmd;
89
128
  let wait = -1;
90
129
  switch (cmd) {
91
130
  case "start":
92
131
  case "stop":
93
- this.draw(config[cmd], cancel);
132
+ await this.draw(this.opts[cmd], false);
94
133
  break;
95
134
  case "home":
96
- this.draw([["m", [0, 0]]], cancel);
135
+ wait = this.home();
136
+ break;
137
+ case "reset":
138
+ this.reset();
97
139
  break;
98
140
  case "on":
99
- this.send("EM,1,1\r");
141
+ this.motorsOn();
100
142
  break;
101
143
  case "off":
102
- this.send("EM,0,0\r");
144
+ this.motorsOff();
103
145
  break;
104
146
  case "pen":
105
- {
106
- let val = a !== undefined ? a : config.down;
107
- // unit ref:
108
- // https://github.com/evil-mad/AxiDraw-Processing/blob/80d81a8c897b8a1872b0555af52a8d1b5b13cec4/AxiGen1/AxiGen1.pde#L213
109
- this.send(`SC,5,${(7500 + 175 * val) | 0}\r`);
110
- val = b !== undefined ? b : config.up;
111
- this.send(`SC,4,${(7500 + 175 * val) | 0}\r`);
112
- this.send(`SC,10,65535\r`);
113
- }
147
+ this.penConfig(a, b);
114
148
  break;
115
149
  case "u":
116
- wait = a !== undefined ? a : config.delayUp;
117
- this.send(`SP,1,${wait}\r`);
150
+ wait = this.penUp(a, b);
118
151
  break;
119
152
  case "d":
120
- wait = a !== undefined ? a : config.delayDown;
121
- this.send(`SP,0,${wait}\r`);
153
+ wait = this.penDown(a, b);
122
154
  break;
123
155
  case "w":
124
156
  wait = a;
125
157
  break;
126
158
  case "m":
127
- {
128
- mulN2(targetPos, a, scale);
129
- sub2(delta, targetPos, pos);
130
- set2(pos, targetPos);
131
- config.logger.info("target", targetPos, "delta", delta);
132
- const maxAxis = Math.max(...abs2([], delta));
133
- wait = (1000 * maxAxis) / (speed * (b || 1));
134
- this.send(`XM,${wait | 0},${delta[0] | 0},${delta[1] | 0}\r`);
135
- }
159
+ wait = this.moveTo(a, b);
136
160
  break;
137
161
  default:
138
162
  unsupported(`unknown command: ${$cmd}`);
139
163
  }
140
164
  if (wait > 0) {
141
165
  wait = Math.max(0, wait - preDelay);
142
- config.logger.debug(`waiting ${wait}ms...`);
166
+ logger.debug(`waiting ${wait}ms...`);
143
167
  await delayed(0, wait);
144
168
  }
145
169
  }
146
170
  return Date.now() - t0;
147
171
  }
148
172
  /**
149
- * Takes an array of 2D points and converts them into an array of
150
- * {@link DrawCommand}s. The optional `speed` factor can be used to control
151
- * draw speed (default: 1).
173
+ * Syntax sugar for drawing a **single** command only, otherwise same as
174
+ * {@link AxiDraw.draw}.
152
175
  *
153
- * @remarks
154
- * Unless `onlyGeo` is explicitly enabled, the resulting command sequence
155
- * will also contain necessary pen up/down commands.
156
- *
157
- * @param pts
158
- * @param speed
159
- * @param onlyGeo
176
+ * @param cmd
160
177
  */
161
- polyline(pts, speed = 1, onlyGeo = false) {
162
- const commands = pts.map((p) => ["m", p, speed]);
163
- return onlyGeo
164
- ? commands
165
- : [UP, commands[0], DOWN, ...commands.slice(1), UP];
178
+ draw1(cmd) {
179
+ return this.draw([cmd], false);
180
+ }
181
+ motorsOn() {
182
+ this.send("EM,1,1\r");
183
+ }
184
+ motorsOff() {
185
+ this.send("EM,0,0\r");
186
+ }
187
+ penConfig(down, up) {
188
+ down = down !== undefined ? down : this.opts.down;
189
+ this.sendPenConfig(5, down);
190
+ this.penLimits[0] = down;
191
+ up = up !== undefined ? up : this.opts.up;
192
+ this.sendPenConfig(4, up);
193
+ this.penLimits[1] = up;
194
+ this.send(`SC,10,65535\r`);
195
+ }
196
+ penUp(delay, level) {
197
+ if (level !== undefined)
198
+ this.sendPenConfig(4, level);
199
+ delay = delay !== undefined && delay >= 0 ? delay : this.opts.delayUp;
200
+ this.send(`SP,1,${delay}\r`);
201
+ this.isPenDown = false;
202
+ return delay;
203
+ }
204
+ penDown(delay, level) {
205
+ if (level !== undefined)
206
+ this.sendPenConfig(5, level);
207
+ delay = delay !== undefined && delay >= 0 ? delay : this.opts.delayDown;
208
+ this.send(`SP,0,${delay}\r`);
209
+ this.isPenDown = true;
210
+ return delay;
211
+ }
212
+ moveTo(p, tempo = 1) {
213
+ const { pos, targetPos, opts } = this;
214
+ // apply scale factor: worldspace units -> motor steps
215
+ mulN2(targetPos, p, opts.stepsPerInch / opts.unitsPerInch);
216
+ const delta = sub2([], targetPos, pos);
217
+ set2(pos, targetPos);
218
+ const maxAxis = Math.max(...abs2([], delta));
219
+ const duration = (1000 * maxAxis) / (opts.speed * tempo);
220
+ this.send(`XM,${duration | 0},${delta[0] | 0},${delta[1] | 0}\r`);
221
+ return duration;
222
+ }
223
+ home() {
224
+ return this.moveTo(ZERO2);
225
+ }
226
+ async onSignal() {
227
+ this.opts.logger.warn(`SIGNINT received, stop drawing...`);
228
+ this.penUp(0);
229
+ this.motorsOff();
230
+ await delayed(0, 100);
231
+ process.exit(1);
166
232
  }
167
233
  send(msg) {
168
234
  this.opts.logger.debug(msg);
169
235
  this.serial.write(msg);
170
236
  }
237
+ /**
238
+ * Sends pen up/down config
239
+ *
240
+ * @remarks
241
+ * Reference:
242
+ * - https://github.com/evil-mad/AxiDraw-Processing/blob/80d81a8c897b8a1872b0555af52a8d1b5b13cec4/AxiGen1/AxiGen1.pde#L213
243
+ *
244
+ * @param id
245
+ * @param x
246
+ */
247
+ sendPenConfig(id, x) {
248
+ this.send(`SC,${id},${(7500 + 175 * x) | 0}\r`);
249
+ }
171
250
  }
package/control.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { IDeref, IReset } from "@thi.ng/api";
2
+ import { AxiDrawState } from "./api.js";
3
+ /**
4
+ * Default implementation with easy-to-use API to control the processing of draw
5
+ * commands via {@link AxiDraw.draw}.
6
+ *
7
+ * @remarks
8
+ * See {@link AxiDrawOpts.control} for further details.
9
+ */
10
+ export declare class AxiDrawControl implements IDeref<AxiDrawState>, IReset {
11
+ interval: number;
12
+ state: AxiDrawState;
13
+ constructor(interval?: number);
14
+ deref(): AxiDrawState;
15
+ reset(): this;
16
+ pause(): void;
17
+ resume(): void;
18
+ cancel(): void;
19
+ }
20
+ //# sourceMappingURL=control.d.ts.map
package/control.js ADDED
@@ -0,0 +1,34 @@
1
+ import { AxiDrawState } from "./api.js";
2
+ /**
3
+ * Default implementation with easy-to-use API to control the processing of draw
4
+ * commands via {@link AxiDraw.draw}.
5
+ *
6
+ * @remarks
7
+ * See {@link AxiDrawOpts.control} for further details.
8
+ */
9
+ export class AxiDrawControl {
10
+ constructor(interval = 1000) {
11
+ this.interval = interval;
12
+ this.state = AxiDrawState.CONTINUE;
13
+ }
14
+ deref() {
15
+ return this.state;
16
+ }
17
+ reset() {
18
+ this.state = AxiDrawState.CONTINUE;
19
+ return this;
20
+ }
21
+ pause() {
22
+ if (this.state === AxiDrawState.CONTINUE) {
23
+ this.state = AxiDrawState.PAUSE;
24
+ }
25
+ }
26
+ resume() {
27
+ if (this.state === AxiDrawState.PAUSE) {
28
+ this.state = AxiDrawState.CONTINUE;
29
+ }
30
+ }
31
+ cancel() {
32
+ this.state = AxiDrawState.CANCEL;
33
+ }
34
+ }
package/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from "./api.js";
2
2
  export * from "./axidraw.js";
3
+ export * from "./control.js";
4
+ export * from "./polyline.js";
3
5
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./api.js";
2
2
  export * from "./axidraw.js";
3
+ export * from "./control.js";
4
+ export * from "./polyline.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/axidraw",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Minimal AxiDraw plotter/drawing machine controller for Node.js",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "https://github.com/thi-ng/umbrella.git"
12
12
  },
13
- "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/axidraw#readme",
13
+ "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/axidraw#readme",
14
14
  "funding": [
15
15
  {
16
16
  "type": "github",
@@ -21,7 +21,7 @@
21
21
  "url": "https://patreon.com/thing_umbrella"
22
22
  }
23
23
  ],
24
- "author": "Karsten Schmidt <k+npm@thi.ng>",
24
+ "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
27
  "build": "yarn clean && tsc --declaration",
@@ -34,31 +34,33 @@
34
34
  "test": "testament test"
35
35
  },
36
36
  "dependencies": {
37
- "@thi.ng/api": "^8.5.1",
38
- "@thi.ng/compose": "^2.1.20",
39
- "@thi.ng/errors": "^2.2.5",
40
- "@thi.ng/logger": "^1.4.4",
41
- "@thi.ng/vectors": "^7.5.26",
37
+ "@thi.ng/api": "^8.6.0",
38
+ "@thi.ng/compose": "^2.1.21",
39
+ "@thi.ng/errors": "^2.2.6",
40
+ "@thi.ng/logger": "^1.4.5",
41
+ "@thi.ng/vectors": "^7.5.27",
42
42
  "serialport": "^10.5.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@microsoft/api-extractor": "^7.33.5",
46
- "@thi.ng/testament": "^0.3.6",
45
+ "@microsoft/api-extractor": "^7.33.7",
46
+ "@thi.ng/testament": "^0.3.7",
47
47
  "rimraf": "^3.0.2",
48
48
  "tools": "^0.0.1",
49
- "typedoc": "^0.23.20",
50
- "typescript": "^4.8.4"
49
+ "typedoc": "^0.23.22",
50
+ "typescript": "^4.9.4"
51
51
  },
52
52
  "keywords": [
53
+ "2d",
54
+ "async",
53
55
  "axidraw",
56
+ "driver",
54
57
  "geometry",
55
58
  "io",
56
59
  "logger",
57
60
  "node",
58
- "plotter",
59
- "polygon",
61
+ "penplotter",
60
62
  "polyline",
61
- "serial",
63
+ "serialport",
62
64
  "typescript"
63
65
  ],
64
66
  "publishConfig": {
@@ -80,11 +82,17 @@
80
82
  },
81
83
  "./axidraw": {
82
84
  "default": "./axidraw.js"
85
+ },
86
+ "./control": {
87
+ "default": "./control.js"
88
+ },
89
+ "./polyline": {
90
+ "default": "./polyline.js"
83
91
  }
84
92
  },
85
93
  "thi.ng": {
86
94
  "status": "alpha",
87
95
  "year": 2022
88
96
  },
89
- "gitHead": "54c5f22520162bac0d58426a3f86ca636e797f71\n"
97
+ "gitHead": "f445a9cc8022bcdebbf6ff91fd66ced016d72f01\n"
90
98
  }
package/polyline.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { ReadonlyVec } from "@thi.ng/vectors";
2
+ import { DrawCommand, PolylineOpts } from "./api.js";
3
+ /**
4
+ * Takes an array of 2D points and yields an iterable of {@link DrawCommand}s.
5
+ * The drawing behavior can be customized via additional {@link PolylineOpts}
6
+ * given.
7
+ *
8
+ * @remarks
9
+ * The resulting command sequence assumes the pen is in the **up** position at
10
+ * the beginning of the line. Each polyline will end with a {@link UP} command.
11
+ *
12
+ * @param pts
13
+ * @param opts
14
+ */
15
+ export declare function polyline(pts: ReadonlyVec[], opts: Partial<PolylineOpts>): IterableIterator<DrawCommand>;
16
+ /**
17
+ * Syntax sugar. Takes an iterable of draw commands, adds {@link START} as
18
+ * prefix and {@link STOP} as suffix. I.e. it creates a "complete" drawing...
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * [...complete([ ["m", [0, 0]] ])]
23
+ * // [ ["start"], ["m", [0, 0]], ["stop"] ]
24
+ * ```
25
+ *
26
+ * @param commands
27
+ */
28
+ export declare function complete(commands: Iterable<DrawCommand>): Generator<DrawCommand, void, undefined>;
29
+ //# sourceMappingURL=polyline.d.ts.map
package/polyline.js ADDED
@@ -0,0 +1,54 @@
1
+ import { DOWN, START, STOP, UP } from "./api.js";
2
+ /**
3
+ * Takes an array of 2D points and yields an iterable of {@link DrawCommand}s.
4
+ * The drawing behavior can be customized via additional {@link PolylineOpts}
5
+ * given.
6
+ *
7
+ * @remarks
8
+ * The resulting command sequence assumes the pen is in the **up** position at
9
+ * the beginning of the line. Each polyline will end with a {@link UP} command.
10
+ *
11
+ * @param pts
12
+ * @param opts
13
+ */
14
+ export function* polyline(pts, opts) {
15
+ if (!pts.length)
16
+ return;
17
+ const { speed, delayDown, delayUp, down, onlyGeo } = {
18
+ speed: 1,
19
+ onlyGeo: false,
20
+ ...opts,
21
+ };
22
+ if (onlyGeo) {
23
+ for (let p of pts)
24
+ yield ["m", p, speed];
25
+ return;
26
+ }
27
+ yield ["m", pts[0]];
28
+ if (down !== undefined)
29
+ yield ["pen", down];
30
+ yield delayDown != undefined ? ["d", delayDown] : DOWN;
31
+ for (let i = 1, n = pts.length; i < n; i++)
32
+ yield ["m", pts[i], speed];
33
+ yield delayUp != undefined ? ["u", delayUp] : UP;
34
+ // reset pen to configured defaults
35
+ if (down !== undefined)
36
+ yield ["pen"];
37
+ }
38
+ /**
39
+ * Syntax sugar. Takes an iterable of draw commands, adds {@link START} as
40
+ * prefix and {@link STOP} as suffix. I.e. it creates a "complete" drawing...
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * [...complete([ ["m", [0, 0]] ])]
45
+ * // [ ["start"], ["m", [0, 0]], ["stop"] ]
46
+ * ```
47
+ *
48
+ * @param commands
49
+ */
50
+ export function* complete(commands) {
51
+ yield START;
52
+ yield* commands;
53
+ yield STOP;
54
+ }