color and layers¶
Geometry says where the ink goes. A Style says
which pen puts it there — a layer name, a color, a stroke width — and it rides
along in Design.meta rather than in Path, because none of it changes the
maths.
from geomotif import layer, save_svg, styled
from geomotif.motifs import Circle, Phyllotaxis
outline = styled(Circle(radius=120).build(), layer="pen1", stroke="black")
seeds = styled(Phyllotaxis(count=400).build(), layer="pen2", stroke="crimson")
save_svg(layer(outline, seeds), "two-pens.svg")
That file opens in Inkscape as two named layers, loads into vpype as two
layers, and plots as two pen changes.
Applying a style¶
styled lays a style over every stroke and every
loose point of a design:
styled(design, layer="pen1")
styled(design, stroke="#c0392b", width=0.35)
styled(design, Style(layer="cuts", stroke="red")) # or a whole style at once
Styling merges rather than replaces, so the two calls compose:
design = styled(styled(design, layer="pen1"), stroke="red")
# every stroke is now Style(layer="pen1", stroke="red")
Every field is optional, and None means not stated rather than default: a
style that names only a layer still draws in whatever ink the writer was told to
use. Nothing is required to carry a style, and a design that carries none writes
exactly the file it wrote before this existed.
What survives what¶
Styles are attached per stroke and per loose point, and every operation that reshapes a design keeps them lined up with the geometry:
| Operation | What happens to the styles |
|---|---|
transformed, fit, flipped_y, jitter |
carried, one for one |
a + b, layer(...), radial_repeat, tile |
laid end to end, so both designs keep theirs |
resampled |
carried; a stroke dropped for want of points takes its style with it |
clip_to |
carried onto every fragment a stroke is cut into; dropped with what falls outside |
save_points, iteration |
gone — those are coordinates, and a coordinate has no color |
The end-to-end rule for + is the one that matters. A right-biased merge would
hand the whole result the second design's styles, and layer(red, blue) would
come out entirely blue — which is the one thing a layer exists to prevent.
Reading them back¶
from geomotif import by_layer, layer_names, point_styles_of, styles_of
styles_of(design) # one entry per stroke, None where a stroke has none
point_styles_of(design) # the same, per loose point
layer_names(design) # ("pen1", "pen2") -- in the order they are drawn
by_layer(design) # {"pen1": Design(...), "pen2": Design(...)}
styles_of is always exactly as long as design.paths, whatever the metadata
says, so you can zip the two without checking.
by_layer keys anything unlayered under None rather than under a stand-in
name, because each writer has its own idea of what the unnamed layer is called —
"0" in DXF, 1 in vpype — and picking one here would be wrong somewhere
else.
What each writer does with them¶
SVG writes each layer as the group Inkscape and vpype read
(inkscape:groupmode="layer"), and per-element stroke, stroke-width and
fill attributes wherever a style differs from the document's own. A styled dot
takes its color as a fill and its width as a radius.
DXF writes real DXF layers: every one is declared in the file's layer table
and every entity carries its own. color is the part DXF barely models — R12
knows 255 indexed colors and no arbitrary ones — so the seven it can name
(red, yellow, green, cyan, blue, magenta, white/black) are
written as entity colors and anything else is left to the layer. Layer names
have to be ones R12 permits: at most 31 characters from letters, digits and
_$-., which the writer checks rather than leaving to the reader.
matplotlib draws a styled stroke in its own color and width, so a two-pen design looks on screen the way it will come off the plotter.
Specs and design files carry styles too. They are written beside the parameters rather than among them, because they belong to the design rather than to the motif:
{
"geomotif": "1.3.1",
"motif": "circle",
"params": {"radius": 120.0},
"path-style": [{"$type": "geomotif.core.style.Style", "layer": "pen1", ...}]
}
The two metadata keys are hyphenated deliberately — no Python parameter can be
called path-style, so a motif's own parameters can never collide with them.
Preparing the file for a plotter¶
Layers are what the plotter helpers work in terms of: optimize sorts and
merges strokes within each layer and never across them, and to_plotter_svg
writes the whole thing at a physical paper size in millimeters. See
Plotting it for real.