geomotif.io.plotter
¶
Preparing a design for a pen plotter.
A plotter cares about three things this library otherwise does not: how big the paper is, how far the pen travels while it is up, and which pen is drawing. This module answers all three.
A real size. :func:to_plotter_svg writes the drawing at a named paper
size in millimeters -- width="210mm", not width="210" -- so what comes
out of the plotter is the size you asked for rather than whatever the receiving
software guessed.
Fewer wasted moves. :func:optimize joins strokes that end where another
begins and then orders them so the pen travels as little as possible between
them -- which on a plotted mandala is most of the drawing time.
:func:pen_up_distance measures the difference rather than asserting it.
One pen at a time. Everything here works layer by layer
(:mod:geomotif.core.style) and never joins or reorders across layers: two
strokes on different layers are drawn by different pens, and merging them would
mean drawing one of them in the wrong color.
::
from geomotif.io.plotter import optimize, pen_up_distance, save_plotter_svg
design = optimize(mandala.build())
save_plotter_svg(design, "mandala.svg", paper="a4", margin=15.0)
For anything beyond this -- occlusion, hatching, HPGL -- reach for vpype,
which does all of it and which :func:to_vpype hands a design to directly.
Functions:
| Name | Description |
|---|---|
page_size |
Return a named paper's size in millimeters. |
on_page |
Return |
to_plotter_svg |
Render a design as an SVG measured in real millimeters. |
save_plotter_svg |
Write a plotter-ready SVG and return the path written. |
pen_up_distance |
Return how far the pen travels while it is not drawing. |
optimize |
Return |
to_vpype |
Return this design as a |
page_size
¶
Return a named paper's size in millimeters.
Raises:
| Type | Description |
|---|---|
KeyError
|
If the name is not one of :data: |
Source code in src/geomotif/io/plotter.py
on_page
¶
on_page(design: Design, *, paper: str = 'a4', margin: float = 10.0, landscape: bool = False) -> Design
Return design fitted to a sheet of paper, in millimeters, y-down.
Scaling is uniform and the drawing is centered, so nothing is distorted and the margin is honored on all four sides. The result is in the coordinate space a plotter and an SVG both use -- y growing downward -- which is why it is worth doing once here rather than in each writer.
Source code in src/geomotif/io/plotter.py
to_plotter_svg
¶
to_plotter_svg(design: Design, *, paper: str = 'a4', margin: float = 10.0, landscape: bool = False, stroke_width: float = DEFAULT_PEN, **kwargs: Any) -> str
Render a design as an SVG measured in real millimeters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
design
|
Design
|
What to plot. |
required |
paper
|
str
|
A name from :data: |
'a4'
|
margin
|
float
|
Border to leave unplotted, in millimeters. Worth more than you would think: most plotters cannot reach the last few millimeters of a sheet. |
10.0
|
landscape
|
bool
|
Turn the paper on its side. |
False
|
stroke_width
|
float
|
Pen width in millimeters. Only affects how the file looks -- a plotter draws with the pen it has -- but getting it right makes the preview honest. |
DEFAULT_PEN
|
**kwargs
|
Any
|
Passed to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
An SVG whose |
Source code in src/geomotif/io/plotter.py
save_plotter_svg
¶
save_plotter_svg(design: Design, path: str | PathLike[str], **kwargs: Any) -> Path
Write a plotter-ready SVG and return the path written.
Keyword arguments are passed straight through to :func:to_plotter_svg.
Source code in src/geomotif/io/plotter.py
pen_up_distance
¶
pen_up_distance(design: Design, *, start: Point = (0.0, 0.0)) -> float
Return how far the pen travels while it is not drawing.
Counted from start to the first stroke, then between the end of each
stroke and the beginning of the next. This is the number :func:optimize
exists to reduce, and the one to measure it by.
Source code in src/geomotif/io/plotter.py
optimize
¶
optimize(design: Design, *, tolerance: float = 0.1, merge: bool = True, sort: bool = True, start: Point = (0.0, 0.0)) -> Design
Return design with its strokes joined up and put in a sensible order.
Two passes, both of which only ever move whole strokes about:
- Merge. Two open strokes whose ends meet within
tolerancebecome one, reversing either if that is what makes them meet. A stroke whose own ends then meet is closed. This is where a tiling built cell by cell stops being four thousand separate edges. - Sort. The strokes are ordered greedily from
start, always taking whichever begins nearest to where the pen just finished, and reversing an open stroke when its far end is the nearer one.
Neither pass crosses a layer, for the reason this module opens with, and layers come out in the order they went in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
design
|
Design
|
What to optimize. |
required |
tolerance
|
float
|
How close two ends must be to count as touching, in the design's own units. Must be >= 0; zero demands exactly equal coordinates. |
0.1
|
merge
|
bool
|
Run each pass. Both on by default. |
True
|
sort
|
bool
|
Run each pass. Both on by default. |
True
|
start
|
(float, float)
|
Where the pen begins, for the sort. |
(0.0, 0.0)
|
Returns:
| Type | Description |
|---|---|
Design
|
The same ink in the same colors, drawn in a better order. Loose points and metadata are carried through untouched -- a dot is a dot wherever it is in the list. |
Notes
Both passes are O(n²) in the number of strokes, which is nothing at the
thousands a plotted design runs to and would matter at a million. Neither
is optimal, and neither is trying to be: this is nearest-neighbour applied
to the travelling salesman, which carries no bound on how far from the best
answer it lands but is what vpype linesort does too. The suite measures
the two against each other rather than taking either on trust.
Source code in src/geomotif/io/plotter.py
to_vpype
¶
to_vpype(design: Design, *, paper: str = 'a4', margin: float = 10.0, landscape: bool = False) -> Any
Return this design as a vpype document, fitted to a page.
vpype is the pen-plotter toolchain -- occlusion, hatching, HPGL output,
and a plotting pipeline this library is deliberately not trying to be. This
is the door to it: one layer per :mod:geomotif.core.style layer, named the
same, on a page of the size asked for.
::
import vpype_cli
document = to_vpype(design, paper="a3")
vpype_cli.execute("linemerge linesort write out.svg", document)
Returns:
| Type | Description |
|---|---|
Document
|
Typed loosely because |
Raises:
| Type | Description |
|---|---|
ImportError
|
If |