geomotif.motifs.stringart
¶
String art: straight lines only, and a curve appears anyway.
Nails in a board, thread between them, and the thing you end up looking at is not any of the threads. The curve is their envelope -- the line every thread is tangent to -- and it is a real curve with a real equation that no thread ever lies along. A corner strung this way produces a parabola; a circle strung with the two times table produces a cardioid.
Four ways in, from the most specific to the most general:
- :class:
StringArtCorner-- two arms and a parabola between them, which is the one everybody has made. - :class:
StringArtPolygon-- that corner, at every corner of a polygon. StringArtCircle-- nails around a circle joined by a times table. This is :class:~geomotif.motifs.graphs.ModularMultiplicationunder the name a string artist would look for it under; the geometry is identical, so it is re-exported here rather than reimplemented.- :class:
StringArtEnvelope-- pointion one curve to pointrule(i)on another, for any curves and any rule. The other three are special cases.
Classes:
| Name | Description |
|---|---|
StringArtCorner |
Two arms, laced so the far end of one meets the near end of the other. |
StringArtPolygon |
A strung corner at every corner of a regular polygon. |
StringArtEnvelope |
The general engine: nail |
StringArtCorner
dataclass
¶
StringArtCorner(count: int = 20, corner: Point = (0.0, 0.0), arm_a: Point = (200.0, 0.0), arm_b: Point = (0.0, 200.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Two arms, laced so the far end of one meets the near end of the other.
The first piece of string art anybody makes, and the reason the whole family is interesting: every thread is straight, and the curve they hug is a parabola -- specifically the one tangent to both arms. It is exactly tangent to each thread at one point and touches nothing else, which is what an envelope is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Nails per arm, not counting the corner itself. More threads means a smoother-looking parabola and no change at all to the parabola. |
20
|
corner
|
(float, float)
|
Where the two arms meet. |
(0.0, 0.0)
|
arm_a
|
(float, float)
|
The far end of each arm. They need not be the same length or at a right angle -- an oblique corner still gives a parabola, just a skewed one. |
(200.0, 0.0)
|
arm_b
|
(float, float)
|
The far end of each arm. They need not be the same length or at a right angle -- an oblique corner still gives a parabola, just a skewed one. |
(200.0, 0.0)
|
StringArtPolygon
dataclass
¶
StringArtPolygon(sides: int = 5, count: int = 16, radius: float = 130.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
A strung corner at every corner of a regular polygon.
Each edge carries nails, and each corner's two edges are laced against each other, so the finished piece is a ring of parabolic arcs meeting at the vertices. Three sides gives the classic triangle; raise the count and it converges on a flower.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sides
|
int
|
Corners of the underlying polygon. |
5
|
count
|
int
|
Nails along each edge, not counting its endpoints. |
16
|
radius
|
float
|
Distance from the middle to each corner. |
130.0
|
rotation
|
float
|
Angle of the first corner, in radians. A quarter turn puts it at the top. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the polygon. |
(0.0, 0.0)
|
StringArtEnvelope
dataclass
¶
StringArtEnvelope(count: int = 144, rule: Callable[[int], int] = _doubled, curve: Callable[[float], Point] = _ring, partner: Callable[[float], Point] | None = _frame, *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
The general engine: nail i on one curve, to nail rule(i) on another.
Every other motif in this module and the whole of
:mod:geomotif.motifs.graphs is this with the curves and the rule filled
in. Supply your own and see what the threads hug::
StringArtEnvelope(
count=240,
rule=lambda i: 3 * i,
curve=lambda t: (150 * math.cos(math.tau * t), 150 * math.sin(math.tau * t)),
)
Leaving partner unset strings the curve against itself, which is the
usual case; setting it laces two different curves together -- a circle to
a square, an ellipse to a line. The default does the latter, because two
different curves is the case that needs a general engine at all: one
circle laced to itself already has a name, and it is
:class:~geomotif.motifs.graphs.ModularMultiplication.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Nails on each curve. |
144
|
rule
|
callable
|
Maps a nail index to the index it is strung to. Taken modulo
|
_doubled
|
curve
|
callable
|
Maps a fraction of the way round, in |
_ring
|
partner
|
callable
|
A second curve for the far end of each thread. |
_frame
|
Notes
The design records the function objects in its metadata, so it round-trips within a session but not through a file. Anything that has to survive being written down wants a registered class of its own.