Skip to content

Latest commit

 

History

History
362 lines (291 loc) · 14.2 KB

README.md

File metadata and controls

362 lines (291 loc) · 14.2 KB

@thi.ng/geom-axidraw

npm version npm downloads Mastodon Follow

Note

This is one of 199 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

Conversion and preparation of thi.ng/geom shapes & shape groups to/from AxiDraw pen plotter draw commands. This is a support package for @thi.ng/geom.

This package only deals with the (bi-directional) conversion aspects between geometry and plotter commands. The @thi.ng/axidraw package is responsible for the actual plotter output...

Supported shape types

Shape type Remarks
arc elliptic arc(1)
circle circle(1)
complexpoly polygon with optional holes
cubic cubic bezier segment(1)
ellipse ellipse(1)
group shape group (possibly nested)(3)
line line segment(2)
path optionally with holes or multiple curves
points point cloud (stippling)(3)
polyline polyline (any number of vertices)(2)
polygon simple polygon, no holes(2)
quad arbitrary 4-gon(2)
quadratic quadratic bezier segment(1)
rect axis aligned rectangle(2)
triangle triangle(2)
  • (1) always interpolated/sampled
  • (2) only interpolated if forced via attrib
  • (3) supports command sequence interleaving

AxiDraw specific shape attributes

All thi.ng/geom shape types have support for arbitrary attributes. Different support packages can then utilize these attribs to customize usage or behaviors. In this case, any package-specific attribs must be stored under the __axi key:

  • clip: Optional clip polygon vertices. If given only the parts of strokes inside that polygon will be plotted. The clip polygon itself can only have a single boundary, no holes supported (yet?).
  • delayDown: Shape specific delay (in ms), i.e. initial hold time for the stroke or when stippling...
  • delayUp: Delay for pen up command at the end this particular shape/polyline/point.
  • down: Pen down position (in %) for this particular shape/polyline. Will be reset to globally configured default at the end of the shape.
  • speed: Speed factor (multiple of globally configured draw speed). Depending on pen used, slower speeds might result in thicker strokes.
  • skip: Only used for groups or point clouds. If given, only every (n+1)th child shape or point is being processed and the others ignored. Useful for low detail test runs.
  • sort: Ordering function (in lieu of full path planning/optimization, which is planned for a later stage). For shapes other than points(), order of appearance is used by default.
  • interleave: Currently only supported for point clouds. See Supporting custom drawing tools.
import { circle } from "@thi.ng/geom";

// a circle which will be plotted at only 10% of the normal speed
circle(100, { __axi: { speed: 0.1 } })

Shape interpolation/resampling

Since many of the supported shapes are not inherently vertex-based, their boundaries/outlines need to be sampled when they are being converted to polylines. Therefore, in addition to the above attributes, the thi.ng/geom package itself also makes use of the __samples attribute to control the re-sampling of individual shapes. Please see the following links for more details and supported options:

Draw order

As mentioned above, the default draw order for shapes within a group is order of appearance. For point clouds, the {@link pointsByNearestNeighbor} ordering function is used by default, which attempts to minimize travel distance between points.

Currently, the following ordering functions are available, but custom implementations can be provided too...

Command visualization

For debug, optimization & visualization purposes it's useful to convert a sequence of plotter commands back into 2D geometry. This can be done via the supplied asGeometry() function, which also takes several options to customize the resulting output. One of the examples below is demonstrating basic usage.

Example draw command visualization based on the code example in this readme

Basic usage & examples

The main function of this package is the polymorphic function asAxiDraw(), which converts any of the supported shape types into an iterable of thi.ng/axidraw drawing commands. This conversion happens semi-lazily (via generator functions) to minimize memory usage and spread out the computational load of the conversions.

The below example can be directly launched via node cubics.js (obviously provided you have an AxiDraw connected and all listed packages installed):

Interpolated polygons

