@thi.ng/axidraw 1.0.0 → 1.1.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**: 2023-03-19T14:07:45Z
3
+ - **Last updated**: 2023-03-27T19:05:48Z
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,38 @@ 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
+ ## [1.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/axidraw@1.1.0) (2023-03-22)
13
+
14
+ #### 🚀 Features
15
+
16
+ - add save/restore commands ([317f8e0](https://github.com/thi-ng/umbrella/commit/317f8e0))
17
+ - add/update command types
18
+ - add SAVE/RESTORE to store/restore pen levels
19
+ - update AxiDraw.draw() to restore state after one-off pen config
20
+ - update DipOpts & dip() ([52d8924](https://github.com/thi-ng/umbrella/commit/52d8924))
21
+ - rename `down` => `downDelay`, `up` => `upDelay`
22
+ - add `down`/`up` level opts
23
+ - update dip() impl to store/restore pen state if using custom
24
+ up/down levels for dipping
25
+ - add palette command seq gens ([0e453c1](https://github.com/thi-ng/umbrella/commit/0e453c1))
26
+ - add linearPalette() & radialPalette() and config options
27
+ - update pkg export maps
28
+ - add global clipping bounds option ([a99a58e](https://github.com/thi-ng/umbrella/commit/a99a58e))
29
+ - add AxiDrawOpts.clip
30
+ - add support for paper sizes, home offset ([c44510f](https://github.com/thi-ng/umbrella/commit/c44510f))
31
+ - update AxiDrawOpts.bounds to accept paper sizes ([@thi.ng/units](https://github.com/thi-ng/umbrella/tree/main/packages/units) quantities)
32
+ - add AxiDrawOpts.home
33
+ - update AxiDraw ctor & move/sendMove methods
34
+ - add AxiDraw.setHome()
35
+ - update pkg deps
36
+
37
+ #### ♻️ Refactoring
38
+
39
+ - remove obsolete clamping ([50978ba](https://github.com/thi-ng/umbrella/commit/50978ba))
40
+ - update linearPalette()
41
+ - update bounds handling/clamping ([7850ed6](https://github.com/thi-ng/umbrella/commit/7850ed6))
42
+ - precalc scale factor & bounds in ctor
43
+
12
44
  # [1.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/axidraw@1.0.0) (2023-03-19)
13
45
 
14
46
  #### 🛑 Breaking changes
package/README.md CHANGED
@@ -11,7 +11,7 @@ This project is part of the
11
11
 
12
12
  - [About](#about)
13
13
  - [Declarative vs. imperative](#declarative-vs-imperative)
14
- - [Units, limits & clipping](#units-limits--clipping)
14
+ - [Units, limits & clamping](#units-limits--clamping)
15
15
  - [Path planning](#path-planning)
16
16
  - [thi.ng/geom support](#thinggeom-support)
17
17
  - [SVG support](#svg-support)
@@ -22,11 +22,12 @@ This project is part of the
22
22
  - [Installation](#installation)
23
23
  - [Dependencies](#dependencies)
24
24
  - [API](#api)
25
+ - [Available draw commands](#available-draw-commands)
26
+ - [Command sequence generators](#command-sequence-generators)
25
27
  - [Example usage](#example-usage)
26
28
  - [Basics](#basics)
27
29
  - [geom-axidraw example](#geom-axidraw-example)
28
- - [Available draw commands](#available-draw-commands)
29
- - [Command sequence generators](#command-sequence-generators)
30
+ - [Brush & paint palette](#brush--paint-palette)
30
31
  - [Authors](#authors)
31
32
  - [License](#license)
32
33
 
@@ -34,6 +35,10 @@ This project is part of the
34
35
 
35
36
  Minimal AxiDraw plotter/drawing machine controller for Node.js.
36
37
 
38
+ ![AXI-SCAPE #000](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/axidraw/axiscape-1280.jpg)
39
+
40
+ <small>AXI-SCAPE \#000, 12-layer watercolor painting/plot of cellular automata, © 2023 Karsten Schmidt</small>
41
+
37
42
  This package provides a super-lightweight alternative to control an [AxiDraw
38
43
  plotter](https://axidraw.com/) directly from Node.js, using a small custom set
39
44
  of medium/high-level drawing commands. Structurally, these custom commands are
@@ -53,15 +58,30 @@ following the pattern of other packages in the
53
58
  until the very last moment before being sent to the machine for physical
54
59
  output...
55
60
 
56
- ### Units, limits & clipping
61
+ ### Units, limits & clamping
57
62
 
58
- This package performs **no bounds checking nor clipping** and expects all given
59
- coordinates to be valid and within machine limits. Coordinates can be given in
60
- any unit, but if not using millimeters (default), a conversion factor to inches
61
- (`unitsPerInch`) **MUST** be provided as part of the [options
63
+ By default, bounds checking and coordinate clamping are applied (against a user
64
+ defined bounding rect or paper size), however this can be disabled (in which
65
+ case all given coordinates are expected to be valid and within machine limits).
66
+ Coordinates can be given in any unit, but if not using millimeters (default), a
67
+ conversion factor to inches (`unitsPerInch`) **MUST** be provided as part of the
68
+ [options
62
69
  object](https://docs.thi.ng/umbrella/axidraw/interfaces/AxiDrawOpts.html) given
63
- to the `AxiDraw` constructor. Clipping can be handled by the geom or
64
- geom-axidraw packages (see below)...
70
+ to the `AxiDraw` constructor. Actual geometry clipping can be handled by the
71
+ [geom or geom-axidraw](#thinggeom-support) packages...
72
+
73
+ The bounding rect can be either defined by a tuple of `[[minX,minY],
74
+ [maxX,maxY]]` (in worldspace units) or as paper size defined as a
75
+ [`quantity()`](https://docs.thi.ng/umbrella/units/functions/quantity-1.html). The
76
+ default value is DIN A3 landscape.
77
+
78
+ If given as paper size (e.g. via
79
+ [thi.ng/units](https://github.com/thi-ng/umbrella/blob/develop/packages/units/)
80
+ presets), the actual units used to define these dimensions are irrelevant and
81
+ will be automatically converted.
82
+
83
+ [List of paper
84
+ sizes/presets](https://github.com/thi-ng/umbrella/blob/develop/packages/units/README.md#constants)
65
85
 
66
86
  ### Path planning
67
87
 
@@ -166,7 +186,7 @@ For Node.js REPL:
166
186
  const axidraw = await import("@thi.ng/axidraw");
167
187
  ```
168
188
 
169
- Package sizes (brotli'd, pre-treeshake): ESM: 2.46 KB
189
+ Package sizes (brotli'd, pre-treeshake): ESM: 3.13 KB
170
190
 
171
191
  ## Dependencies
172
192
 
@@ -176,7 +196,9 @@ Package sizes (brotli'd, pre-treeshake): ESM: 2.46 KB
176
196
  - [@thi.ng/date](https://github.com/thi-ng/umbrella/tree/develop/packages/date)
177
197
  - [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
178
198
  - [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
199
+ - [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/develop/packages/math)
179
200
  - [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers)
201
+ - [@thi.ng/units](https://github.com/thi-ng/umbrella/tree/develop/packages/units)
180
202
  - [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/develop/packages/vectors)
181
203
  - [serialport](git://github.com/serialport/node-serialport.git)
182
204
 
@@ -184,6 +206,46 @@ Package sizes (brotli'd, pre-treeshake): ESM: 2.46 KB
184
206
 
185
207
  [Generated API docs](https://docs.thi.ng/umbrella/axidraw/)
186
208
 
209
+ ## Available draw commands
210
+
211
+ All
212
+ [`DrawCommand`s](https://docs.thi.ng/umbrella/axidraw/types/DrawCommand.html)
213
+ are expressed as S-expression-like, [thi.ng/hiccup]()-style elements, aka JS
214
+ arrays/tuples of `[command, ...args]`. The following commands are supported. All
215
+ also as predefined constants or factory functions for the parametric ones:
216
+
217
+ | Command | Preset/factory | Description |
218
+ |---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------|
219
+ | `["comment", msg]` | [`COMMENT`](https://docs.thi.ng/umbrella/axidraw/functions/COMMENT.html) | Ignored, but logged during plotting |
220
+ | `["d", delay?, level?]` / `["u", delay?, level?]` | [`DOWN`](https://docs.thi.ng/umbrella/axidraw/functions/DOWN.html) / [`UP`](https://docs.thi.ng/umbrella/axidraw/functions/UP.html) | Move pen up/down w/ optional delay & level (0-100) |
221
+ | `["home"]` | `HOME` | Move to home position (origin) |
222
+ | `["m", [x,y], speed?]` | [`MOVE_REL`](https://docs.thi.ng/umbrella/axidraw/functions/MOVE_REL.html) | Move to relative position w/ optional speed factor |
223
+ | `["M", [x,y], speed?]` | [`MOVE`](https://docs.thi.ng/umbrella/axidraw/functions/MOVE.html) | Move to absolute position w/ optional speed factor |
224
+ | `["on"]` / `["off"]` | `ON` / `OFF` | Turn motors on/off |
225
+ | `["pen", down, up]` | [`PEN`](https://docs.thi.ng/umbrella/axidraw/functions/PEN.html) | Pen config (up/down levels) |
226
+ | `["reset"]` | `RESET` | Execute user defined reset sequence<sup>(1)</sup> |
227
+ | `["restore"]` | `RESTORE` | Restore saved pen up/down levels |
228
+ | `["save"]` | `SAVE` | Save current pen up/down levels |
229
+ | `["start"]` | `START` | Execute user defined start sequence<sup>(1)</sup> |
230
+ | `["stop"]` | `STOP` | Execute user defined stop sequence<sup>(1)</sup> |
231
+ | `["w", delay]` | [`WAIT`](https://docs.thi.ng/umbrella/axidraw/functions/WAIT.html) | Wait N milliseconds |
232
+
233
+ - <sup>(1)</sup> See
234
+ [AxiDrawOpts](https://docs.thi.ng/umbrella/axidraw/interfaces/AxiDrawOpts.html)
235
+ for details.
236
+
237
+ ### Command sequence generators
238
+
239
+ Additionally, the following command sequence generators are provided (see their
240
+ docs for details and code examples):
241
+
242
+ - [`complete()`](https://docs.thi.ng/umbrella/axidraw/functions/complete.html)
243
+ - [`dip()`](https://docs.thi.ng/umbrella/axidraw/functions/dip.html)
244
+ - [`linearPalette()`](https://docs.thi.ng/umbrella/axidraw/functions/linearPalette.html)
245
+ - [`polyline()`](https://docs.thi.ng/umbrella/axidraw/functions/polyline.html)
246
+ - [`radialPalette()`](https://docs.thi.ng/umbrella/axidraw/functions/radialPalette.html)
247
+ - [`registrationMark()`](https://docs.thi.ng/umbrella/axidraw/functions/registrationMark.html)
248
+
187
249
  ### Example usage
188
250
 
189
251
  #### Basics
@@ -259,6 +321,72 @@ import { map, range } from "@thi.ng/transducers";
259
321
  })();
260
322
  ```
261
323
 
324
+ ### Brush & paint palette
325
+
326
+ This example illustrates how the [`linearPalette()`]() command sequence
327
+ generator can be used to paint random dots with a brush which gets re-dipped in
328
+ different paints every 10 dots...
329
+
330
+ Also see
331
+ [`InterleaveOpts`](https://docs.thi.ng/umbrella/geom-axidraw/interfaces/InterleaveOpts.html)
332
+ for more details...
333
+
334
+ ```ts
335
+ import { points } from "@thi.ng/geom";
336
+ import { asAxiDraw } from "@thi.ng/geom-axidraw";
337
+ import { repeatedly } from "@thi.ng/transducers";
338
+ import { randMinMax2 } from "@thi.ng/vectors";
339
+
340
+ // configure palette
341
+ // "linear" here means the palette slots are arranged in a line
342
+ // (there's also a radialPalette() function for circular/elliptical palette layouts)
343
+ const palette = linearPalette({
344
+ // first palette slot is near the world origin (slight offset)
345
+ pos: [2, 0],
346
+ // 2mm jitter radius (to not always move to exact same position)
347
+ jitter: 2,
348
+ // palette has 5 paint slots
349
+ num: 5,
350
+ // each slot 40mm separation along Y-axis
351
+ // (needs to be measured/determined manually)
352
+ step: [0, 40],
353
+ // dip brush 3x each time
354
+ repeat: 3,
355
+ });
356
+
357
+ // define point cloud of 100 random points
358
+ // using a random palette slot each time (for each refill)
359
+ // assign axidraw-specific attribs to refill brush every 10 dots
360
+ const cloud = points(
361
+ // pick random points
362
+ [...repeatedly(() => randMinMax2([], [10, 10], [190, 190]), 100)],
363
+ // shape attributes
364
+ {
365
+ __axi: {
366
+ interleave: {
367
+ // every 10 elements/dots...
368
+ num: 10,
369
+ // execute these commands...
370
+ commands: () => [
371
+ // first brush cleaning in water
372
+ // (we decide to use the last palette slot for that)
373
+ ...palette(4),
374
+ // now "refill" brush at a random other slot
375
+ ...palette(Math.floor(Math.random() * 4))
376
+ ]
377
+ }
378
+ }
379
+ }
380
+ );
381
+
382
+ // AxiDraw setup
383
+ const axi = new AxiDraw();
384
+ ...
385
+
386
+ // convert geometry into axidraw commands and send to plotter
387
+ axi.draw(asAxiDraw(cloud));
388
+ ```
389
+
262
390
  Other selected toots/tweets:
263
391
 
264
392
  - [Project announcement](https://mastodon.thi.ng/@toxi/109490174709589253)
@@ -268,39 +396,9 @@ Other selected toots/tweets:
268
396
  - [Bitmap-to-vector conversions & shape sorting](https://mastodon.thi.ng/@toxi/109570540391689321)
269
397
  - [Multi-color plotting](https://mastodon.thi.ng/@toxi/109586780630493994)
270
398
  - [Using water color & paintbrush](https://mastodon.thi.ng/@toxi/110044424626641749)
399
+ - [Water color brush tests](https://mastodon.thi.ng/@toxi/110051629944117139)
271
400
  - more to come...
272
401
 
273
- ### Available draw commands
274
-
275
- All
276
- [`DrawCommand`s](https://docs.thi.ng/umbrella/axidraw/types/DrawCommand.html)
277
- are expressed as S-expression-like, [thi.ng/hiccup]()-style elements, aka JS
278
- arrays/tuples of `[command, ...args]`. The following commands are supported. All
279
- also as predefined constants or factory functions for the parametric ones:
280
-
281
- | Command | Preset/factory |
282
- |-----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
283
- | `["comment", msg]` | [`COMMENT`](https://docs.thi.ng/umbrella/axidraw/functions/COMMENT.html) |
284
- | `["d", delay?]` / `["u", delay?]` | [`DOWN`](https://docs.thi.ng/umbrella/axidraw/functions/DOWN.html) / [`UP`](https://docs.thi.ng/umbrella/axidraw/functions/UP.html) |
285
- | `["home"]` | `HOME` |
286
- | `["m", [x,y], speed?]` | [`MOVE_REL`](https://docs.thi.ng/umbrella/axidraw/functions/MOVE_REL.html) |
287
- | `["M", [x,y], speed?]` | [`MOVE`](https://docs.thi.ng/umbrella/axidraw/functions/MOVE.html) |
288
- | `["on"]` / `["off"]` | `ON` / `OFF` |
289
- | `["pen", down, up]` | [`PEN`](https://docs.thi.ng/umbrella/axidraw/functions/PEN.html) |
290
- | `["reset"]` | `RESET` |
291
- | `["start"]` | `START` |
292
- | `["stop"]` | `STOP` |
293
- | `["w", delay]` | [`WAIT`](https://docs.thi.ng/umbrella/axidraw/functions/WAIT.html) |
294
-
295
- #### Command sequence generators
296
-
297
- Additionally, the following command sequence generators are provided (see their docs for code examples):
298
-
299
- - [`complete`](https://docs.thi.ng/umbrella/axidraw/functions/complete.html)
300
- - [`dip`](https://docs.thi.ng/umbrella/axidraw/functions/dip.html)
301
- - [`polyline`](https://docs.thi.ng/umbrella/axidraw/functions/polyline.html)
302
- - [`registrationMark`](https://docs.thi.ng/umbrella/axidraw/functions/registrationMark.html)
303
-
304
402
  ## Authors
305
403
 
306
404
  - [Karsten Schmidt](https://thi.ng)
package/api.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { IDeref } from "@thi.ng/api";
2
2
  import type { ILogger } from "@thi.ng/logger";
3
+ import type { Quantity } from "@thi.ng/units";
3
4
  import type { ReadonlyVec } from "@thi.ng/vectors";
4
5
  /** Start command sequence (configurable via {@link AxiDrawOpts}) */
5
6
  export type StartCommand = ["start"];
@@ -33,7 +34,15 @@ export type MoveRelCommand = ["m", ReadonlyVec, number?];
33
34
  export type WaitCommand = ["w", number];
34
35
  /** Ignored, but will be logged (if logging enabled) */
35
36
  export type CommentCommand = ["comment", string];
36
- export type DrawCommand = StartCommand | StopCommand | HomeCommand | ResetCommand | MotorCommand | PenConfigCommand | PenUpDownCommand | MoveXYCommand | MoveRelCommand | WaitCommand | CommentCommand;
37
+ /**
38
+ * Stores current pen configuration on stack.
39
+ */
40
+ export type SaveCommand = ["save"];
41
+ /**
42
+ * Restores current pen configuration from stack.
43
+ */
44
+ export type RestoreCommand = ["restore"];
45
+ export type DrawCommand = CommentCommand | HomeCommand | MotorCommand | MoveRelCommand | MoveXYCommand | PenConfigCommand | PenUpDownCommand | ResetCommand | RestoreCommand | SaveCommand | StartCommand | StopCommand | WaitCommand;
37
46
  /**
38
47
  * Global plotter drawing configuration. Also see {@link DEFAULT_OPTS}.
39
48
  */
@@ -65,6 +74,28 @@ export interface AxiDrawOpts {
65
74
  * all or if the control is in the {@link AxiDrawState.CONTINUE} state.
66
75
  */
67
76
  control?: IDeref<AxiDrawState>;
77
+ /**
78
+ * All XY coords will be clamped to given bounding rect, either defined by
79
+ * `[[minX,minY], [maxX,maxY]]` (in worldspace units) or as paper size
80
+ * defined as a
81
+ * [`quantity`](https://docs.thi.ng/umbrella/units/functions/quantity-1.html).
82
+ * The default value is DIN A3 landscape.
83
+ *
84
+ * @remarks
85
+ * Set to `undefined` to disable bounds/clipping. If given a paper size (via
86
+ * thi.ng/units `quantity()`), the units used to define these dimensions are
87
+ * irrelevant (and independent of {@link AxiDrawOpts.unitsPerInch}!) and
88
+ * will be automatically converted. Also, the resulting bounds will always
89
+ * be based on [0, 0].
90
+ *
91
+ * List of paper sizes/presets:
92
+ * https://github.com/thi-ng/umbrella/blob/develop/packages/units/README.md#constants
93
+ *
94
+ * Also see {@link AxiDrawOpts.unitsPerInch}
95
+ *
96
+ * @defaultValue `DIN_A3_LANDSCAPE`
97
+ */
98
+ bounds?: [ReadonlyVec, ReadonlyVec] | Quantity<number[]>;
68
99
  /**
69
100
  * Conversion factor from geometry worldspace units to inches.
70
101
  * Default units are millimeters.
@@ -132,6 +163,22 @@ export interface AxiDrawOpts {
132
163
  * @defaultValue `[UP, HOME, OFF]`
133
164
  */
134
165
  stop: DrawCommand[];
166
+ /**
167
+ * Position (in world space units) which is considered the origin and which
168
+ * will be added to all coordinates as offset (e.g. for plotting with a
169
+ * physically offset easel).
170
+ *
171
+ * @remarks
172
+ * Note: The configured {@link AxiDrawOpts.bounds} are independent from this
173
+ * setting and are intended to enforce the plotter's physical movement
174
+ * limits. E.g. shifting the home/offset position by +100mm along X (and
175
+ * assuming only positive coordinates will be used for the drawing), simply
176
+ * means the available horizontal drawing space will be reduced by the same
177
+ * amount...
178
+ *
179
+ * @defaultValue [0, 0]
180
+ */
181
+ home: ReadonlyVec;
135
182
  /**
136
183
  * Refresh interval for checking the control FSM in paused state.
137
184
  *
package/axidraw.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { IReset } from "@thi.ng/api";
2
- import { ReadonlyVec, Vec } from "@thi.ng/vectors/api";
3
- import { AxiDrawOpts, DrawCommand, ISerial, Metrics } from "./api.js";
2
+ import { type ReadonlyVec, type Vec, type VecPair } from "@thi.ng/vectors/api";
3
+ import { type AxiDrawOpts, type DrawCommand, type ISerial, type Metrics } from "./api.js";
4
4
  export declare const DEFAULT_OPTS: AxiDrawOpts;
5
5
  export declare class AxiDraw implements IReset {
6
6
  serial: ISerial;
@@ -8,8 +8,12 @@ export declare class AxiDraw implements IReset {
8
8
  isConnected: boolean;
9
9
  isPenDown: boolean;
10
10
  penLimits: [number, number];
11
+ penState: [number, number][];
11
12
  pos: Vec;
12
13
  targetPos: Vec;
14
+ homePos: ReadonlyVec;
15
+ scale: number;
16
+ bounds?: VecPair;
13
17
  constructor(opts?: Partial<AxiDrawOpts>);
14
18
  reset(): this;
15
19
  /**
@@ -69,6 +73,8 @@ export declare class AxiDraw implements IReset {
69
73
  draw1(cmd: DrawCommand): Promise<Metrics>;
70
74
  motorsOn(): void;
71
75
  motorsOff(): void;
76
+ save(): void;
77
+ restore(): void;
72
78
  penConfig(down?: number, up?: number): void;
73
79
  penUp(delay?: number, level?: number): number;
74
80
  penDown(delay?: number, level?: number): number;
@@ -97,6 +103,7 @@ export declare class AxiDraw implements IReset {
97
103
  * Syntax sugar for {@link AxiDraw.moveTo}([0, 0]).
98
104
  */
99
105
  home(): number[];
106
+ setHome(pos: ReadonlyVec): void;
100
107
  protected onSignal(): Promise<void>;
101
108
  protected send(msg: string): void;
102
109
  protected sendMove(tempo?: number): number[];
package/axidraw.js CHANGED
@@ -5,8 +5,12 @@ import { assert } from "@thi.ng/errors/assert";
5
5
  import { ioerror } from "@thi.ng/errors/io";
6
6
  import { unsupported } from "@thi.ng/errors/unsupported";
7
7
  import { ConsoleLogger } from "@thi.ng/logger/console";
8
+ import { DIN_A3_LANDSCAPE } from "@thi.ng/units/constants/paper-sizes";
9
+ import { convert, div, Quantity } from "@thi.ng/units/unit";
10
+ import { inch } from "@thi.ng/units/units/length";
8
11
  import { abs2 } from "@thi.ng/vectors/abs";
9
- import { ZERO2 } from "@thi.ng/vectors/api";
12
+ import { ZERO2, } from "@thi.ng/vectors/api";
13
+ import { clamp2 } from "@thi.ng/vectors/clamp";
10
14
  import { maddN2 } from "@thi.ng/vectors/maddn";
11
15
  import { mag } from "@thi.ng/vectors/mag";
12
16
  import { mulN2 } from "@thi.ng/vectors/muln";
@@ -22,6 +26,8 @@ export const DEFAULT_OPTS = {
22
26
  logger: new ConsoleLogger("axidraw"),
23
27
  control: new AxiDrawControl(),
24
28
  refresh: 1000,
29
+ bounds: DIN_A3_LANDSCAPE,
30
+ home: [0, 0],
25
31
  unitsPerInch: 25.4,
26
32
  stepsPerInch: 2032,
27
33
  speedDown: 4000,
@@ -39,10 +45,26 @@ export class AxiDraw {
39
45
  constructor(opts = {}) {
40
46
  this.isConnected = false;
41
47
  this.isPenDown = false;
48
+ this.penState = [];
42
49
  this.pos = [0, 0];
43
50
  this.targetPos = [0, 0];
44
51
  this.opts = { ...DEFAULT_OPTS, ...opts };
45
52
  this.penLimits = [this.opts.down, this.opts.up];
53
+ this.scale = this.opts.stepsPerInch / this.opts.unitsPerInch;
54
+ this.setHome(this.opts.home);
55
+ if (this.opts.bounds) {
56
+ this.bounds =
57
+ this.opts.bounds instanceof Quantity
58
+ ? [
59
+ [0, 0],
60
+ convert(this.opts.bounds, div(inch, this.opts.stepsPerInch)),
61
+ ]
62
+ : [
63
+ mulN2([], this.opts.bounds[0], this.scale),
64
+ mulN2([], this.opts.bounds[1], this.scale),
65
+ ];
66
+ }
67
+ this.save();
46
68
  }
47
69
  reset() {
48
70
  zero(this.pos);
@@ -185,6 +207,12 @@ export class AxiDraw {
185
207
  wait = this.penDown(a, b);
186
208
  penCommands++;
187
209
  break;
210
+ case "save":
211
+ this.save();
212
+ break;
213
+ case "restore":
214
+ this.restore();
215
+ break;
188
216
  case "w":
189
217
  wait = a;
190
218
  break;
@@ -207,6 +235,13 @@ export class AxiDraw {
207
235
  logger.debug(`waiting ${wait}ms...`);
208
236
  await delayed(0, wait);
209
237
  }
238
+ // restore one-off pen config to current state
239
+ if (cmd === "d" && b !== undefined) {
240
+ this.sendPenConfig(5, this.penLimits[0]);
241
+ }
242
+ else if (cmd === "u" && b !== undefined) {
243
+ this.sendPenConfig(4, this.penLimits[1]);
244
+ }
210
245
  }
211
246
  const duration = Date.now() - t0;
212
247
  if (showMetrics) {
@@ -239,6 +274,20 @@ export class AxiDraw {
239
274
  motorsOff() {
240
275
  this.send("EM,0,0\r");
241
276
  }
277
+ save() {
278
+ this.opts.logger.debug("saving pen state:", this.penLimits);
279
+ this.penState.push(this.penLimits.slice());
280
+ }
281
+ restore() {
282
+ if (this.penState.length < 2) {
283
+ this.opts.logger.warn("stack underflow, can't restore pen state");
284
+ return;
285
+ }
286
+ const [down, up] = (this.penLimits = this.penState.pop());
287
+ this.sendPenConfig(5, down);
288
+ this.sendPenConfig(4, up);
289
+ this.opts.logger.debug("restored pen state:", this.penLimits);
290
+ }
242
291
  penConfig(down, up) {
243
292
  down = down !== undefined ? down : this.opts.down;
244
293
  this.sendPenConfig(5, down);
@@ -278,9 +327,9 @@ export class AxiDraw {
278
327
  * @param tempo
279
328
  */
280
329
  moveTo(p, tempo) {
281
- const { targetPos, opts } = this;
282
- // apply scale factor: worldspace units -> motor steps
283
- mulN2(targetPos, p, opts.stepsPerInch / opts.unitsPerInch);
330
+ const { homePos, scale, targetPos } = this;
331
+ // apply scale factor: worldspace units -> motor steps, add home offset
332
+ maddN2(targetPos, p, scale, homePos);
284
333
  return this.sendMove(tempo);
285
334
  }
286
335
  /**
@@ -290,9 +339,9 @@ export class AxiDraw {
290
339
  * @param tempo
291
340
  */
292
341
  moveRelative(delta, tempo) {
293
- const { pos, targetPos, opts } = this;
342
+ const { pos, scale, targetPos } = this;
294
343
  // apply scale factor: worldspace units -> motor steps
295
- maddN2(targetPos, delta, opts.stepsPerInch / opts.unitsPerInch, pos);
344
+ maddN2(targetPos, delta, scale, pos);
296
345
  return this.sendMove(tempo);
297
346
  }
298
347
  /**
@@ -301,6 +350,10 @@ export class AxiDraw {
301
350
  home() {
302
351
  return this.moveTo(ZERO2);
303
352
  }
353
+ setHome(pos) {
354
+ this.homePos = mulN2([], pos, this.scale);
355
+ this.opts.logger.debug("setting home position:", pos);
356
+ }
304
357
  async onSignal() {
305
358
  this.opts.logger.warn(`SIGNINT received, stop drawing...`);
306
359
  this.penUp(0);
@@ -313,14 +366,16 @@ export class AxiDraw {
313
366
  this.serial.write(msg);
314
367
  }
315
368
  sendMove(tempo = 1) {
316
- const { pos, targetPos, opts, isPenDown } = this;
369
+ const { bounds, pos, scale, targetPos, opts, isPenDown } = this;
370
+ if (bounds)
371
+ clamp2(null, targetPos, ...bounds);
317
372
  const delta = sub2([], targetPos, pos);
318
373
  set2(pos, targetPos);
319
374
  const maxAxis = Math.max(...abs2([], delta));
320
375
  const duration = (1000 * maxAxis) /
321
376
  ((isPenDown ? opts.speedDown : opts.speedUp) * tempo);
322
377
  this.send(`XM,${duration | 0},${delta[0] | 0},${delta[1] | 0}\r`);
323
- return [duration, (mag(delta) * opts.unitsPerInch) / opts.stepsPerInch];
378
+ return [duration, mag(delta) / scale];
324
379
  }
325
380
  /**
326
381
  * Sends pen up/down config
package/commands.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import type { ReadonlyVec } from "@thi.ng/vectors";
2
- import type { CommentCommand, DrawCommand, HomeCommand, MotorCommand, MoveRelCommand, MoveXYCommand, PenConfigCommand, PenUpDownCommand, ResetCommand, StartCommand, StopCommand, WaitCommand } from "./api.js";
2
+ import type { CommentCommand, DrawCommand, HomeCommand, MotorCommand, MoveRelCommand, MoveXYCommand, PenConfigCommand, PenUpDownCommand, ResetCommand, RestoreCommand, SaveCommand, StartCommand, StopCommand, WaitCommand } from "./api.js";
3
3
  export declare const START: StartCommand;
4
4
  export declare const STOP: StopCommand;
5
5
  export declare const HOME: HomeCommand;
6
6
  export declare const RESET: ResetCommand;
7
+ export declare const ON: MotorCommand;
8
+ export declare const OFF: MotorCommand;
9
+ export declare const SAVE: SaveCommand;
10
+ export declare const RESTORE: RestoreCommand;
7
11
  /**
8
12
  * Creates a {@link PenConfigCommand} using provided down/up positions.
9
13
  *
@@ -12,19 +16,21 @@ export declare const RESET: ResetCommand;
12
16
  */
13
17
  export declare const PEN: (posDown?: number, posUp?: number) => PenConfigCommand;
14
18
  /**
15
- * Creates a {@link PenUpDownCommand} to move the pen up.
19
+ * Creates a {@link PenUpDownCommand} to move the pen up. Both args are optional
20
+ * and if omitted globally configured values are used.
16
21
  *
17
22
  * @param delay
23
+ * @param level
18
24
  */
19
- export declare const UP: (delay?: number) => PenUpDownCommand;
25
+ export declare const UP: (delay?: number, level?: number) => PenUpDownCommand;
20
26
  /**
21
- * Creates a {@link PenUpDownCommand} to move the pen down.
27
+ * Creates a {@link PenUpDownCommand} to move the pen down. Both args are
28
+ * optional and if omitted globally configured values are used.
22
29
  *
23
30
  * @param delay
31
+ * @param level
24
32
  */
25
- export declare const DOWN: (delay?: number) => PenUpDownCommand;
26
- export declare const ON: MotorCommand;
27
- export declare const OFF: MotorCommand;
33
+ export declare const DOWN: (delay?: number, level?: number) => PenUpDownCommand;
28
34
  /**
29
35
  * Creates a {@link MoveXYCommand} command (absolute coordinates).
30
36
  *
package/commands.js CHANGED
@@ -2,6 +2,10 @@ export const START = ["start"];
2
2
  export const STOP = ["stop"];
3
3
  export const HOME = ["home"];
4
4
  export const RESET = ["reset"];
5
+ export const ON = ["on"];
6
+ export const OFF = ["off"];
7
+ export const SAVE = ["save"];
8
+ export const RESTORE = ["restore"];
5
9
  /**
6
10
  * Creates a {@link PenConfigCommand} using provided down/up positions.
7
11
  *
@@ -14,19 +18,29 @@ export const PEN = (posDown, posUp) => [
14
18
  posUp,
15
19
  ];
16
20
  /**
17
- * Creates a {@link PenUpDownCommand} to move the pen up.
21
+ * Creates a {@link PenUpDownCommand} to move the pen up. Both args are optional
22
+ * and if omitted globally configured values are used.
18
23
  *
19
24
  * @param delay
25
+ * @param level
20
26
  */
21
- export const UP = (delay) => ["u", delay];
27
+ export const UP = (delay, level) => [
28
+ "u",
29
+ delay,
30
+ level,
31
+ ];
22
32
  /**
23
- * Creates a {@link PenUpDownCommand} to move the pen down.
33
+ * Creates a {@link PenUpDownCommand} to move the pen down. Both args are
34
+ * optional and if omitted globally configured values are used.
24
35
  *
25
36
  * @param delay
37
+ * @param level
26
38
  */
27
- export const DOWN = (delay) => ["d", delay];
28
- export const ON = ["on"];
29
- export const OFF = ["off"];
39
+ export const DOWN = (delay, level) => [
40
+ "d",
41
+ delay,
42
+ level,
43
+ ];
30
44
  /**
31
45
  * Creates a {@link MoveXYCommand} command (absolute coordinates).
32
46
  *
package/dip.d.ts CHANGED
@@ -2,18 +2,28 @@ import type { Fn0 } from "@thi.ng/api";
2
2
  import type { DrawCommand } from "./api.js";
3
3
  export interface DipOpts {
4
4
  /**
5
- * Delay for emitted {@link DOWN} commands. If omitted, uses globally
6
- * configured default.
5
+ * Pen level (0-99) for emitted {@link UP} commands. If omitted, uses
6
+ * currently configured default.
7
7
  */
8
8
  up: number;
9
9
  /**
10
- * Delay for emitted {@link UP} commands. If omitted, uses globally
10
+ * Delay for emitted {@link DOWN} commands. If omitted, uses currently
11
11
  * configured default.
12
12
  */
13
+ upDelay: number;
14
+ /**
15
+ * Pen level (0-99) for emitted {@link DOWN} commands. If omitted, uses
16
+ * currently configured default.
17
+ */
13
18
  down: number;
19
+ /**
20
+ * Delay for emitted {@link UP} commands. If omitted, uses currently
21
+ * configured default.
22
+ */
23
+ downDelay: number;
14
24
  /**
15
25
  * No-arg function to inject custom commands between each down - up command.
16
- * See example in {@link DIP} docs.
26
+ * See example in {@link dip} docs.
17
27
  */
18
28
  commands: Fn0<Iterable<DrawCommand>>;
19
29
  }
@@ -56,5 +66,5 @@ export interface DipOpts {
56
66
  * @param n
57
67
  * @param opts
58
68
  */
59
- export declare const dip: (n: number, opts?: Partial<DipOpts>) => IterableIterator<DrawCommand>;
69
+ export declare const dip: (n: number, opts?: Partial<DipOpts>) => Iterable<DrawCommand>;
60
70
  //# sourceMappingURL=dip.d.ts.map
package/dip.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { flatten1 } from "@thi.ng/transducers/flatten1";
2
2
  import { repeatedly } from "@thi.ng/transducers/repeatedly";
3
- import { DOWN, UP } from "./commands.js";
3
+ import { DOWN, RESTORE, SAVE, UP } from "./commands.js";
4
4
  /**
5
5
  * Yields a **sequence** of `n` repetitions of {@link DOWN}, {@link UP}
6
6
  * commands, optionally interspersed with other user provided
@@ -40,6 +40,13 @@ import { DOWN, UP } from "./commands.js";
40
40
  * @param n
41
41
  * @param opts
42
42
  */
43
- export const dip = (n, opts = {}) => flatten1(repeatedly(opts.commands
44
- ? () => [DOWN(opts.down), ...opts.commands(), UP(opts.up)]
45
- : () => [DOWN(opts.down), UP(opts.up)], n));
43
+ export const dip = (n, opts = {}) => {
44
+ const down = DOWN(opts.downDelay, opts.down);
45
+ const up = UP(opts.upDelay, opts.up);
46
+ const main = flatten1(repeatedly(opts.commands
47
+ ? () => [down, ...opts.commands(), up]
48
+ : () => [down, up], n));
49
+ return opts.down !== undefined || opts.up !== undefined
50
+ ? [SAVE, ...main, RESTORE]
51
+ : main;
52
+ };
package/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./axidraw.js";
3
3
  export * from "./commands.js";
4
4
  export * from "./control.js";
5
5
  export * from "./dip.js";
6
+ export * from "./palettes.js";
6
7
  export * from "./polyline.js";
7
8
  export * from "./registration.js";
8
9
  export * from "./serial.js";
package/index.js CHANGED
@@ -3,6 +3,7 @@ export * from "./axidraw.js";
3
3
  export * from "./commands.js";
4
4
  export * from "./control.js";
5
5
  export * from "./dip.js";
6
+ export * from "./palettes.js";
6
7
  export * from "./polyline.js";
7
8
  export * from "./registration.js";
8
9
  export * from "./serial.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/axidraw",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Minimal AxiDraw plotter/drawing machine controller for Node.js",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -34,23 +34,25 @@
34
34
  "test": "testament test"
35
35
  },
36
36
  "dependencies": {
37
- "@thi.ng/api": "^8.7.4",
38
- "@thi.ng/checks": "^3.3.10",
39
- "@thi.ng/compose": "^2.1.29",
40
- "@thi.ng/date": "^2.4.8",
41
- "@thi.ng/errors": "^2.2.13",
42
- "@thi.ng/logger": "^1.4.11",
43
- "@thi.ng/transducers": "^8.4.0",
44
- "@thi.ng/vectors": "^7.6.9",
37
+ "@thi.ng/api": "^8.7.5",
38
+ "@thi.ng/checks": "^3.3.11",
39
+ "@thi.ng/compose": "^2.1.30",
40
+ "@thi.ng/date": "^2.4.10",
41
+ "@thi.ng/errors": "^2.2.14",
42
+ "@thi.ng/logger": "^1.4.12",
43
+ "@thi.ng/math": "^5.4.6",
44
+ "@thi.ng/transducers": "^8.4.1",
45
+ "@thi.ng/units": "^0.4.1",
46
+ "@thi.ng/vectors": "^7.6.10",
45
47
  "serialport": "^10.5.0"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@microsoft/api-extractor": "^7.34.4",
49
- "@thi.ng/testament": "^0.3.13",
50
- "rimraf": "^4.4.0",
51
+ "@thi.ng/testament": "^0.3.14",
52
+ "rimraf": "^4.4.1",
51
53
  "tools": "^0.0.1",
52
- "typedoc": "^0.23.26",
53
- "typescript": "^4.9.5"
54
+ "typedoc": "^0.23.28",
55
+ "typescript": "^5.0.2"
54
56
  },
55
57
  "keywords": [
56
58
  "2d",
@@ -96,6 +98,9 @@
96
98
  "./dip": {
97
99
  "default": "./dip.js"
98
100
  },
101
+ "./palettes": {
102
+ "default": "./palettes.js"
103
+ },
99
104
  "./polyline": {
100
105
  "default": "./polyline.js"
101
106
  },
@@ -110,5 +115,5 @@
110
115
  "status": "alpha",
111
116
  "year": 2022
112
117
  },
113
- "gitHead": "1359645f3af8a7d0d43fe7944ea5cd865832f8ee\n"
118
+ "gitHead": "83b15b34326d480cbca0472b20390d4d3bbb792a\n"
114
119
  }
package/palettes.d.ts ADDED
@@ -0,0 +1,145 @@
1
+ import type { ReadonlyVec } from "@thi.ng/vectors/api";
2
+ import { type DipOpts } from "./dip.js";
3
+ export interface BasePaletteOpts extends Partial<DipOpts> {
4
+ /**
5
+ * Jitter radius for base position.
6
+ *
7
+ * @defaultValue 0
8
+ */
9
+ jitter?: number;
10
+ /**
11
+ * Number of palette slots.
12
+ */
13
+ num: number;
14
+ /**
15
+ * Number of brush dips to execute.
16
+ *
17
+ * @defaultValue 1
18
+ */
19
+ repeat?: number;
20
+ }
21
+ export interface LinearPaletteOpts extends BasePaletteOpts {
22
+ /**
23
+ * Palette's base position (in world space units). See {@link AxiDrawOpts.unitsPerInch}.
24
+ *
25
+ * @defaultValue [0, 0]
26
+ */
27
+ pos?: ReadonlyVec;
28
+ /**
29
+ * Step direction vector between palette slots.
30
+ */
31
+ step: ReadonlyVec;
32
+ }
33
+ export interface RadialPaletteOpts extends BasePaletteOpts {
34
+ /**
35
+ * Palette's center position (in world space units). See {@link AxiDrawOpts.unitsPerInch}.
36
+ */
37
+ pos: ReadonlyVec;
38
+ /**
39
+ * Palette radius (measured to the center of each palette slot). If
40
+ * circular, given as number. If elliptical, given as vector.
41
+ */
42
+ radius: number | ReadonlyVec;
43
+ /**
44
+ * Start angle (in radians).
45
+ *
46
+ * @defaultValue 0
47
+ */
48
+ startTheta?: number;
49
+ /**
50
+ * End angle (in radians). This is 2π for a full-circle palette.
51
+ */
52
+ endTheta?: number;
53
+ }
54
+ /**
55
+ * Higher order {@link DrawCommand} sequence generator for working with paint
56
+ * brushes. Takes an config object describing a linear paint palette layout and
57
+ * behavior options for dipping the brush. Returns a function which takes a
58
+ * palette slot index as argument and returns a command sequence moving the
59
+ * plotter to the palette slot and dipping the brush to refill.
60
+ *
61
+ * @remarks
62
+ * Can be used with
63
+ * [InterleaveOpts](https://docs.thi.ng/umbrella/geom-axidraw/interfaces/InterleaveOpts.html)
64
+ * of https://thi.ng/geom-axidraw.
65
+ *
66
+ * Also see:
67
+ * - {@link LinearPaletteOpts} for options
68
+ * - {@link radialPalette} for circular/elliptical palette layouts
69
+ * - {@link dip} (used internally for dipping sequence)
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { points } from "@thi.ng/geom";
74
+ * import { asAxiDraw } from "@thi.ng/geom-axidraw";
75
+ * import { repeatedly } from "@thi.ng/transducers";
76
+ * import { randMinMax2 } from "@thi.ng/vectors";
77
+ *
78
+ * // configure palette
79
+ * const palette = linearPalette({
80
+ * // first palette slot is near the world origin (slight offset)
81
+ * pos: [2, 0],
82
+ * // palette has 5 paint slots
83
+ * num: 5,
84
+ * // each slot 40mm separation along Y-axis
85
+ * // (needs to be measured/determined manually)
86
+ * step: [0, 40],
87
+ * // 2mm jitter radius (to not always move to exact same position)
88
+ * jitter: 2,
89
+ * // dip brush 3x each time
90
+ * repeat: 3,
91
+ * });
92
+ *
93
+ * // investigate command sequence for requesting slot #1
94
+ * palette(1)
95
+ * // [
96
+ * // [ "M", [ 0.8949, 41.6697 ], 1 ],
97
+ * // [ "d", undefined, undefined ],
98
+ * // [ "u", undefined, undefined ],
99
+ * // [ "d", undefined, undefined ],
100
+ * // [ "u", undefined, undefined ],
101
+ * // [ "d", undefined, undefined ],
102
+ * // [ "u", undefined, undefined ]
103
+ * // ]
104
+ *
105
+ * // define point cloud of 100 random points
106
+ * // using a random palette slot each time (for each refill)
107
+ * // assign axidraw-specific attribs to refill brush every 10 dots
108
+ * const cloud = points(
109
+ * [...repeatedly(() => randMinMax2([], [10, 10], [190, 190]), 100)],
110
+ * {
111
+ * __axi: {
112
+ * interleave: {
113
+ * num: 10,
114
+ * commands: () => palette((Math.random() * 5) | 0)
115
+ * }
116
+ * }
117
+ * }
118
+ * );
119
+ *
120
+ * // AxiDraw setup
121
+ * const axi = new AxiDraw();
122
+ * ...
123
+ *
124
+ * // convert geometry into axidraw commands and send to plotter
125
+ * axi.draw(asAxiDraw(cloud));
126
+ * ```
127
+ *
128
+ * @param opts
129
+ */
130
+ export declare const linearPalette: (opts: LinearPaletteOpts) => (id: number) => import("./api.js").DrawCommand[];
131
+ /**
132
+ * Higher order {@link DrawCommand} sequence generator for working with paint
133
+ * brushes. Similar to {@link linearPalette}, but for circular or elliptic
134
+ * palette layouts.
135
+ *
136
+ * @remarks
137
+ * Also see:
138
+ * - {@link RadialPaletteOpts} for options
139
+ * - {@link linearPalette} for more details & code example
140
+ * - {@link dip} (used internally for dipping sequence)
141
+ *
142
+ * @param opts
143
+ */
144
+ export declare const radialPalette: (opts: RadialPaletteOpts) => (id: number) => import("./api.js").DrawCommand[];
145
+ //# sourceMappingURL=palettes.d.ts.map
package/palettes.js ADDED
@@ -0,0 +1,145 @@
1
+ import { isNumber } from "@thi.ng/checks/is-number";
2
+ import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
3
+ import { cossin } from "@thi.ng/math/angle";
4
+ import { mix } from "@thi.ng/math/mix";
5
+ import { jitter } from "@thi.ng/vectors/jitter";
6
+ import { madd2 } from "@thi.ng/vectors/madd";
7
+ import { maddN2 } from "@thi.ng/vectors/maddn";
8
+ import { MOVE } from "./commands.js";
9
+ import { dip } from "./dip.js";
10
+ /**
11
+ * Higher order {@link DrawCommand} sequence generator for working with paint
12
+ * brushes. Takes an config object describing a linear paint palette layout and
13
+ * behavior options for dipping the brush. Returns a function which takes a
14
+ * palette slot index as argument and returns a command sequence moving the
15
+ * plotter to the palette slot and dipping the brush to refill.
16
+ *
17
+ * @remarks
18
+ * Can be used with
19
+ * [InterleaveOpts](https://docs.thi.ng/umbrella/geom-axidraw/interfaces/InterleaveOpts.html)
20
+ * of https://thi.ng/geom-axidraw.
21
+ *
22
+ * Also see:
23
+ * - {@link LinearPaletteOpts} for options
24
+ * - {@link radialPalette} for circular/elliptical palette layouts
25
+ * - {@link dip} (used internally for dipping sequence)
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { points } from "@thi.ng/geom";
30
+ * import { asAxiDraw } from "@thi.ng/geom-axidraw";
31
+ * import { repeatedly } from "@thi.ng/transducers";
32
+ * import { randMinMax2 } from "@thi.ng/vectors";
33
+ *
34
+ * // configure palette
35
+ * const palette = linearPalette({
36
+ * // first palette slot is near the world origin (slight offset)
37
+ * pos: [2, 0],
38
+ * // palette has 5 paint slots
39
+ * num: 5,
40
+ * // each slot 40mm separation along Y-axis
41
+ * // (needs to be measured/determined manually)
42
+ * step: [0, 40],
43
+ * // 2mm jitter radius (to not always move to exact same position)
44
+ * jitter: 2,
45
+ * // dip brush 3x each time
46
+ * repeat: 3,
47
+ * });
48
+ *
49
+ * // investigate command sequence for requesting slot #1
50
+ * palette(1)
51
+ * // [
52
+ * // [ "M", [ 0.8949, 41.6697 ], 1 ],
53
+ * // [ "d", undefined, undefined ],
54
+ * // [ "u", undefined, undefined ],
55
+ * // [ "d", undefined, undefined ],
56
+ * // [ "u", undefined, undefined ],
57
+ * // [ "d", undefined, undefined ],
58
+ * // [ "u", undefined, undefined ]
59
+ * // ]
60
+ *
61
+ * // define point cloud of 100 random points
62
+ * // using a random palette slot each time (for each refill)
63
+ * // assign axidraw-specific attribs to refill brush every 10 dots
64
+ * const cloud = points(
65
+ * [...repeatedly(() => randMinMax2([], [10, 10], [190, 190]), 100)],
66
+ * {
67
+ * __axi: {
68
+ * interleave: {
69
+ * num: 10,
70
+ * commands: () => palette((Math.random() * 5) | 0)
71
+ * }
72
+ * }
73
+ * }
74
+ * );
75
+ *
76
+ * // AxiDraw setup
77
+ * const axi = new AxiDraw();
78
+ * ...
79
+ *
80
+ * // convert geometry into axidraw commands and send to plotter
81
+ * axi.draw(asAxiDraw(cloud));
82
+ * ```
83
+ *
84
+ * @param opts
85
+ */
86
+ export const linearPalette = (opts) => {
87
+ const $opts = {
88
+ pos: [0, 0],
89
+ repeat: 1,
90
+ jitter: 0,
91
+ ...opts,
92
+ };
93
+ const dipOpts = __dipOpts($opts);
94
+ return (id) => {
95
+ if (id < 0 || id >= $opts.num) {
96
+ illegalArgs(`invalid palette index: ${id}`);
97
+ }
98
+ return [
99
+ MOVE(jitter(null, maddN2([], $opts.step, id, $opts.pos), $opts.jitter)),
100
+ ...dip($opts.repeat, dipOpts),
101
+ ];
102
+ };
103
+ };
104
+ /**
105
+ * Higher order {@link DrawCommand} sequence generator for working with paint
106
+ * brushes. Similar to {@link linearPalette}, but for circular or elliptic
107
+ * palette layouts.
108
+ *
109
+ * @remarks
110
+ * Also see:
111
+ * - {@link RadialPaletteOpts} for options
112
+ * - {@link linearPalette} for more details & code example
113
+ * - {@link dip} (used internally for dipping sequence)
114
+ *
115
+ * @param opts
116
+ */
117
+ export const radialPalette = (opts) => {
118
+ const $opts = {
119
+ repeat: 1,
120
+ jitter: 0,
121
+ startTheta: 0,
122
+ endTheta: Math.PI * 2,
123
+ ...opts,
124
+ };
125
+ const radius = isNumber($opts.radius)
126
+ ? [$opts.radius, $opts.radius]
127
+ : $opts.radius;
128
+ const dipOpts = __dipOpts($opts);
129
+ return (id) => {
130
+ if (id < 0 || id >= $opts.num) {
131
+ illegalArgs(`invalid palette index: ${id}`);
132
+ }
133
+ return [
134
+ MOVE(jitter(null, madd2(null, cossin(mix($opts.startTheta, $opts.endTheta, id / $opts.num)), radius, $opts.pos), $opts.jitter)),
135
+ ...dip($opts.repeat, dipOpts),
136
+ ];
137
+ };
138
+ };
139
+ const __dipOpts = (opts) => ({
140
+ down: opts.down,
141
+ downDelay: opts.downDelay,
142
+ up: opts.up,
143
+ upDelay: opts.upDelay,
144
+ commands: opts.commands,
145
+ });