(Result: https://mastodon.thi.ng/@toxi/109473655772673067)

import { AxiDraw } from "@thi.ng/axidraw";
import { asCubic, group, pathFromCubics, star } from "@thi.ng/geom";
import { asAxiDraw } from "@thi.ng/geom-axidraw";
import { map, range } from "@thi.ng/transducers";

(async () => {
    // create group of bezier-interpolated star polygons,
    // with each path using a slightly different configuration
    const geo = group({ translate: [100, 100] }, [
        ...map(
            (t) =>
                pathFromCubics(
                    asCubic(star(90, 6, [t, 1]), {
                        breakPoints: true,
                        scale: 0.66,
                    })
                ),
            range(0.3, 1.01, 0.05)
        ),
    ]);

    // connect to plotter
    const axi = new AxiDraw();
    await axi.connect();
    // convert geometry to drawing commands & send to plotter
    await axi.draw(asAxiDraw(geo, { samples: 40 }));
})();

Clipping

(Result: https://mastodon.thi.ng/@toxi/109483553358349473)

import { AxiDraw } from "@thi.ng/axidraw";
import {
    asSvg,
    circle,
    group,
    starWithCentroid,
    svgDoc,
    vertices,
} from "@thi.ng/geom";
import { asAxiDraw, asGeometry } from "@thi.ng/geom-axidraw";
import { map, range } from "@thi.ng/transducers";
import { writeFileSync } "node:fs";

(async () => {
    const origin = [100, 100];
    const radius = 50;
    const boundary = starWithCentroid(origin, radius, 5, [1, 0.45], { __axi: { speed: 0.25 } });
    // group of concentric circles using boundary as clip polygon
    const geo = group({}, [
        boundary,
        group({ __samples: 60, __axi: { clip: vertices(boundary) } }, [
            ...map((r) => circle(origin, r), range(2, radius, 2)),
        ]),
    ]);

    // convert into AxiDraw command sequence
    const commands = [...asAxiDraw(geo)];

    // now visualize command sequence (convert back to geometry, incl. pen movements)
    const { paths, rapids, ups, downs } = asGeometry(commands);
    // write visualization as SVG
    writeFileSync(
        "export/clipping-commands.svg",
        asSvg(svgDoc({ width: 600, weight: 0.2 }, paths, rapids, ups, downs))
    );

    // actually connect & send to plotter
    const axi = new AxiDraw();
    await axi.connect();
    await axi.draw(commands);
})();

Supporting custom drawing tools

AxiDraw (and other pen plotters) are not restricted to just using pens, but can be used with all sorts of custom drawing tools, some of which (like paint brushes) require regular "refills" every N strokes/dots. For that reason, some shape types (currently only shape groups and point clouds) are supporting config & behavior options for interleaving their normal shape command sequence with additional tool-specific arbitrary utility command sequences (e.g. to regularly dip a brush into a paint pot/palette).

import { AxiDraw, COMMENT, dip, MOVE } from "@thi.ng/axidraw";
import { circle, points, vertices } from "@thi.ng/geom";
import { asAxiDraw } from "@thi.ng/geom-axidraw";

(async () => {
    // create point cloud container
    const pts = points(
        // using 24 points on a circle w/ origin @ 150,150, radius=100
        vertices(circle([150, 150], 100), 24),
        { __axi: {
            // use command interleaving
            interleave: {
                // every 5 points/dots
                num: 5,
                // insert these draw commands:
                // (this function is being re-called every `num` points and
                // can produce different commands to insert each time...)
                commands: (n) => [
                    // no-op command, but will be logged during plotting
                    COMMENT(`--- refill brush (@ ${n} points) ---`),
                    // move to XY pos (i.e. position of paint reservoir)
                    MOVE([10,50]),
                    // dip the brush 3x times down & up (each time wait 200ms whilst down)
                    // (dip() creates a cmd sequence, so need to use the spread operator `...` here)
                    ...dip(3, { downDelay: 200 }),
                    // (...and then drawing continues w/ next 5 points)
                ]
            }
        }
    });

    // actually connect & send to plotter
    const axi = new AxiDraw();
    await axi.connect();
    await axi.draw(asAxiDraw(pts));
})();

Status

ALPHA - bleeding edge / work-in-progress

Search or submit any issues for this package

Related packages

  • @thi.ng/axidraw - Minimal AxiDraw plotter/drawing machine controller for Node.js

Installation

yarn add @thi.ng/geom-axidraw

ESM import:

import * as gaxi from "@thi.ng/geom-axidraw";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/geom-axidraw"></script>

JSDelivr documentation

For Node.js REPL:

const gaxi = await import("@thi.ng/geom-axidraw");

Package sizes (brotli'd, pre-treeshake): ESM: 1.52 KB

Dependencies

Note: @thi.ng/api is in most cases a type-only import (not used at runtime)

API

Generated API docs

TODO

Authors

If this project contributes to an academic publication, please cite it as:

@misc{thing-geom-axidraw,
  title = "@thi.ng/geom-axidraw",
  author = "Karsten Schmidt",
  note = "https://thi.ng/geom-axidraw",
  year = 2022
}

License

© 2022 - 2024 Karsten Schmidt // Apache License 2.0