geomotif.motifs
¶
The motif catalog.
Motifs are imported from their family modules rather than from the top-level package: there will eventually be well over a hundred of them, and a flat namespace that large is unusable. Import what you need::
from geomotif.motifs import Circle, GoldenSpiral
from geomotif.motifs.spirals import GoldenSpiral
or construct one by name through :mod:geomotif.core.registry.
============ ==========================================================
Module Contents
============ ==========================================================
spirals Archimedean, logarithmic, golden, Fibonacci, Fermat,
hyperbolic, lituus, Theodorus, Euler, involute, and the
endpoint-constrained :class:~.spirals.SpiralBetween
primitives Circles, arcs, sectors, rectangles, regular and star
polygons, superellipses, Reuleaux curves, grids and
Poisson-disc point fields
curves The named curves: hearts, cardioids, lemniscates, Cassini
ovals, limacons, astroids, cycloids and the rest
roulettes What one circle draws rolling around another --
trochoids, cycloids, the Spirograph, and the stacked
rotating arms of :class:~.roulettes.Epicycles
polar Roses and Maurer roses, phyllotaxis, and a one-off
radius function
harmonic Lissajous figures, harmonics and the harmonograph --
sharing a module with polar
fractals Koch, Hilbert, Gosper, the dragons and the Sierpinski
family as grammars; carpets, trees and the Apollonian
gasket by recursion; the Barnsley fern by chaos game
graphs Complete and circulant graphs, chord diagrams, and the
times-table cardioid of :class:~.graphs.ModularMultiplication
stringart Straight threads whose envelope is a curve: the strung
corner, the strung polygon, and the general engine
tilings Periodic tilings on a lattice, both Penrose tilings, the
Ammann-Beenker quasicrystal and Truchet's tossed tiles
sacred Circles on a hexagonal grid: the vesica, the seed, the
flower, Metatron's cube, the Sri Yantra
guilloche The woven line work of banknotes and watch dials
girih Islamic strapwork: the five girih tiles, the tenfold
tiling they generate, and the star rosette
knots Celtic knotwork: the triquetra, the endless knot, the
circular and square knots and the plait
solids The Platonic solids and the football, flattened onto the
page by a choice of projection
illusions Impossible figures and interference: the tribar, the
endless staircase, the cafe wall, moire
voronoi Nearest-point maps and their dual triangulation, plus
Lloyd's relaxation -- the one family needing [scipy]
symmetry Points constrained by a symmetry group and solved for
rather than evaluated -- experimental
mandala Composed figures -- mandalas, kaleidoscopes, snowflakes --
motifs made out of the other families, living in
:mod:geomotif.compose
============ ==========================================================
Modules:
| Name | Description |
|---|---|
curves |
The named curves: the ones that earned a name before they earned a use. |
fractals |
Fractals: the same shape at every scale, reached three different ways. |
girih |
Islamic geometric patterns: girih tiles, strapwork and the star rosette. |
graphs |
Graph and number art: points on a circle, joined by an arithmetic rule. |
guilloche |
Guilloché: the engine-turned line work on banknotes and watch dials. |
illusions |
Impossible figures and interference patterns. |
knots |
Celtic knotwork: strands that cross over and under and never end. |
polar |
Roses, harmonics and the sunflower: curves written as angle and radius. |
primitives |
The basic shapes: circles, polygons, stars, grids and point fields. |
roulettes |
Roulettes: what one circle draws while rolling around another. |
sacred |
Sacred geometry: circles on a hexagonal grid, and what people drew on them. |
solids |
Polyhedra, flattened onto the page. |
spirals |
Spiral motifs. |
stringart |
String art: straight lines only, and a curve appears anyway. |
symmetry |
Point sets defined by a symmetry group and a rule about their distances. |
tilings |
Tilings: the periodic ones, the aperiodic ones, and one that rolls dice. |
voronoi |
Voronoi diagrams, the Delaunay triangulation, and Lloyd's relaxation. |
Classes:
| Name | Description |
|---|---|
Astroid |
The four-cusped star |
BowCurve |
The bow, |
Butterfly |
Temple Fay's butterfly: twelve revolutions that draw two wings. |
Cardioid |
The heart-shaped |
CassiniOval |
Points whose distances to two foci multiply to a constant. |
Cochleoid |
The snail shell |
Cornoid |
The cornoid, |
Cycloid |
The path of a point on a rolling wheel's rim. |
Deltoid |
The three-cusped tricuspoid, Euler's curve of 1745. |
FishCurve |
A fish, tail and all, from |
Folium |
The leaf curve |
Heart |
A heart, in either of the two shapes that go by the name. |
Lemniscate |
Bernoulli's lemniscate: the infinity symbol. |
LemniscateOfGerono |
Gerono's lemniscate: the other figure eight, |
Limacon |
Pascal's snail, |
Nephroid |
The kidney: the two-cusped epicycloid, |
Trochoid |
The path of a point fixed to a rolling wheel, on the rim or off it. |
Witch |
The witch of Agnesi: a bell curve with an exact algebraic definition. |
ApollonianGasket |
Circles packed into circles, filling every gap they leave. |
BarnsleyFern |
Four affine maps and a coin, producing a fern. |
CantorSet |
Take out the middle third. Repeat. Stack the rounds to see it happen. |
DragonCurve |
The Heighway dragon: fold a strip of paper in half, repeatedly, unfold. |
GosperCurve |
The flowsnake: a space-filling curve on a hexagonal grid. |
HilbertCurve |
The space-filling curve that keeps neighbours together. |
HTree |
An H whose serifs are smaller H's, and so on down. |
IFSAttractor |
The chaos game: pick a map at random, apply it, plot the point, repeat. |
IFSMap |
One contracting map of an iterated function system, and how often to pick it. |
KochAntisnowflake |
The snowflake with its spikes turned inward, which is a different shape. |
KochCurve |
The original: replace the middle third of every segment with a spike. |
KochSnowflake |
Three Koch curves around a triangle: finite area, infinite perimeter. |
LevyCCurve |
Paul Levy's C curve: two half-size copies at right angles, forever. |
MinkowskiIsland |
Four Minkowski sausages around a square: the quadratic Koch island. |
MinkowskiSausage |
Koch's idea on a square grid: a battlement instead of a spike. |
MooreCurve |
Hilbert's curve closed into a loop. |
PeanoCurve |
The first space-filling curve ever published, from 1890. |
PythagorasTree |
Squares on the legs of right triangles, all the way up. |
SierpinskiArrowhead |
The gasket approached along a curve instead of through a triangle. |
SierpinskiCarpet |
A square with its middle ninth removed, then again in each ninth. |
SierpinskiTriangle |
The gasket, drawn as one unbroken stroke that closes on itself. |
Terdragon |
The dragon done in thirds: one segment becomes three at 120 degrees. |
TwinDragon |
Two Heighway dragons back to back, enclosing a region that tiles. |
VicsekFractal |
The box fractal: a plus sign made of plus signs. |
GirihTile |
One of the five girih tiles, with the strapwork it carries. |
HexStarLattice |
Six-pointed stars on a triangular lattice, rhombi filling the gaps. |
InterlockingDecagons |
The pattern the tenfold tiling carries: ten-pointed stars, interlocked. |
Rosette |
The shamsa: a star, a blunter star inside it, and so on inward. |
RosetteTiling |
A field of rosettes, touching point to point. |
TenfoldGirih |
Regular decagons laid edge to edge, with a bowtie in every gap. |
BipartiteGraph |
Two rows of nodes, and every line from one row to the other. |
ChordDiagram |
Nodes on a circle and whichever chords you name between them. |
CompleteGraph |
Every node joined to every other: K5, K12, and the ones in between. |
CyclicGraph |
The circulant: join every node to the ones a fixed number of steps away. |
ModularAddition |
Join each number to the one a fixed distance further round. |
ModularMultiplication |
The times table drawn as chords, which turns out to be a cardioid. |
PrimeChords |
Join two numbers whenever they add up to a prime. |
GuillocheBand |
A woven ribbon along a straight spine: the border of a banknote. |
GuillochePattern |
A rosette inside a woven border, with rules between: the banknote layout. |
GuillocheRosette |
A stack of rosettes, each turned a little further than the last. |
CafeWall |
Parallel mortar lines that refuse to look parallel. |
ImpossibleCube |
The same cube, told two contradictory things about which face is nearer. |
MoirePattern |
Two regular patterns laid over each other, and the fringes between them. |
NeckerCube |
A wireframe cube with nothing to say which face is in front. |
PenroseStairs |
The endless staircase: four flights, every step up, back where you began. |
PenroseTriangle |
The tribar: three square beams meeting at three right angles. |
CelticGrid |
The plait: strands at 45 degrees, bouncing off the frame and off barriers. |
CircularCelticKnot |
One strand wound several times round a ring, weaving with itself. |
EndlessKnot |
The endless knot: four strands each way, woven, with their ends looped back. |
SquareCelticKnot |
The same winding, but round a square frame instead of a ring. |
Triquetra |
The trinity knot: three arcs that turn out to be one strand. |
Harmonic |
Sums of sines on each axis: :class: |
Harmonograph |
The Victorian drawing machine: swinging pendulums, slowly running down. |
Lissajous |
Two perpendicular oscillations, plotted against each other. |
MaurerRose |
A rose walked in whole-degree steps and joined by straight chords. |
Pendulum |
One swinging weight of a :class: |
Phyllotaxis |
The sunflower head: |
PolarExpression |
Any radius function you like, wrapped as a motif. |
Rose |
The rhodonea |
Arc |
Part of a circle: |
Circle |
A circle of radius |
Egg |
An oval fatter at one end than the other. |
Ellipse |
An ellipse with semi-axes |
Line |
A straight segment from |
PointGrid |
A rectangular lattice of loose points, optionally staggered. |
PoissonDiscPoints |
Points scattered at random, but never closer together than a set distance. |
Rectangle |
An axis-aligned rectangle centered on |
RegularPolygon |
A regular |
ReuleauxPolygon |
A curve of constant width: the Reuleaux triangle and its odd-sided kin. |
RoundedRectangle |
A rectangle with its corners replaced by quarter circles. |
Sector |
A pie slice: an arc closed back to its center by two radii. |
Squircle |
The square-circle midpoint: a :class: |
Star |
The star people actually draw: outer points joined through inner ones. |
StarPolygon |
The |
Superellipse |
The curve |
Epicycles |
Rotating arms stacked tip to tail; the path of the last tip. |
Epicycloid |
A point on the rim of a wheel rolling outside a ring: a ring of petals. |
Epitrochoid |
A pen fixed to a wheel rolling around the outside of a ring. |
Hypocycloid |
A point on the rim of a wheel rolling inside a ring: a cusped star. |
Hypotrochoid |
A pen fixed to a wheel rolling around the inside of a ring. |
Spirograph |
The toy, in the toy's own terms: a ring, a wheel and a hole. |
FlowerOfLife |
The seed of life continued outward: circles on a hexagonal grid. |
FruitOfLife |
The thirteen circles of the flower that touch without overlapping. |
GoldenRectangle |
A rectangle that keeps its shape when you cut a square off it. |
MetatronsCube |
Thirteen circles with every pair of middles joined by a line. |
SeedOfLife |
Seven circles: one in the middle, six around it, all the same size. |
SriYantra |
Nine interlocking triangles inside a circle, four pointing up and five down. |
VesicaPiscis |
Two circles, each through the other's middle. |
Cube |
Six squares. Dual to the octahedron, and the one everybody can check. |
Dodecahedron |
Twelve pentagons, built on a cube and the golden ratio. |
Icosahedron |
Twenty triangles: three golden rectangles at right angles to each other. |
Octahedron |
Eight triangles: a corner of the cube's every face, joined up. |
Polyhedron |
Any solid you like: your corners, your edges. |
PolyhedronBase |
Base for a solid: corners in space, joined and flattened onto the page. |
Projection |
How a corner in space becomes a point on the page. |
Tetrahedron |
Four triangles: the simplest solid there is, and its own dual. |
TruncatedIcosahedron |
The football: twelve pentagons and twenty hexagons. |
ArchimedeanSpiral |
The arithmetic spiral |
CircleInvolute |
The path of a string unwinding, taut, from a circle. |
EulerSpiral |
The clothoid: curvature increasing linearly with distance traveled. |
FermatSpiral |
The parabolic spiral |
FibonacciSpiral |
Quarter circles inscribed in a chain of Fibonacci squares. |
GoldenSpiral |
The logarithmic spiral that widens by the golden ratio each quarter turn. |
HyperbolicSpiral |
The reciprocal spiral |
Lituus |
The spiral |
LogarithmicSpiral |
The equiangular spiral |
SpiralBase |
Shared ground for spirals defined by a radius that grows with theta. |
SpiralBetween |
The endpoint-constrained arithmetic spiral: |
TheodorusSpiral |
The square-root spiral: a chain of right triangles, drawn exactly. |
StringArtCorner |
Two arms, laced so the far end of one meets the near end of the other. |
StringArtEnvelope |
The general engine: nail |
StringArtPolygon |
A strung corner at every corner of a regular polygon. |
SymmetricPointSet |
Points arranged by a symmetry group, spaced by iterative relaxation. |
AmmannBeenker |
The octagonal quasicrystal: squares and 45-degree rhombs, never repeating. |
CairoPentagonal |
The Cairo tiling: pentagons in fours, spinning like a pinwheel. |
HerringboneTiling |
Rectangles laid in chevrons, each one's end against the next one's side. |
HexagonalTiling |
The honeycomb: regular hexagons, three to a corner. |
PenroseP2 |
Penrose's kite and dart, the tiling that cannot repeat. |
PenroseP3 |
Penrose's rhombs: one thin, one thick, and no repeating pattern ever. |
PenroseTiling |
Shared scaffolding for the two Penrose tilings: the seed and the scale. |
RhombilleTiling |
Tumbling blocks: each hexagon split into three rhombi. |
RobinsonTriangle |
Half of a Penrose tile: an isosceles triangle in the complex plane. |
SnubSquare |
Squares and triangles, two of each at every corner -- the 3.3.4.3.4. |
SquareTiling |
Squares edge to edge: the graph paper of tilings. |
TriangularTiling |
Equilateral triangles, alternately point up and point down. |
TruchetTiling |
Quarter-circles in square cells, each turned at random. |
TruncatedSquare |
Octagons with small squares in the gaps -- the 4.8.8 tiling. |
Delaunay |
The triangulation that joins points whose Voronoi cells touch. |
LloydRelaxation |
Points nudged toward the middle of their own cells, over and over. |
Voronoi |
The map of which point is nearest, drawn as its borders. |
VoronoiCells |
The same map, drawn one closed region at a time. |
Astroid
dataclass
¶
Bases: ParametricMotif
The four-cusped star x**(2/3) + y**(2/3) == 1.
A hypocycloid with four cusps, and the envelope of a ladder sliding down a wall -- which is why it turns up in string art without anyone having set out to draw it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve: cusp to opposite cusp. |
100.0
|
center
|
(float, float)
|
The middle, equidistant from all four cusps. |
(0.0, 0.0)
|
BowCurve
dataclass
¶
Bases: ParametricMotif
The bow, x**4 == x**2*y - y**3: two loops pinched at the origin.
Rational all the way through -- x = t - t**3, y = t**2 - t**4 --
so it needs no trigonometry and no domain surgery.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve: the full width across both loops. |
100.0
|
center
|
(float, float)
|
The pinch point where the two loops meet. |
(0.0, 0.0)
|
Butterfly
dataclass
¶
Bases: ParametricMotif
Temple Fay's butterfly: twelve revolutions that draw two wings.
r = exp(cos(theta)) - 2*cos(4*theta) + sin(theta/12)**5. The last
term has twelve times the period of the rest, so the curve takes twelve
turns to close and each turn lays down a slightly different outline --
which is the whole trick, and why it is the one transcendental doodle
everybody recognizes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve. |
100.0
|
center
|
(float, float)
|
The body, which is where the pole sits. |
(0.0, 0.0)
|
Cardioid
dataclass
¶
Bases: ParametricMotif
The heart-shaped r = 1 + cos(theta): one circle rolled around another.
An epicycloid with a single cusp, and the shape of the bright caustic in
a coffee cup. Also the limiting case of :class:Limacon where the inner
loop has shrunk to a point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve. |
100.0
|
center
|
(float, float)
|
The cusp, which is where the curve's own origin sits. |
(0.0, 0.0)
|
CassiniOval
dataclass
¶
CassiniOval(a: float = 70.0, b: float = 80.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: MultiCurveMotif
Points whose distances to two foci multiply to a constant.
An ellipse adds those distances; a Cassini oval multiplies them, and the difference is a shape that changes topology as the constant crosses the focal separation. Cassini proposed it for planetary orbits and was wrong, which has not stopped it being the more interesting curve.
Three regimes, and the class draws each one honestly:
b > a-- a single closed loop, either an oval or, onceb < a*sqrt(2), the pinched peanut.b < a-- two separate loops, one around each focus, returned as two strokes rather than one path with an invented bridge between them.b == a-- the loops have just touched, and the curve is Bernoulli's lemniscate. Rejected here, because that case is :class:Lemniscateand it draws it better.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Half the distance between the foci, which sit at |
70.0
|
b
|
float
|
Square root of the constant product. Compare it to |
80.0
|
center
|
(float, float)
|
Midpoint between the foci. |
(0.0, 0.0)
|
Cochleoid
dataclass
¶
Cochleoid(size: float = 100.0, loops: int = 4, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The snail shell r = sin(theta) / theta, coiling in on itself.
Every loop touches the pole and every loop is smaller than the last, so
the whole family of them nests inside the first. The curve is symmetric
about the x-axis -- r(-theta) == r(theta) -- so it is drawn from
-loops turns to +loops, in one stroke through the point at
theta = 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve. Set by the outermost loop, so adding loops makes the picture busier rather than bigger. |
100.0
|
loops
|
int
|
Loops drawn on each side of the axis. |
4
|
center
|
(float, float)
|
The pole every loop passes through. |
(0.0, 0.0)
|
Cornoid
dataclass
¶
Bases: ParametricMotif
The cornoid, x = cos(t)cos(2t), y = sin(t)(2 + cos(2t)).
An oval with a pair of cusped loops tucked inside it, one near each end. A closed sextic, and one of the few named curves that looks like nothing else in the catalog.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve, along the oval. |
100.0
|
center
|
(float, float)
|
The middle, between the two inner loops. |
(0.0, 0.0)
|
Cycloid
dataclass
¶
Cycloid(radius: float = 40.0, arches: int = 3, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The path of a point on a rolling wheel's rim.
The brachistochrone and the tautochrone at once: the curve a bead slides down fastest, and the curve it takes the same time to slide down from anywhere. Seventeenth-century mathematicians fought over it enough for it to be nicknamed the Helen of geometers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of the rolling wheel. Each arch is |
40.0
|
arches
|
int
|
How many arches to roll out. |
3
|
center
|
(float, float)
|
Where the first arch touches the ground. |
(0.0, 0.0)
|
Deltoid
dataclass
¶
Bases: ParametricMotif
The three-cusped tricuspoid, Euler's curve of 1745.
The hypocycloid a wheel traces rolling inside a ring three times its size, and the shape of the caustic you get reflecting parallel light off the inside of a cup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve. |
100.0
|
center
|
(float, float)
|
The middle, equidistant from all three cusps. |
(0.0, 0.0)
|
FishCurve
dataclass
¶
Bases: ParametricMotif
A fish, tail and all, from x = cos(t) - sin(t)**2 / sqrt(2).
The negative pedal of an ellipse at a particular eccentricity, which is a dry way of saying that the tail crosses itself exactly where a tail should.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve, nose to tail. |
100.0
|
center
|
(float, float)
|
The curve's own origin, just behind the head. |
(0.0, 0.0)
|
Folium
dataclass
¶
Folium(a: float = 100.0, b: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The leaf curve r = cos(theta) * (4*a*sin(theta)**2 - b).
Three named shapes live in two numbers: b == a is the trifolium's
three petals, b == 4*a is the single-petalled simple folium, and
b == 0 is the bifolium's two. Anything between them is a legitimate
intermediate.
Half a revolution draws the whole thing. The other half retraces it,
because r(theta + pi) == -r(theta) and a negative radius lands back
on the ray it came from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Scale of the petals. |
100.0
|
b
|
float
|
Petal count, in effect: see the shapes listed above. |
100.0
|
center
|
(float, float)
|
The pole all the petals meet at. |
(0.0, 0.0)
|
Heart
dataclass
¶
Heart(size: float = 100.0, form: HeartForm = 'classic', center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
A heart, in either of the two shapes that go by the name.
"classic" is the valentine: the 16*sin(t)**3 curve, with the
dimple on top and a proper point at the bottom. "cardioid" is
r = 1 - sin(theta), which is the same cardioid as
:class:Cardioid stood on end -- rounder, symmetrical, and the one that
falls out of the maths rather than out of a greetings card.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve. |
100.0
|
form
|
('classic', 'cardioid')
|
Which heart to draw. |
"classic"
|
center
|
(float, float)
|
The curve's own origin, which for both forms is the dimple between the two lobes. |
(0.0, 0.0)
|
Lemniscate
dataclass
¶
Bases: ParametricMotif
Bernoulli's lemniscate: the infinity symbol.
The locus of points whose distances to two foci multiply to a constant --
the one case of :class:CassiniOval where the two lobes have just met.
Drawn from its rational parametrization rather than from
r**2 = a**2 * cos(2*theta), which goes imaginary over half its range
and would have to be stitched together from two arcs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve: the full width across both lobes. |
100.0
|
center
|
(float, float)
|
The crossing point in the middle. |
(0.0, 0.0)
|
LemniscateOfGerono
dataclass
¶
LemniscateOfGerono(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Gerono's lemniscate: the other figure eight, x**4 = x**2 - y**2.
Fatter and blunter than :class:Lemniscate, and far easier to say:
x = cos(t), y = sin(t)*cos(t). Worth having both -- they are
drawn interchangeably and they are not the same curve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve: the full width across both lobes. |
100.0
|
center
|
(float, float)
|
The crossing point in the middle. |
(0.0, 0.0)
|
Limacon
dataclass
¶
Limacon(a: float = 100.0, b: float = 60.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Pascal's snail, r = b + a*cos(theta), inner loop and all.
One knob spans a whole family. abs(a) > abs(b) gives the looped
limacon, whose inner loop is drawn correctly because a negative radius
reflects onto the opposite ray rather than being clipped away.
a == b is the :class:Cardioid. abs(b) >= 2*abs(a) is convex,
with not even a dimple left.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Amplitude of the cosine term: how lopsided the curve is. |
100.0
|
b
|
float
|
Constant term: the radius the curve would have if |
60.0
|
center
|
(float, float)
|
The pole the radius is measured from. |
(0.0, 0.0)
|
Nephroid
dataclass
¶
Bases: ParametricMotif
The kidney: the two-cusped epicycloid, r = 3cos(t) - cos(3t).
The caustic of a circle lit from infinity, which is the bright cusp of light in a teacup that everyone has seen and few have named.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Largest extent of the curve, across the two lobes. |
100.0
|
center
|
(float, float)
|
The middle, on the line joining the cusps. |
(0.0, 0.0)
|
Trochoid
dataclass
¶
Trochoid(radius: float = 40.0, arm: float = 60.0, arches: int = 3, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The path of a point fixed to a rolling wheel, on the rim or off it.
arm < radius is the curtate trochoid, the gentle wave a point inside
the wheel traces. arm > radius is the prolate one, whose overhanging
point runs backwards once per revolution and cuts a loop. arm ==
radius is exactly the :class:Cycloid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of the rolling wheel. |
40.0
|
arm
|
float
|
Distance from the wheel's center to the traced point. |
60.0
|
arches
|
int
|
How many revolutions to roll out. |
3
|
center
|
(float, float)
|
Where the wheel's center starts, projected onto the ground. |
(0.0, 0.0)
|
Witch
dataclass
¶
Witch(radius: float = 50.0, extent: float = 3.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The witch of Agnesi: a bell curve with an exact algebraic definition.
y = 8a**3 / (x**2 + 4a**2), constructed from a circle of radius a
sitting on the origin. Named a witch by a translator who mistook
versiera for avversiera; the curve has been stuck with it since.
It approaches its asymptote without reaching it, so it has to be cut off
somewhere -- that is what extent is for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of the generating circle. The peak sits at |
50.0
|
extent
|
float
|
How far out to draw, in units of the peak's height. The curve has
fallen to a fifth of its height by |
3.0
|
center
|
(float, float)
|
The point on the asymptote directly below the peak. |
(0.0, 0.0)
|
ApollonianGasket
dataclass
¶
ApollonianGasket(depth: int = 5, radius: float = 150.0, min_radius: float = 0.004, center: Point = (0.0, 0.0))
Bases: Motif
Circles packed into circles, filling every gap they leave.
Start with three circles that touch, drop the largest circle that fits in the gap between them, and repeat on the three new gaps. Apollonius of Perga posed the underlying problem; Descartes' circle theorem solves it in one line, which is what this uses.
The default packing is the integral gasket (-1, 2, 2, 3): every circle
in it has an integer curvature, which is a fact about this particular
starting configuration rather than about gaskets in general.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Rounds of gap-filling. Each round triples the number of gaps. |
5
|
radius
|
float
|
Radius of the outer circle. |
150.0
|
min_radius
|
float
|
Stop subdividing once a circle would be smaller than this fraction of the outer radius. Necessary as well as kind: the packing is infinite, and depth alone does not bound how small it gets. |
0.004
|
center
|
(float, float)
|
Middle of the outer circle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
circles |
Return every circle in the packing as a |
circles
¶
Return every circle in the packing as a (center, radius) pair.
Source code in src/geomotif/motifs/fractals.py
BarnsleyFern
dataclass
¶
Bases: Motif
Four affine maps and a coin, producing a fern.
The canonical iterated function system, and still the most persuasive argument that a very short description can encode a very complicated shape: twenty-four numbers, listed in the source, and the result has a stem, fronds and leaflets that were never drawn.
The same engine with different numbers is :class:IFSAttractor. This is
here because the fern is the example everybody arrives looking for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Points to plot. The fern needs tens of thousands before its leaflets fill in. |
40000
|
size
|
float
|
Largest extent of the finished plant, which is its height. |
300.0
|
seed
|
int
|
Seeds a private generator, so the same seed always gives the same fern. |
0
|
center
|
(float, float)
|
Middle of the plant's bounding box. |
(0.0, 0.0)
|
CantorSet
dataclass
¶
Bases: PolygonMotif
Take out the middle third. Repeat. Stack the rounds to see it happen.
The set that survives is uncountable and has length zero, which is the single most useful counterexample in analysis. Drawn the way it is always drawn: one row of bars per round, so the construction is visible rather than just its limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Rounds to draw. Round zero is the single unbroken bar, so |
6
|
width
|
float
|
Length of the first bar. |
240.0
|
gap
|
float
|
Vertical distance between consecutive rows. |
14.0
|
center
|
(float, float)
|
Middle of the whole stack. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
bar_count |
Return how many bars this stack draws in total. |
DragonCurve
dataclass
¶
Bases: LSystemMotif
The Heighway dragon: fold a strip of paper in half, repeatedly, unfold.
Every crease is a right angle and the curve never crosses itself, which is far from obvious at any depth past about six. Four dragons fit together around a point with no gap, so the shape tiles the plane.
GosperCurve
dataclass
¶
Bases: LSystemMotif
The flowsnake: a space-filling curve on a hexagonal grid.
Bill Gosper's curve fills a region whose own boundary is fractal -- the Gosper island, which tiles the plane in sevens. The most beautiful thing in this module, and it is two rewrite rules.
HilbertCurve
dataclass
¶
Bases: LSystemMotif
The space-filling curve that keeps neighbours together.
Hilbert's curve visits every cell of a square grid, and two cells close along the curve are always close on the plane. That property is why it is the standard order for laying out image tiles, database keys and anything else where locality has to survive being flattened to one dimension.
X and Y drive the rewriting without ever drawing: the turtle only
moves on F.
HTree
dataclass
¶
Bases: PolygonMotif
An H whose serifs are smaller H's, and so on down.
Each round adds a perpendicular segment at both ends of every existing one, shortened by the square root of two so the arms of successive levels stay in proportion. It is the standard layout for a clock distribution network on a chip, because every leaf is exactly the same wire length from the root.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Branching rounds. The segment count is |
6
|
size
|
float
|
Length of the first, longest bar. |
220.0
|
center
|
(float, float)
|
Middle of the tree, which is the middle of that first bar. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
segment_count |
Return how many bars this tree draws in total. |
IFSAttractor
dataclass
¶
IFSAttractor(maps: tuple[IFSMap, ...] = _GASKET_MAPS, count: int = 20000, size: float = 240.0, center: Point = (0.0, 0.0), *, seed: int = 0)
Bases: Motif
The chaos game: pick a map at random, apply it, plot the point, repeat.
Michael Barnsley's construction, and the most surprising thing in this module. A handful of contracting affine maps have exactly one compact set they leave unchanged, and iterating them at random converges onto it from any starting point whatsoever -- so the attractor draws itself without anyone ever computing where it is.
The default maps halve towards the corners of a triangle and produce the
Sierpinski gasket, which is the same set :class:SierpinskiTriangle
traces as a single stroke. Comparing the two is the cheapest way to see
what a fractal actually is, as opposed to how one is drawn.
Produces loose points rather than a stroke: the order they arrive in is random, so joining them would draw noise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
maps
|
tuple of IFSMap
|
The system to iterate. Each map should contract; the weights are relative and get normalized. |
_GASKET_MAPS
|
count
|
int
|
Points to plot. More fills the attractor in more finely, and costs linearly. |
20000
|
size
|
float
|
Largest extent of the finished cloud. The attractor is measured and rescaled, since its own coordinates depend on the maps. |
240.0
|
seed
|
int
|
Seeds a private generator, so the same seed always gives the same cloud and the design stays reproducible from its metadata. |
0
|
center
|
(float, float)
|
Middle of the cloud's bounding box. |
(0.0, 0.0)
|
IFSMap
dataclass
¶
IFSMap(transform: Affine, weight: float = 1.0)
One contracting map of an iterated function system, and how often to pick it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transform
|
Affine
|
The map itself. It must contract -- shrink distances -- or the chaos game runs away instead of settling onto an attractor. |
required |
weight
|
float
|
Relative probability of choosing this map. Weights are normalized, so only their ratios matter. Setting it near each map's area scale factor is what keeps the resulting cloud evenly dense. |
1.0
|
KochAntisnowflake
dataclass
¶
Bases: LSystemMotif
The snowflake with its spikes turned inward, which is a different shape.
One sign change in the rule, and the star becomes three bites taken out of
a triangle. Worth having next to :class:KochSnowflake precisely because
the grammars differ by so little and the results by so much.
KochCurve
dataclass
¶
Bases: LSystemMotif
The original: replace the middle third of every segment with a spike.
Helge von Koch's 1904 curve, published as an example of something continuous that has a tangent nowhere. Each round quadruples the segment count while thirding the length, so the curve between two fixed points grows without limit -- which is the whole point of it.
KochSnowflake
dataclass
¶
Bases: LSystemMotif
Three Koch curves around a triangle: finite area, infinite perimeter.
The perimeter multiplies by 4/3 every round and never stops; the area converges to exactly 8/5 of the starting triangle. Both facts are easy to check on the geometry this builds, and the test suite does.
LevyCCurve
dataclass
¶
Bases: LSystemMotif
Paul Levy's C curve: two half-size copies at right angles, forever.
It starts as a simple bracket and turns into a cauliflower. Unlike the dragons it overlaps itself freely, which is what gives the finished curve its dense, ruffled edge.
MinkowskiIsland
dataclass
¶
Bases: LSystemMotif
Four Minkowski sausages around a square: the quadratic Koch island.
What :class:KochSnowflake is to :class:KochCurve. The coastline never
stops growing while the area it encloses stays exactly that of the
starting square, which is the tidiest statement of the coastline paradox
there is.
MinkowskiSausage
dataclass
¶
Bases: LSystemMotif
Koch's idea on a square grid: a battlement instead of a spike.
Every segment becomes eight of a quarter the length, all of them axis aligned, which makes it the fractal of choice when the output has to look deliberate rather than organic. Eight pieces at a quarter scale puts its dimension at exactly three halves.
MooreCurve
dataclass
¶
Bases: LSystemMotif
Hilbert's curve closed into a loop.
Four Hilbert curves arranged so the walk returns to where it began, which makes it the space-filling curve to use when the traversal has to be cyclic rather than to start and stop somewhere.
PeanoCurve
dataclass
¶
Bases: LSystemMotif
The first space-filling curve ever published, from 1890.
Giuseppe Peano's construction predates Hilbert's by a year and divides the square into nine rather than four. It reads as a comb of combs, and it was the result that forced mathematics to take the idea of dimension seriously.
PythagorasTree
dataclass
¶
PythagorasTree(depth: int = 8, size: float = 60.0, lean: float = pi / 4.0, base: Point = (0.0, 0.0))
Bases: PolygonMotif
Squares on the legs of right triangles, all the way up.
Albert Bosman's 1942 construction, and a picture of the Pythagorean theorem rather than an illustration of it: the two child squares have the combined area of their parent at every level, whatever the lean, so the tree's total area grows by exactly one trunk per level.
lean is the angle at the left corner of the triangle sitting on each
square. A quarter of a right angle makes the symmetric tree; anything else
tips it, and values near zero or a right angle draw a fern-like frond.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Branching rounds. The square count is |
8
|
size
|
float
|
Side of the trunk square. |
60.0
|
lean
|
float
|
Apex angle of the triangle, in radians. Must be strictly between zero and a right angle. |
pi / 4.0
|
base
|
(float, float)
|
Midpoint of the trunk's bottom edge -- where the tree stands. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
square_count |
Return how many squares this tree draws in total. |
SierpinskiArrowhead
dataclass
¶
Bases: LSystemMotif
The gasket approached along a curve instead of through a triangle.
Two rules that swap roles at every round, tracing an open path from one
corner of the gasket to another. The limit is the same set as
:class:SierpinskiTriangle, reached by a route that looks nothing like
it at any finite depth.
SierpinskiCarpet
dataclass
¶
Bases: PolygonMotif
A square with its middle ninth removed, then again in each ninth.
The two-dimensional Cantor set, and the shape whose limit contains a copy of every possible one-dimensional curve. Drawn as outlines: the outer square plus every hole cut out of it, which is both what a plotter wants and what makes the construction legible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Rounds of subdivision. The hole count is |
4
|
size
|
float
|
Side of the outer square. |
200.0
|
center
|
(float, float)
|
Middle of the carpet. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
hole_count |
Return how many holes this carpet has at its depth. |
SierpinskiTriangle
dataclass
¶
Bases: LSystemMotif
The gasket, drawn as one unbroken stroke that closes on itself.
The triangle with its middle removed, then again, forever. Drawing it without lifting the pen is the trick the grammar performs: the naive construction is a pile of separate triangles, while this is a single closed path that happens to trace all of them.
The same set of points falls out of :class:IFSAttractor played as a
chaos game -- one arrives as a stroke, the other as a cloud.
Terdragon
dataclass
¶
Bases: LSystemMotif
The dragon done in thirds: one segment becomes three at 120 degrees.
Threefold rather than twofold symmetry, and a much lacier result than the Heighway curve for the same number of segments.
TwinDragon
dataclass
¶
Bases: LSystemMotif
Two Heighway dragons back to back, enclosing a region that tiles.
The Davis-Knuth dragon. Joining the pair closes the curve, and the region inside it is the fundamental domain of the base -1+i number system -- the reason this shape turns up in radix arithmetic as well as in art.
VicsekFractal
dataclass
¶
Bases: LSystemMotif
The box fractal: a plus sign made of plus signs.
Tamas Vicsek's construction, which keeps only the center and the four edge-midpoints of each subdivided square. The saltire cross the outline traces is what stochastic versions of it are used to model -- percolation clusters and diffusion fronts.
GirihTile
dataclass
¶
GirihTile(shape: GirihShape = 'decagon', size: float = 60.0, contact: float = GIRIH_CONTACT, rotation: float = 0.0, center: Point = (0.0, 0.0), *, strapwork: bool = True, outline: bool = True)
Bases: Motif
One of the five girih tiles, with the strapwork it carries.
All five have the same side length and only angles that are multiples of 36 degrees, which is what lets them be shuffled freely -- and what makes the strapwork of one line up with the strapwork of its neighbour.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shape
|
str
|
One of |
'decagon'
|
size
|
float
|
Side length, shared by every tile. |
60.0
|
contact
|
float
|
Angle the strapwork makes with each edge, in radians. |
GIRIH_CONTACT
|
strapwork
|
bool
|
Draw the lines the tile carries. |
True
|
outline
|
bool
|
Draw the tile itself. In finished work the tile is rubbed out and only the strapwork remains. |
True
|
rotation
|
float
|
Turn the tile, in radians. |
0.0
|
center
|
(float, float)
|
Middle of the tile. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
corners |
Return the tile's corners, turned and placed. |
corners
¶
Return the tile's corners, turned and placed.
Source code in src/geomotif/motifs/girih.py
HexStarLattice
dataclass
¶
HexStarLattice(size: float = 30.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Six-pointed stars on a triangular lattice, rhombi filling the gaps.
The six-fold pattern that needs no strapwork: the stars and the rhombi are already the design. Three rhombi to a star, and the star takes two thirds of the plane -- the tests check that by area rather than by eye.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Edge length, shared by the star and the rhombi. |
30.0
|
InterlockingDecagons
dataclass
¶
InterlockingDecagons(size: float = 26.0, contact: float = GIRIH_CONTACT, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
The pattern the tenfold tiling carries: ten-pointed stars, interlocked.
Hankin's rule applied to :class:TenfoldGirih. Each decagon turns into a
ten-pointed star and each bowtie into the straps that tie four of them
together. The tiles themselves are gone -- the lines cross their edges
dead straight, because both tiles meet the shared midpoint at the same
angle from opposite sides, so the two halves are exactly opposite.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Side length of the tiles underneath. |
26.0
|
contact
|
float
|
Angle the strapwork makes with each edge, in radians. Turning it down makes the stars sharper and the pattern more open. |
GIRIH_CONTACT
|
Rosette
dataclass
¶
Rosette(points: int = 12, sharpness: int = 3, layers: int = 3, radius: float = 140.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0))
Bases: PolygonMotif
The shamsa: a star, a blunter star inside it, and so on inward.
Each layer is the outline of the {points/sharpness} star polygon. The
next layer in is turned half a step and scaled so that its points land
exactly in the valleys of the one outside it, which is the proportion the
figure is built on: cos(k*pi/n) / cos((k-1)*pi/n), the same ratio that
gives the pentagram its 1/phi**2. The innermost valleys are joined by
a plain polygon, which is where the tilework usually puts a boss.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
int
|
Points on each star. |
12
|
sharpness
|
int
|
The |
3
|
layers
|
int
|
How many stars, counting outward from the middle. |
3
|
radius
|
float
|
Circumradius of the outermost star. |
140.0
|
rotation
|
float
|
Angle of the outermost star's first point. Defaults to straight up. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the rosette. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
star |
Return the corners of one layer, points and valleys alternating. |
Attributes:
| Name | Type | Description |
|---|---|---|
nesting |
float
|
Radius of one layer as a fraction of the layer outside it. |
star
¶
Return the corners of one layer, points and valleys alternating.
Source code in src/geomotif/motifs/girih.py
RosetteTiling
dataclass
¶
RosetteTiling(points: int = 6, sharpness: int = 2, layers: int = 2, radius: float = 45.0, lattice: Literal['hex', 'square'] = 'hex', *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
A field of rosettes, touching point to point.
What a whole wall looks like rather than one medallion. The lattice is square or triangular; on the triangular one a six-pointed rosette meets its neighbours at every point, which is the arrangement most tiled courtyards use.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
int
|
Passed straight to :class: |
6
|
sharpness
|
int
|
Passed straight to :class: |
6
|
layers
|
int
|
Passed straight to :class: |
6
|
radius
|
float
|
Circumradius of each rosette. Neighbours are two radii apart, so their points touch. |
45.0
|
lattice
|
str
|
|
'hex'
|
Methods:
| Name | Description |
|---|---|
unit |
Return the rosette this tiling repeats. |
TenfoldGirih
dataclass
¶
TenfoldGirih(size: float = 26.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Regular decagons laid edge to edge, with a bowtie in every gap.
The girih tiling a craftsman would chalk on the wall before drawing anything: two of the five tiles, repeating. The decagons touch along four of their ten edges and the bowtie fills what is left, which it does exactly, since it was cut from the same set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Side length, shared by both tiles. |
26.0
|
BipartiteGraph
dataclass
¶
BipartiteGraph(left: int = 4, right: int = 5, span: float = 200.0, height: float = 220.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Two rows of nodes, and every line from one row to the other.
The complete bipartite graph, drawn the way it is drawn in textbooks: two
facing ranks with all left * right connections between them. Every
crossing is visible, which is what makes it useful for showing why K33
cannot be drawn without them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
int
|
Nodes in each rank. |
4
|
right
|
int
|
Nodes in each rank. |
4
|
span
|
float
|
Horizontal distance between the two ranks. |
200.0
|
height
|
float
|
Vertical extent of each rank. |
220.0
|
center
|
(float, float)
|
Middle of the whole figure. |
(0.0, 0.0)
|
ChordDiagram
dataclass
¶
ChordDiagram(order: int = 16, chords: tuple[tuple[int, int], ...] = _EXAMPLE_CHORDS, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = True)
Bases: SegmentMotif
Nodes on a circle and whichever chords you name between them.
The escape hatch of the family, and the one to reach for when the connections come from data rather than from arithmetic -- a dependency graph, a migration table, who talks to whom. The other classes in this module are this one with the chord list computed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Number of nodes around the circle. |
16
|
chords
|
tuple of (int, int)
|
Index pairs to join. Self-loops and repeats are dropped rather than rejected, since a real dataset routinely contains both. |
_EXAMPLE_CHORDS
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
show_nodes
|
bool
|
Emit the nodes as loose points. On by default here and nowhere else: an arithmetic rule fills the circle densely enough to imply where its nodes are, while a handful of chords from a dataset does not, and without the dots the figure reads as a pile of sticks. |
True
|
CompleteGraph
dataclass
¶
CompleteGraph(order: int = 12, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Every node joined to every other: K5, K12, and the ones in between.
The picture mathematicians draw when they say "complete graph", and a
surprisingly good ornament -- the chords cross in a moire that tightens
towards the middle. The edge count is order * (order - 1) / 2, so it
grows quadratically and gets solid black somewhere around forty nodes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Number of nodes. |
12
|
radius
|
float
|
Radius of the circle they sit on. |
120.0
|
rotation
|
float
|
Angle of the first node, in radians. A quarter turn puts it at the top, which is how these are conventionally drawn. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
edge_count |
Return how many chords this graph draws. |
CyclicGraph
dataclass
¶
CyclicGraph(order: int = 16, steps: tuple[int, ...] = (1, 3, 5), radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
The circulant: join every node to the ones a fixed number of steps away.
One step is the plain cycle, which is a regular polygon. Several steps at
once is where it gets interesting -- each one contributes its own star
polygon and they overlay into a rosette. steps=(1, 2, 3) on a dozen
nodes is a good place to start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Number of nodes. |
16
|
steps
|
tuple of int
|
How far around to reach. Each step |
(1, 3, 5)
|
radius
|
float
|
Radius of the circle the nodes sit on. |
120.0
|
rotation
|
float
|
Angle of the first node, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
ModularAddition
dataclass
¶
ModularAddition(modulus: int = 120, addend: int = 37, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Join each number to the one a fixed distance further round.
The times table's sibling, and the plainer of the two: adding a constant
steps around the circle at a constant rate, so the result is the star
polygon {modulus/addend} -- one loop if the two are coprime, several
interleaved ones if they are not.
That makes it the same set of lines :class:~geomotif.motifs.primitives.StarPolygon
draws, and it is here because the family reads wrong without it: seeing
how little the addition version does is what makes the multiplication
version surprising. Reach for StarPolygon when you want the star
itself, and for this when you are exploring the arithmetic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
modulus
|
int
|
How many numbers go around the circle. |
120
|
addend
|
int
|
How far each step reaches. |
37
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
loop_count |
Return how many separate loops the walk falls into. |
ModularMultiplication
dataclass
¶
ModularMultiplication(modulus: int = 200, factor: int = 2, radius: float = 120.0, rotation: float = pi, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
The times table drawn as chords, which turns out to be a cardioid.
Space the numbers 0 to modulus - 1 evenly around a circle and join
each i to factor * i. Doubling gives a cardioid, tripling a
nephroid, and every factor after that an epicycloid with one fewer cusp
than the factor -- none of which is put there deliberately. The cusps are
the envelope of the chords, and the whole family falls out of one line of
arithmetic.
Seen from the other direction this is circle string art, which is why
:mod:geomotif.motifs.stringart re-exports it as StringArtCircle
rather than drawing the same chords twice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
modulus
|
int
|
How many numbers go around the circle. |
200
|
factor
|
int
|
What each is multiplied by. Two for the cardioid, three for the nephroid, and large primes for the dense figures. |
2
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
cusp_count |
Return how many cusps the chord envelope has: one fewer than the factor. |
cusp_count
¶
PrimeChords
dataclass
¶
PrimeChords(limit: int = 60, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Join two numbers whenever they add up to a prime.
Space the whole numbers below limit around a circle and draw a chord
between every pair whose sum is prime. The result is a dense, oddly
orderly web, and every feature in it is a fact about primes rather than a
decision about drawing: no chord ever joins two even numbers or two odd
ones, because their sum would be even, so the figure is bipartite and the
ring of alternating nodes shows it. The one exception is the pair summing
to two, which is why zero-to-two is the only even-even chord in the
picture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit
|
int
|
Numbers to place around the circle, from |
60
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
GuillocheBand
dataclass
¶
GuillocheBand(length: float = 320.0, height: float = 76.0, waves: float = 5.0, counter: float = 8.0, lines: int = 18, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: MultiCurveMotif
A woven ribbon along a straight spine: the border of a banknote.
Each stroke is the same two-frequency wave at a different phase, so the stack fills a band of constant height with a pattern that never quite repeats along its length.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
float
|
Length of the spine. |
320.0
|
height
|
float
|
Full height of the band. |
76.0
|
waves
|
float
|
Cycles of the forward wave along the whole length. |
5.0
|
counter
|
float
|
Cycles of the backward wave. |
8.0
|
lines
|
int
|
How many strokes to stack. Their phases divide one full cycle evenly. |
18
|
center
|
(float, float)
|
Middle of the band. |
(0.0, 0.0)
|
GuillochePattern
dataclass
¶
GuillochePattern(radius: float = 150.0, layers: int = 12, petals: float = 11.0, border_lines: int = 16, border_height: float = 30.0, border_waves: float = 36.0, center: Point = (0.0, 0.0), *, rules: bool = True)
Bases: Motif
A rosette inside a woven border, with rules between: the banknote layout.
A composition rather than a curve of its own: two
:class:GuillocheRosette instances, one broad and slow for the middle and
one narrow and fast for the border, with a plain circle ruled either side
of the border to close it off. A band bent into a ring is a rosette, so
there is no third shape to write.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Outer radius, to the middle of the border. |
150.0
|
layers
|
int
|
Strokes in the central rosette. |
12
|
petals
|
float
|
Waves per revolution in the central rosette. |
11.0
|
border_lines
|
int
|
Strokes in the border. |
16
|
border_height
|
float
|
Full width of the border band. |
30.0
|
border_waves
|
float
|
Waves around the border. |
36.0
|
rules
|
bool
|
Draw the two plain circles that fence the border in. |
True
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
rosette |
Return the central rosette, sized to sit inside the border. |
border |
Return the border, which is a rosette with a tight, fast wave. |
rosette
¶
rosette() -> GuillocheRosette
Return the central rosette, sized to sit inside the border.
Source code in src/geomotif/motifs/guilloche.py
border
¶
border() -> GuillocheRosette
Return the border, which is a rosette with a tight, fast wave.
Source code in src/geomotif/motifs/guilloche.py
GuillocheRosette
dataclass
¶
GuillocheRosette(radius: float = 96.0, amplitude: float = 30.0, petals: float = 13.0, counter: float = 5.0, layers: int = 14, spread: float = 2.6, twist: float = 0.42, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: MultiCurveMotif
A stack of rosettes, each turned a little further than the last.
The wave runs around a circle instead of along a line, so every stroke
closes. Successive layers step outward by :attr:spread and forward by
:attr:twist, and where the two rates disagree the lines braid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of the innermost layer's spine. |
96.0
|
amplitude
|
float
|
How far the wave carries a stroke off its spine. |
30.0
|
petals
|
float
|
Waves per revolution. Whole numbers close cleanly. |
13.0
|
counter
|
float
|
Waves per revolution of the backward-running second frequency. |
5.0
|
layers
|
int
|
How many strokes to stack. |
14
|
spread
|
float
|
Radial step between layers. |
2.6
|
twist
|
float
|
Phase step between layers, in radians. This is what turns a stack of identical rings into a weave. |
0.42
|
center
|
(float, float)
|
Middle of the rosette. |
(0.0, 0.0)
|
CafeWall
dataclass
¶
CafeWall(cols: int = 8, rows: int = 6, size: float = 40.0, mortar: float = 3.0, shift: float = 0.5, hatch: int = 4, center: Point = (0.0, 0.0))
Bases: Motif
Parallel mortar lines that refuse to look parallel.
Rows of tiles, every other row shifted sideways, with a line of mortar between them. The dark tiles are hatched rather than filled, which is what a plotter can draw -- and the illusion needs only the contrast, not the ink. Every mortar line is exactly horizontal; none of them looks it.
Named for a cafe in Bristol whose tiling did this to passers-by.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cols
|
int
|
How many tiles across and down. |
8
|
rows
|
int
|
How many tiles across and down. |
8
|
size
|
float
|
Side of one tile. |
40.0
|
mortar
|
float
|
Gap between rows. |
3.0
|
shift
|
float
|
How far every other row is displaced, as a fraction of a tile. The illusion is strongest around a quarter to a half. |
0.5
|
hatch
|
int
|
Lines drawn across each dark tile. |
4
|
center
|
(float, float)
|
Middle of the wall. |
(0.0, 0.0)
|
ImpossibleCube
dataclass
¶
ImpossibleCube(size: float = 180.0, depth: float = 0.45, angle: float = pi / 4.0, center: Point = (0.0, 0.0), gap: float = 0.06)
Bases: _CubeBase
The same cube, told two contradictory things about which face is nearer.
The near and far faces cross each other twice. At one crossing the far edge is broken, which says the near face is in front; at the other the near edge is broken, which says the opposite. Either break alone would be an ordinary solid cube; together they are Escher's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
As :class: |
180.0
|
depth
|
float
|
As :class: |
180.0
|
angle
|
float
|
As :class: |
180.0
|
center
|
float
|
As :class: |
180.0
|
gap
|
float
|
Length of the break, as a fraction of the size. |
0.06
|
MoirePattern
dataclass
¶
MoirePattern(kind: MoireKind = 'rings', count: int = 34, spacing: float = 6.0, offset: float = 26.0, angle: float = 0.06, center: Point = (0.0, 0.0))
Bases: Motif
Two regular patterns laid over each other, and the fringes between them.
Nothing draws the fringes. They are where the two patterns nearly agree, and they move much faster than either pattern does -- shift one grating by a hair and the bands sweep across the whole figure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
'rings'
|
count
|
int
|
Lines or circles in each of the two patterns. |
34
|
spacing
|
float
|
Distance between neighbouring lines or circles. |
6.0
|
offset
|
float
|
How far apart the two patterns' middles are. |
26.0
|
angle
|
float
|
How far the second pattern is turned, in radians. A very small angle
gives very wide fringes. Concentric circles look the same however far
you turn them, so |
0.06
|
center
|
(float, float)
|
Middle of the first pattern. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
family |
Return one of the two patterns, placed and turned. |
family
¶
family(at: Point, turn: float) -> tuple[Path, ...]
Return one of the two patterns, placed and turned.
Source code in src/geomotif/motifs/illusions.py
NeckerCube
dataclass
¶
NeckerCube(size: float = 180.0, depth: float = 0.45, angle: float = pi / 4.0, center: Point = (0.0, 0.0))
Bases: _CubeBase
A wireframe cube with nothing to say which face is in front.
All twelve edges drawn, none broken. Louis Necker noticed in 1832 that the same drawing flips between two solid cubes as you look at it, and it does so because nothing in it is wrong -- the drawing is simply true of both.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Width of the finished figure. |
180.0
|
depth
|
float
|
How far the far face is offset, as a fraction of the near face's width. |
0.45
|
angle
|
float
|
Which way it is offset, in radians. |
pi / 4.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
PenroseStairs
dataclass
¶
PenroseStairs(steps: int = 5, rise: float = 0.4, width: float = 1.8, size: float = 280.0, center: Point = (0.0, 0.0))
Bases: Motif
The endless staircase: four flights, every step up, back where you began.
Built in space and then flattened, rather than drawn flat and fudged. Four
flights of equal step count run round a rectangle, each step rising by
rise; after a full circuit the walk has failed to close by exactly
(t, t, t), and an isometric view sends that to nothing. Two opposite
flights have to be longer than the other two by four times the rise for the
error to come out equal on all three axes -- which is why a real drawing of
this staircase is never quite square.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steps
|
int
|
Steps per flight. |
5
|
rise
|
float
|
Height of one step, in units of the short flight's tread. |
0.4
|
width
|
float
|
How deep each tread is, in the same units. |
1.8
|
size
|
float
|
Width of the finished figure. |
280.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
walk |
Return the corners of the stepped band's outer edge, in space. |
walk
¶
Return the corners of the stepped band's outer edge, in space.
Three points per step: the foot of the riser, its top, and the far end of the tread.
Source code in src/geomotif/motifs/illusions.py
PenroseTriangle
dataclass
¶
Bases: Motif
The tribar: three square beams meeting at three right angles.
Each beam is drawn as the silhouette of a long cuboid seen isometrically, and the three are the same beam turned by a third of a revolution. Every beam passes in front of the next one round, which is the whole trick: locally each joint is an ordinary right angle, and following them round gets you back underneath where you started.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Width of the finished figure. |
240.0
|
thickness
|
float
|
Beam width as a fraction of its length. Thin beams give the spidery version, fat ones the chunky Escher version. |
0.25
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
beams |
Return the three beams as their outlines, before any is hidden. |
beams
¶
Return the three beams as their outlines, before any is hidden.
Source code in src/geomotif/motifs/illusions.py
CelticGrid
dataclass
¶
CelticGrid(cols: int = 4, rows: int = 3, size: float = 60.0, breaks: tuple[tuple[int, int, str], ...] = (), roundness: float = 0.55, gap: float = 0.14, center: Point = (0.0, 0.0))
Bases: Motif
The plait: strands at 45 degrees, bouncing off the frame and off barriers.
The construction every Celtic panel is built on. Strands set off diagonally
across a grid of cols by rows cells and turn wherever they meet the
frame; wherever they meet each other they weave. Place a barrier inside and
the strands turn there too, which is how one plait becomes a thousand
different knots -- the breaks are the design.
Barriers sit on the half-cell grid, whose coordinates run from 0 to
2 * cols across and 0 to 2 * rows up. A barrier must be strictly
inside and its two coordinates must add to an odd number, which is where
the strands actually go.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cols
|
int
|
Size of the panel, in cells. |
4
|
rows
|
int
|
Size of the panel, in cells. |
4
|
size
|
float
|
Side of one cell. |
60.0
|
breaks
|
tuple
|
Barriers, each |
()
|
roundness
|
float
|
How much the turns are curved, as a fraction of the half-cell. |
0.55
|
gap
|
float
|
Length of the break in the under-strand, as a fraction of the cell. |
0.14
|
center
|
(float, float)
|
Middle of the panel. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
turns |
Return each strand as the grid nodes where it changes direction. |
loops |
Return the strands as closed polylines, corners rounded. |
turns
¶
Return each strand as the grid nodes where it changes direction.
Only the corners: between two of them the strand runs dead straight, so listing every node it passes through would add points a plotter would draw over anyway -- and would put a crossing exactly on a vertex, where it is far harder to find.
The strands never reach the four corners of the frame, where they could only turn back on themselves: a corner's coordinates add to an even number, and every strand lives on the odd ones.
Source code in src/geomotif/motifs/knots.py
loops
¶
Return the strands as closed polylines, corners rounded.
Source code in src/geomotif/motifs/knots.py
CircularCelticKnot
dataclass
¶
CircularCelticKnot(radius: float = 110.0, amplitude: float = 34.0, lobes: int = 5, wraps: int = 2, resolution: int = 48, gap: float = 0.06, center: Point = (0.0, 0.0))
Bases: Motif
One strand wound several times round a ring, weaving with itself.
The radius rises and falls as the strand goes round, and because it wraps
more than once the turns swap places and cross. With wraps and
lobes sharing no factor the whole thing is a single closed strand --
the circular knot cut into stone crosses, and, read as a knot, a torus
knot's standard diagram.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Mean radius of the ring. |
110.0
|
amplitude
|
float
|
How far the strand wanders in and out. |
34.0
|
lobes
|
int
|
Cycles of that wandering over the whole journey. |
5
|
wraps
|
int
|
Times the strand goes round. Must share no factor with |
2
|
resolution
|
int
|
Samples per lobe. |
48
|
gap
|
float
|
Length of the break in the under-strand, as a fraction of the radius. |
0.06
|
center
|
(float, float)
|
Middle of the ring. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
loop |
Return the strand as one closed polyline, before it is broken. |
loop
¶
Return the strand as one closed polyline, before it is broken.
EndlessKnot
dataclass
¶
EndlessKnot(size: float = 220.0, roundness: float = 0.4, gap: float = 0.02, center: Point = (0.0, 0.0))
Bases: Motif
The endless knot: four strands each way, woven, with their ends looped back.
A plain over-and-under weave of four horizontal and four vertical strands, whose sixteen loose ends are joined in pairs round the four corners. The joining is what the name is about -- get it wrong and the figure falls into two closed rings; get it right and it is one strand with no beginning, which is the whole point of the symbol.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Width of the finished figure. |
220.0
|
roundness
|
float
|
How much the corners are curved, as a fraction of the strand spacing. |
0.4
|
gap
|
float
|
Length of the break in the under-strand, as a fraction of the size. |
0.02
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
loop |
Return the single closed strand, corners rounded, before it is broken. |
loop
¶
Return the single closed strand, corners rounded, before it is broken.
Source code in src/geomotif/motifs/knots.py
SquareCelticKnot
dataclass
¶
SquareCelticKnot(size: float = 220.0, amplitude: float = 24.0, lobes: int = 8, wraps: int = 3, squareness: float = 6.0, resolution: int = 48, gap: float = 0.035, center: Point = (0.0, 0.0))
Bases: Motif
The same winding, but round a square frame instead of a ring.
The spine is a squircle, so the strand runs straight down each side and turns the corner without a kink -- the shape of a knotwork panel border.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Width of the frame, across the middle of the strand. |
220.0
|
amplitude
|
float
|
How far the strand wanders in and out. |
24.0
|
lobes
|
int
|
Cycles of that wandering over the whole journey. |
8
|
wraps
|
int
|
Times the strand goes round. Must share no factor with |
3
|
squareness
|
float
|
How square the frame is: 2 is a circle, and higher is squarer. |
6.0
|
resolution
|
int
|
Samples per lobe. |
48
|
gap
|
float
|
Length of the break in the under-strand, as a fraction of the size. |
0.035
|
center
|
(float, float)
|
Middle of the frame. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
spine |
Return the frame's radius at |
loop |
Return the strand as one closed polyline, before it is broken. |
spine
¶
Return the frame's radius at theta: a squircle, not a circle.
Source code in src/geomotif/motifs/knots.py
loop
¶
Return the strand as one closed polyline, before it is broken.
Triquetra
dataclass
¶
Triquetra(radius: float = 90.0, gap: float = 0.07, rotation: float = -pi / 2.0, center: Point = (0.0, 0.0), *, ring: bool = False)
Bases: Motif
The trinity knot: three arcs that turn out to be one strand.
Three circles whose middles are one radius apart, each cut down to the half that faces the other two. Those three half-circles join end to end -- the joins are where the circles meet on the far side -- so the figure is a single closed strand crossing itself three times, which is to say a trefoil, drawn the way it was drawn on stone crosses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of each of the three circles. |
90.0
|
gap
|
float
|
Length of the break in the under-strand, as a fraction of the radius. |
0.07
|
rotation
|
float
|
Turn the figure, in radians. Defaults to one lobe pointing up. |
-pi / 2.0
|
ring
|
bool
|
Draw the enclosing circle the knot is often set in, woven through the three lobes. It has the same radius as they do. |
False
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
centers |
Return the three circle middles, one radius apart from each other. |
loops |
Return the closed curves the knot is woven from. |
centers
¶
Return the three circle middles, one radius apart from each other.
loops
¶
Return the closed curves the knot is woven from.
Source code in src/geomotif/motifs/knots.py
Harmonic
dataclass
¶
Harmonic(x_terms: tuple[tuple[float, float, float], ...] = ((100.0, 1.0, 0.0), (40.0, 5.0, 0.0)), y_terms: tuple[tuple[float, float, float], ...] = ((100.0, 1.0, pi / 2.0), (40.0, 5.0, pi / 2.0)), center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Sums of sines on each axis: :class:Lissajous with more terms.
Each term is (amplitude, frequency, phase), and the axes are
independent: matching term for term across the two gives a clean
rosette, mismatching them gives a knot, and a single fast term against a
slow one gives a ribbon with a ripple in it::
Harmonic(
x_terms=((100.0, 1.0, 0.0),),
y_terms=((100.0, 3.0, 0.0), (40.0, 17.0, 0.0)),
)
The frequencies are whole numbers, because that is what makes the figure close.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x_terms
|
tuple of (float, float, float)
|
The sine terms driving each axis. At least one each. |
((100.0, 1.0, 0.0), (40.0, 5.0, 0.0))
|
y_terms
|
tuple of (float, float, float)
|
The sine terms driving each axis. At least one each. |
((100.0, 1.0, 0.0), (40.0, 5.0, 0.0))
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Harmonograph
dataclass
¶
Harmonograph(x_pendulums: tuple[Pendulum, ...] = (Pendulum(140.0, 2.0, 0.0, 0.006), Pendulum(60.0, 3.0, pi / 4.0, 0.0018)), y_pendulums: tuple[Pendulum, ...] = (Pendulum(140.0, 2.005, pi / 2.0, 0.006), Pendulum(60.0, 4.0, 0.0, 0.0018)), duration: float = 50.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The Victorian drawing machine: swinging pendulums, slowly running down.
Two pendulums per axis, each decaying, and a pen where their motions
meet. What makes the figure rather than a scribble is detuning: set two
frequencies to 2.0 and 2.005 and the loops precess a little on
every pass, so the curve fills a band instead of retracing itself. The
damping is what closes the spiral inwards and ends the drawing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x_pendulums
|
tuple of Pendulum
|
What drives each axis. At least one each; two is the classic machine. |
(Pendulum(140.0, 2.0, 0.0, 0.006), Pendulum(60.0, 3.0, pi / 4.0, 0.0018))
|
y_pendulums
|
tuple of Pendulum
|
What drives each axis. At least one each; two is the classic machine. |
(Pendulum(140.0, 2.0, 0.0, 0.006), Pendulum(60.0, 3.0, pi / 4.0, 0.0018))
|
duration
|
float
|
How long to let it swing. Longer means a denser figure, up to the point where the damping has stopped it. |
50.0
|
center
|
(float, float)
|
Where the pen rests once everything has settled. |
(0.0, 0.0)
|
Lissajous
dataclass
¶
Lissajous(a: int = 3, b: int = 2, delta: float = pi / 2.0, width: float = 200.0, height: float = 200.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Two perpendicular oscillations, plotted against each other.
What an oscilloscope draws with a signal on each axis, and how frequency
ratios were measured before there was anything better: the figure is
stable only when the ratio is exactly rational, and it stands still only
when the phase is too. a = b degenerates to an ellipse, and to a
circle when delta is a quarter turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
int
|
Frequencies on x and y. Whole numbers, because the figure closes only when their ratio is rational. |
3
|
b
|
int
|
Frequencies on x and y. Whole numbers, because the figure closes only when their ratio is rational. |
3
|
delta
|
float
|
Phase offset applied to x, in radians. |
pi / 2.0
|
width
|
float
|
Full extent on each axis. |
200.0
|
height
|
float
|
Full extent on each axis. |
200.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
MaurerRose
dataclass
¶
Bases: PolygonMotif
A rose walked in whole-degree steps and joined by straight chords.
Peter Maurer's construction, and the best return on effort in the whole
catalog: take the points of a rose at 0, degrees, 2*degrees
and so on, join them with straight lines, and the chords weave a
filigree the underlying curve gives no hint of. Change degrees by one
and the whole pattern reorganizes.
This is a :class:~geomotif.PolygonMotif rather than a curve: the
vertices are the design, and measuring the chords at even parameters
would round off every corner that makes the pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Petal frequency of the underlying rose, |
6
|
degrees
|
int
|
Whole degrees per step. Coprime with 360 gives the full 360-chord figure; a common factor closes the walk early on a coarser one. |
71
|
size
|
float
|
Petal length of the underlying rose. |
100.0
|
center
|
(float, float)
|
Where the petals meet. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
chord_count |
Return how many chords the walk takes before it closes. |
chord_count
¶
Pendulum
dataclass
¶
Pendulum(amplitude: float = 100.0, frequency: float = 2.0, phase: float = 0.0, damping: float = 0.006)
One swinging weight of a :class:Harmonograph.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
amplitude
|
float
|
How far it swings at the start. |
100.0
|
frequency
|
float
|
Radians per unit of time. Two pendulums at almost the same frequency are what makes a harmonograph drift instead of repeat. |
2.0
|
phase
|
float
|
Where in its swing it is released, in radians. |
0.0
|
damping
|
float
|
Exponential decay per unit of time. Zero never settles. |
0.006
|
Methods:
| Name | Description |
|---|---|
at |
Return this pendulum's displacement at time |
at
¶
Return this pendulum's displacement at time t.
Phyllotaxis
dataclass
¶
Phyllotaxis(count: int = 500, spacing: float = 8.0, angle: float = GOLDEN_ANGLE, center: Point = (0.0, 0.0))
Bases: Motif
The sunflower head: r = c*sqrt(n) at n golden angles.
Vogel's model of how a plant packs seeds, and the best dot art in the library for the least work. The square root keeps the density even from the middle to the rim; the golden angle keeps consecutive seeds from ever lining up, which is why the spiral arms you see are an illusion of the packing rather than anything the formula mentions. Their count is always a Fibonacci number.
Produces loose points rather than a stroke: joining them in order would draw a line no sunflower has.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Number of seeds. |
500
|
spacing
|
float
|
Scale factor. The head's radius works out at |
8.0
|
angle
|
float
|
Turn between consecutive seeds, in radians. Defaults to
:data: |
GOLDEN_ANGLE
|
center
|
(float, float)
|
Middle of the head. |
(0.0, 0.0)
|
PolarExpression
dataclass
¶
PolarExpression(formula: Callable[[float], float] = _ripple, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = 0.0, theta_span: float = tau)
Bases: PolarMotif
Any radius function you like, wrapped as a motif.
The escape hatch for a one-off polar curve that does not deserve a class of its own::
PolarExpression(lambda t: 60 + 20 * math.sin(9 * t))
Subclassing :class:~geomotif.PolarMotif is still the better answer for
anything you will use twice -- it gets a name, a docstring, parameters
and a registry entry. This is for the other times.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
callable
|
Maps an angle in radians to a radius. Called across the sweep only.
Named |
_ripple
|
Notes
The design this builds records the function object 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 real registered class.
Rose
dataclass
¶
Rose(n: int = 5, d: int = 1, size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The rhodonea r = cos(n/d * theta), with the petal count right.
The petal count is the part everyone gets wrong, because it depends on
the parity of the reduced fraction rather than on the numbers as typed.
With k = n/d in lowest terms the curve has n petals when n*d
is odd and 2*n when it is even, and it closes after d*pi or
2*d*pi respectively. This class works that out and sweeps exactly
that far, so no petal is ever traced twice.
d = 1 covers the familiar roses: three petals at n = 3, eight at
n = 4. Larger denominators give the tangled many-lobed rhodoneas.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Numerator of the angular frequency. |
5
|
d
|
int
|
Denominator of the angular frequency. Reduced against |
1
|
size
|
float
|
Petal length, measured from the center. |
100.0
|
center
|
(float, float)
|
Where the petals meet. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
petal_count |
Return how many petals this rose actually has. |
closure |
Return the angular sweep after which the curve returns to its start. |
Arc
dataclass
¶
Arc(radius: float = 100.0, start_angle: float = 0.0, sweep: float = pi, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Part of a circle: sweep radians of it, starting at start_angle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Distance from the center. |
100.0
|
start_angle
|
float
|
Where the arc begins, in radians. |
0.0
|
sweep
|
float
|
Angular extent, in radians. Negative sweeps run clockwise. |
pi
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
Circle
dataclass
¶
Bases: ParametricMotif
A circle of radius radius around center.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Distance from the center. |
100.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
Egg
dataclass
¶
Egg(length: float = 140.0, width: float = 100.0, taper: float = 0.25, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
An oval fatter at one end than the other.
An ellipse whose height is tapered along its length, which is the
simplest form that reads as an egg and stays smooth everywhere.
taper = 0 is exactly an ellipse.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
float
|
Full extent along the x-axis. |
140.0
|
width
|
float
|
Full extent across, before tapering. |
100.0
|
taper
|
float
|
How lopsided the egg is, between -1 and 1. Positive values put the blunt end on the right. |
0.25
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
Ellipse
dataclass
¶
Ellipse(rx: float = 120.0, ry: float = 70.0, center: Point = (0.0, 0.0), rotation: float = 0.0, *, resolution: int | None = None)
Bases: ParametricMotif
An ellipse with semi-axes rx and ry, optionally rotated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rx
|
float
|
Semi-axis lengths, before rotation. |
120.0
|
ry
|
float
|
Semi-axis lengths, before rotation. |
120.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the |
0.0
|
Line
dataclass
¶
Bases: PolygonMotif
A straight segment from start to end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
(float, float)
|
The two endpoints. |
(0.0, 0.0)
|
end
|
(float, float)
|
The two endpoints. |
(0.0, 0.0)
|
PointGrid
dataclass
¶
PointGrid(columns: int = 12, rows: int = 12, dx: float = 20.0, dy: float = 20.0, stagger: float = 0.0, center: Point = (0.0, 0.0))
Bases: Motif
A rectangular lattice of loose points, optionally staggered.
Produces points rather than strokes: this is the substrate for dot art,
stipple fields and object placement, not something to draw a line
through. stagger=0.5 offsets alternate rows by half a step, which
turns the square lattice into a triangular one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
columns
|
int
|
Lattice size. At least 1 each. |
12
|
rows
|
int
|
Lattice size. At least 1 each. |
12
|
dx
|
float
|
Spacing between neighbours. |
20.0
|
dy
|
float
|
Spacing between neighbours. |
20.0
|
stagger
|
float
|
Fraction of |
0.0
|
center
|
(float, float)
|
Midpoint of the lattice. |
(0.0, 0.0)
|
PoissonDiscPoints
dataclass
¶
PoissonDiscPoints(width: float = 300.0, height: float = 300.0, min_distance: float = 20.0, center: Point = (0.0, 0.0), *, seed: int = 0, attempts: int = 30)
Bases: Motif
Points scattered at random, but never closer together than a set distance.
Bridson's algorithm. The result looks organic in a way a plain random scatter does not: random points clump and leave holes, while these fill the area evenly without ever falling into a visible grid. It is what you want for stippling, foliage placement and any "natural" arrangement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
float
|
Size of the area to fill. |
300.0
|
height
|
float
|
Size of the area to fill. |
300.0
|
min_distance
|
float
|
Closest two points may ever be. |
20.0
|
seed
|
int
|
Seeds a private generator, so the same seed always gives the same field and the design stays reproducible from its metadata. |
0
|
attempts
|
int
|
Candidates tried around each accepted point before giving up on it. Higher packs slightly tighter and costs proportionally more. |
30
|
center
|
(float, float)
|
Midpoint of the area. |
(0.0, 0.0)
|
Rectangle
dataclass
¶
Bases: PolygonMotif
An axis-aligned rectangle centered on center.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
float
|
Full extents, not half-extents. |
160.0
|
height
|
float
|
Full extents, not half-extents. |
160.0
|
center
|
(float, float)
|
Midpoint of the rectangle. |
(0.0, 0.0)
|
RegularPolygon
dataclass
¶
RegularPolygon(sides: int = 6, radius: float = 100.0, center: Point = (0.0, 0.0), rotation: float = _UP)
Bases: PolygonMotif
A regular sides-gon inscribed in a circle of radius radius.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sides
|
int
|
Number of corners. At least 3. |
6
|
radius
|
float
|
Circumradius -- the distance from the center to a corner, not to an edge. |
100.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the first corner, in radians. Defaults to straight up. |
_UP
|
ReuleauxPolygon
dataclass
¶
ReuleauxPolygon(sides: int = 3, width: float = 150.0, center: Point = (0.0, 0.0), rotation: float = _UP)
Bases: Motif
A curve of constant width: the Reuleaux triangle and its odd-sided kin.
Every arc is centered on the corner opposite it, so the distance across the shape is the same in every direction -- it rolls under a plank as smoothly as a circle does, while being nothing like one. Only odd corner counts work: an even one has a corner opposite a corner rather than an edge, and there is nothing to center the arcs on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sides
|
int
|
Number of corners. Must be odd and at least 3. |
3
|
width
|
float
|
The constant width, in every direction. |
150.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the first corner, in radians. Defaults to straight up. |
_UP
|
RoundedRectangle
dataclass
¶
RoundedRectangle(width: float = 160.0, height: float = 100.0, corner_radius: float = 20.0, center: Point = (0.0, 0.0))
Bases: Motif
A rectangle with its corners replaced by quarter circles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
float
|
Full extents, not half-extents. |
160.0
|
height
|
float
|
Full extents, not half-extents. |
160.0
|
corner_radius
|
float
|
Radius of each corner arc. |
20.0
|
center
|
(float, float)
|
Midpoint of the rectangle. |
(0.0, 0.0)
|
Sector
dataclass
¶
Sector(radius: float = 100.0, start_angle: float = 0.0, sweep: float = pi / 3.0, center: Point = (0.0, 0.0))
Bases: Motif
A pie slice: an arc closed back to its center by two radii.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Distance from the center to the arc. |
100.0
|
start_angle
|
float
|
Where the arc begins, in radians. |
0.0
|
sweep
|
float
|
Angular extent, in radians. Negative sweeps run clockwise. |
pi / 3.0
|
center
|
(float, float)
|
Point of the slice. |
(0.0, 0.0)
|
Squircle
dataclass
¶
Bases: ParametricMotif
The square-circle midpoint: a :class:Superellipse with n = 4.
A preset rather than a new curve, and the one worth having a name for -- it is the rounded square of app icons and camera viewfinders, and it fills a noticeably larger fraction of its bounding box than a circle without reading as a square.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Half-extent along both axes. |
100.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
Star
dataclass
¶
Star(points: int = 5, inner_ratio: float = 0.382, radius: float = 100.0, center: Point = (0.0, 0.0), rotation: float = _UP)
Bases: PolygonMotif
The star people actually draw: outer points joined through inner ones.
Distinct from :class:StarPolygon, which is a single line visiting every
step-th corner of one circle. This one has two circles and twice as
many corners, so its arms can be as fat or as thin as you like -- and it
works for any point count, including the even ones that {n/k} cannot
draw in a single stroke.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
int
|
Number of arms. |
5
|
inner_ratio
|
float
|
Inner radius as a fraction of the outer. The default is
|
0.382
|
radius
|
float
|
Outer radius. |
100.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the first arm, in radians. Defaults to straight up. |
_UP
|
StarPolygon
dataclass
¶
StarPolygon(points: int = 5, step: int = 2, radius: float = 100.0, center: Point = (0.0, 0.0), rotation: float = _UP)
Bases: PolygonMotif
The {n/k} star polygon: every step-th corner of an n-gon.
{5/2} is the pentagram, {7/3} the heptagram, {6/2} the Star
of David -- and that last one is two triangles, not one path, because
n and k share a factor. Each component comes back as its own
stroke; joining them would draw an edge that is not part of the figure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
int
|
The |
5
|
step
|
int
|
The |
2
|
radius
|
float
|
Circumradius. |
100.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the first corner, in radians. Defaults to straight up. |
_UP
|
Superellipse
dataclass
¶
Superellipse(exponent: float = 2.5, rx: float = 100.0, ry: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The curve |x/rx|**n + |y/ry|**n == 1, for any positive n.
One knob spans the whole range from a four-pointed astroid through the
diamond (n = 1), the ellipse (n = 2) and Piet Hein's rounded
rectangle (n = 2.5, the shape of Sergels torg) up to the rectangle
itself as n grows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exponent
|
float
|
The |
2.5
|
rx
|
float
|
Half-extents along each axis. |
100.0
|
ry
|
float
|
Half-extents along each axis. |
100.0
|
center
|
(float, float)
|
Point to draw around. |
(0.0, 0.0)
|
Epicycles
dataclass
¶
Epicycles(arms: tuple[tuple[float, float, float], ...] = ((120.0, 1.0, 0.0), (30.0, 7.0, 0.0), (12.0, 13.0, 0.0)), turns: float = 1.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Rotating arms stacked tip to tail; the path of the last tip.
Each arm is (radius, frequency, phase): how long it is, how many
revolutions it makes per turn of the whole system, and where it starts.
Negative frequencies turn the other way.
This is the general case the rest of this module is made of. Two arms give every trochoid; a few more give the Ptolemaic orbit of a moon of a moon; several dozen give a Fourier series, which is to say any closed curve at all::
Epicycles(arms=((100.0, 1.0, 0.0), (40.0, 5.0, 0.0), (18.0, -9.0, 0.0)))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arms
|
tuple of (float, float, float)
|
The arms, outermost effect last. At least one. |
((120.0, 1.0, 0.0), (30.0, 7.0, 0.0), (12.0, 13.0, 0.0))
|
turns
|
float
|
Revolutions of the slowest hand, in effect: the parameter sweeps
|
1.0
|
center
|
(float, float)
|
Where the first arm is anchored. |
(0.0, 0.0)
|
Epicycloid
dataclass
¶
Epicycloid(outer: int = 100, inner: int = 30, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
A point on the rim of a wheel rolling outside a ring: a ring of petals.
One cusp is the :class:~geomotif.motifs.curves.Cardioid, two is the
:class:~geomotif.motifs.curves.Nephroid, and the general case is the
flower shape a gear leaves when it rolls around another one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outer
|
int
|
Radius of the fixed ring. |
100
|
inner
|
int
|
Radius of the rolling wheel. |
30
|
center
|
(float, float)
|
Center of the fixed ring. |
(0.0, 0.0)
|
Epitrochoid
dataclass
¶
Epitrochoid(outer: int = 100, inner: int = 30, offset: float = 45.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
A pen fixed to a wheel rolling around the outside of a ring.
The same construction as :class:Hypotrochoid with the wheel on the far
side, which turns the scalloped rosette inside out into a ring of petals.
Layered with phase offsets, this is the curve underneath every guilloche
pattern on a banknote.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outer
|
int
|
Radius of the fixed ring. |
100
|
inner
|
int
|
Radius of the rolling wheel. |
30
|
offset
|
float
|
Distance from the wheel's center to the pen. Equal to |
45.0
|
center
|
(float, float)
|
Center of the fixed ring. |
(0.0, 0.0)
|
Hypocycloid
dataclass
¶
Hypocycloid(outer: int = 100, inner: int = 30, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
A point on the rim of a wheel rolling inside a ring: a cusped star.
:class:Hypotrochoid with the pen exactly on the rim, which is what puts
a cusp wherever the rim touches the ring. Three cusps is the
:class:~geomotif.motifs.curves.Deltoid, four is the
:class:~geomotif.motifs.curves.Astroid, and both have their own class
with a scale you can set directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outer
|
int
|
Radius of the fixed ring. |
100
|
inner
|
int
|
Radius of the rolling wheel. |
30
|
center
|
(float, float)
|
Center of the fixed ring. |
(0.0, 0.0)
|
Hypotrochoid
dataclass
¶
Hypotrochoid(outer: int = 100, inner: int = 30, offset: float = 45.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
A pen fixed to a wheel rolling around the inside of a ring.
The Spirograph curve, in its mathematical clothes -- see
:class:Spirograph for the version that takes tooth counts. offset
is free to exceed inner, which puts the pen outside the wheel's rim
and is not something the physical toy can do.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outer
|
int
|
Radius of the fixed ring. |
100
|
inner
|
int
|
Radius of the rolling wheel. Must be less than |
30
|
offset
|
float
|
Distance from the wheel's center to the pen. Equal to |
45.0
|
center
|
(float, float)
|
Center of the fixed ring. |
(0.0, 0.0)
|
Spirograph
dataclass
¶
Spirograph(ring_teeth: int = 96, wheel_teeth: int = 36, hole: float = 0.7, ring_radius: float = 150.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The toy, in the toy's own terms: a ring, a wheel and a hole.
Exactly a :class:Hypotrochoid, parameterized the way the box is. Tooth
counts are what actually determine the pattern -- the ring and wheel that
come in the tin have 96 and 36 teeth, and their ratio is why that
particular rosette is the one everyone remembers drawing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ring_teeth
|
int
|
Teeth on the fixed ring. |
96
|
wheel_teeth
|
int
|
Teeth on the rolling wheel. Fewer than the ring's. |
36
|
hole
|
float
|
Which hole the pen goes in, as a fraction of the wheel's radius from
its center. |
0.7
|
ring_radius
|
float
|
Physical size of the ring, which sets the size of the drawing. |
150.0
|
center
|
(float, float)
|
Center of the ring. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
wheel_radius |
Return the rolling wheel's radius, scaled from the tooth counts. |
wheel_radius
¶
FlowerOfLife
dataclass
¶
FlowerOfLife(rings: int = 2, radius: float = 40.0, center: Point = (0.0, 0.0), *, boundary: bool = True)
Bases: Motif
The seed of life continued outward: circles on a hexagonal grid.
Every circle passes through the middles of its six neighbours, so the whole figure is one lattice with one spacing. Ring counts give 1, 7, 19, 37 and 61 circles; the nineteen-circle version inside its boundary is the one carved at Abydos and the one most people mean.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rings
|
int
|
How many rings of circles out from the middle. |
2
|
radius
|
float
|
Radius of every circle, which is also the spacing between them. |
40.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
boundary
|
bool
|
Draw the circle that encloses the lot. It touches the outermost circles from inside, which is what closes the figure off. |
True
|
Methods:
| Name | Description |
|---|---|
centers |
Return every circle's middle. |
FruitOfLife
dataclass
¶
Bases: Motif
The thirteen circles of the flower that touch without overlapping.
Take the flower of life and keep only the circles that meet edge to edge:
one in the middle, six around it at twice the radius, and six more at the
corners beyond. Their thirteen middles are what :class:MetatronsCube
joins up.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of every circle. Neighbours sit |
40.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the first inner circle, in radians. |
pi / 2.0
|
Methods:
| Name | Description |
|---|---|
centers |
Return the thirteen middles: the shared one, then each ring outward. |
centers
¶
Return the thirteen middles: the shared one, then each ring outward.
Source code in src/geomotif/motifs/sacred.py
GoldenRectangle
dataclass
¶
Bases: Motif
A rectangle that keeps its shape when you cut a square off it.
Cut the largest possible square from a golden rectangle and what is left is another golden rectangle, turned a quarter turn. Do it again and again and the squares spiral inward -- the frame the Fibonacci spiral is drawn in. Draw the outer rectangle plus each cut, and the whole theorem is one picture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Length of the long side. |
280.0
|
depth
|
int
|
How many squares to cut off. Capped: past sixty-odd the cut is narrower than a wavelength of light, never mind a pen. |
8
|
center
|
(float, float)
|
Middle of the outer rectangle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
squares |
Return each cut as the two ends of the line that makes it. |
squares
¶
Return each cut as the two ends of the line that makes it.
Source code in src/geomotif/motifs/sacred.py
MetatronsCube
dataclass
¶
MetatronsCube(radius: float = 34.0, center: Point = (0.0, 0.0), rotation: float = pi / 2.0, *, circles: bool = True)
Bases: Motif
Thirteen circles with every pair of middles joined by a line.
Drawing all seventy-eight chords rather than a chosen few is the point: the outlines of five of the Platonic solids appear in the result without anybody having placed them, because the thirteen middles are a projection of the cubic lattice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of every circle. |
34.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the first inner circle, in radians. |
pi / 2.0
|
circles
|
bool
|
Draw the circles as well as the lines. Turn it off for the bare lattice of chords. |
True
|
Methods:
| Name | Description |
|---|---|
centers |
Return the thirteen middles, as :class: |
centers
¶
Return the thirteen middles, as :class:FruitOfLife places them.
SeedOfLife
dataclass
¶
Bases: Motif
Seven circles: one in the middle, six around it, all the same size.
Each of the six passes through the middle one's center, and the six meet
each other exactly. It is the first closed figure the vesica construction
reaches, and the first ring of the :class:FlowerOfLife.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of every circle, which is also the spacing between them. |
60.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
rotation
|
float
|
Angle of the first outer circle, in radians. |
pi / 2.0
|
Methods:
| Name | Description |
|---|---|
centers |
Return the seven circle middles, the shared one first. |
centers
¶
Return the seven circle middles, the shared one first.
SriYantra
dataclass
¶
SriYantra(size: float = 260.0, center: Point = (0.0, 0.0), *, boundary: bool = True, bindu: bool = True)
Bases: Motif
Nine interlocking triangles inside a circle, four pointing up and five down.
Every triangle has a horizontal base and an apex on the vertical axis, so
three numbers fix each one: how high the base sits, how wide it is, and
how far the apex reaches. That is :attr:bands below, in units of the
radius, and editing it is how you draw a different yantra.
The classical figure additionally asks that all fifty-four crossings be exactly concurrent, which pins those numbers to the solution of a nonlinear system rather than to a table. What is here is the drawn yantra: right in structure, arrangement and count, and true to within a line's width rather than exactly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Diameter of the enclosing circle. |
260.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
boundary
|
bool
|
Draw the enclosing circle. |
True
|
bindu
|
bool
|
Mark the point at the middle, where the innermost triangle closes. |
True
|
Methods:
| Name | Description |
|---|---|
triangles |
Return the nine triangles, base corners first then the apex. |
triangles
¶
Return the nine triangles, base corners first then the apex.
Source code in src/geomotif/motifs/sacred.py
VesicaPiscis
dataclass
¶
Bases: Motif
Two circles, each through the other's middle.
The almond where they overlap is the vesica itself. Its height is
sqrt(3) times its width, so the figure hands you an equilateral
triangle and a square root of three with no measuring -- which is why
every construction below starts here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of both circles, which is also how far apart they sit. |
90.0
|
center
|
(float, float)
|
Midpoint between the two, and the middle of the almond. |
(0.0, 0.0)
|
lens
|
bool
|
Also draw the almond's own outline as a closed path. |
False
|
Methods:
| Name | Description |
|---|---|
centers |
Return the two circle middles, left then right. |
lens_path |
Return the almond: two arcs of 120 degrees, meeting at the points. |
centers
¶
lens_path
¶
lens_path() -> Path
Return the almond: two arcs of 120 degrees, meeting at the points.
Source code in src/geomotif/motifs/sacred.py
Cube
dataclass
¶
Cube(*, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
Dodecahedron
dataclass
¶
Dodecahedron(*, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
Icosahedron
dataclass
¶
Icosahedron(*, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
Octahedron
dataclass
¶
Octahedron(*, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
Polyhedron
dataclass
¶
Polyhedron(corners: tuple[Vertex, ...], links: tuple[tuple[int, int], ...] = (), *, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
Bases: PolyhedronBase
Any solid you like: your corners, your edges.
For the shapes whose corners are not all alike, where "join the nearest
pairs" is not the edge set -- a pyramid, a prism, a stellation, a
scaffold. Leave links empty to fall back to joining the nearest pairs
anyway.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corners
|
tuple of (float, float, float)
|
The corners, in any scale. |
required |
links
|
tuple of (int, int)
|
Index pairs into |
()
|
PolyhedronBase
dataclass
¶
PolyhedronBase(*, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
Bases: SegmentMotif, ABC
Base for a solid: corners in space, joined and flattened onto the page.
Implement :meth:vertices. :meth:edges joins every pair of corners that
are as close together as any pair gets, which is the edge set of any solid
whose corners are all alike; override it for one whose corners are not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Diameter of the sphere the corners sit on. The drawing itself is usually smaller, since a projection foreshortens. |
200.0
|
projection
|
Projection
|
How space becomes the page. |
Projection()
|
center
|
(float, float)
|
Where the middle lands. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
vertices |
Return the corners, in any scale: they are normalized before drawing. |
edges |
Join every pair of corners as close together as any pair gets. |
Projection
dataclass
¶
Projection(kind: View = 'isometric', yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0, distance: float = 3.0)
How a corner in space becomes a point on the page.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
'isometric'
|
yaw
|
float
|
Extra turns applied after the base orientation: about the vertical, about the horizontal, and about the line of sight. |
0.0
|
pitch
|
float
|
Extra turns applied after the base orientation: about the vertical, about the horizontal, and about the line of sight. |
0.0
|
roll
|
float
|
Extra turns applied after the base orientation: about the vertical, about the horizontal, and about the line of sight. |
0.0
|
distance
|
float
|
How far the eye is from the middle, in circumradii. Only
|
3.0
|
Methods:
| Name | Description |
|---|---|
oriented |
Return |
oriented
¶
Return vertex turned into the view's own frame, still in space.
Source code in src/geomotif/motifs/solids.py
Tetrahedron
dataclass
¶
Tetrahedron(*, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
TruncatedIcosahedron
dataclass
¶
TruncatedIcosahedron(*, merge: bool = False, show_nodes: bool = False, size: float = 200.0, projection: Projection = Projection(), center: Point = (0.0, 0.0))
Bases: PolyhedronBase
The football: twelve pentagons and twenty hexagons.
Made by cutting each of the icosahedron's twelve corners off a third of the way along every edge that meets it. The cut leaves a pentagon where the corner was and turns each triangle into a hexagon.
ArchimedeanSpiral
dataclass
¶
ArchimedeanSpiral(a: float = 0.0, b: float = 10.0, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = 0.0, theta_span: float = 3.0 * tau)
Bases: SpiralBase
The arithmetic spiral r = a + b*theta.
Successive turns are a constant b*tau apart, which is what makes this
the spiral of coiled rope, clock springs and vinyl records -- and the one
to reach for when even spacing between the windings is the point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Radius at |
0.0
|
b
|
float
|
Radial growth per radian. Every turn is |
10.0
|
Methods:
| Name | Description |
|---|---|
between |
Return the arithmetic spiral running from |
between
staticmethod
¶
between(start: Point, end: Point, *, center: Point = (0.0, 0.0), clockwise: bool = True, turns: int = 0) -> SpiralBetween
Return the arithmetic spiral running from start to end.
The same curve as this class, constrained by where it has to begin and
end rather than by its growth rate -- which is the useful form when
the endpoints are given and b is whatever it has to be. See
:class:SpiralBetween.
Source code in src/geomotif/motifs/spirals.py
CircleInvolute
dataclass
¶
CircleInvolute(radius: float = 10.0, turns: float = 3.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The path of a string unwinding, taut, from a circle.
Neighbouring turns stay exactly one circumference apart, which is what makes this the profile of very nearly every gear tooth in existence: two involutes rolling against each other transmit motion at a constant ratio however far apart their axes drift.
Not a polar motif, despite looking like one -- r and theta are
both functions of how much string has unwound, and neither is a function
of the other in closed form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radius
|
float
|
Radius of the circle being unwound from. |
10.0
|
turns
|
float
|
Revolutions of string to unwind. |
3.0
|
center
|
(float, float)
|
Center of that circle. |
(0.0, 0.0)
|
EulerSpiral
dataclass
¶
EulerSpiral(scale: float = 200.0, extent: float = 2.5, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The clothoid: curvature increasing linearly with distance traveled.
Drive at constant speed and turn the wheel at a constant rate and this is the path you take, which is why it is the transition curve between a straight railway and a curved one. Both arms wind into their own limit point, and the whole curve is one continuous sweep through the origin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scale
|
float
|
Size of the figure: the two limit points sit |
200.0
|
extent
|
float
|
How far along the curve to travel in each direction. Past about 3 the arms have all but reached their limit points. |
2.5
|
center
|
(float, float)
|
Midpoint of the figure. |
(0.0, 0.0)
|
Notes
The Fresnel integrals are evaluated by power series, which is accurate to
machine precision out to extent = 4. Beyond that the series loses too
many digits to cancellation and a standard rational approximation takes
over, good to about 2e-3 * scale. That is the price of a
dependency-free implementation, and it is charged only where the curve
has already spiralled down to a dot.
FermatSpiral
dataclass
¶
FermatSpiral(a: float = 30.0, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = 0.0, theta_span: float = 3.0 * tau, both_branches: bool = True)
Bases: SpiralBase
The parabolic spiral r = a * sqrt(theta), both branches.
Defined by r**2 = a**2 * theta, so every angle has a positive and a
negative root and the true curve is two arms meeting at the origin, each
the other reflected through it. That symmetry is the whole character of
the shape -- it is the arrangement sunflower seeds settle into -- so both
arms are drawn by default.
Because area grows linearly with theta, the windings crowd together as
they go out, packing points at constant density rather than constant
spacing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Radial scale. |
30.0
|
both_branches
|
bool
|
Draw the second arm. Turn off for the single arm alone. |
True
|
FibonacciSpiral
dataclass
¶
Bases: Motif
Quarter circles inscribed in a chain of Fibonacci squares.
The spiral everyone draws when they mean :class:GoldenSpiral, and not
the same curve: it is a sequence of circular arcs that jump in curvature
at every join, where the golden spiral is smooth throughout. The two run
within a couple of percent of each other, which is exactly why the
distinction is worth drawing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quarters
|
int
|
Number of quarter-circle arcs, one per Fibonacci square. |
9
|
size
|
float
|
Side of the first square. |
10.0
|
GoldenSpiral
dataclass
¶
GoldenSpiral(a: float = 2.0, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = 0.0, theta_span: float = 2.0 * tau)
Bases: SpiralBase
The logarithmic spiral that widens by the golden ratio each quarter turn.
A preset rather than a new curve: :class:LogarithmicSpiral with b
fixed at ln(phi) / (pi/2). Distinct from :class:FibonacciSpiral,
which is the quarter-circle approximation usually drawn in its place --
they are close enough to be mistaken for each other and different enough
to be worth having both.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Radius at |
2.0
|
HyperbolicSpiral
dataclass
¶
HyperbolicSpiral(a: float = 200.0, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = pi / 6.0, theta_span: float = 3.0 * tau)
Bases: SpiralBase
The reciprocal spiral r = a / theta.
Runs the other way from most of the family: it starts far out and winds
inward toward the origin, which it approaches without ever arriving.
Outward it is asymptotic to the line y = a.
theta = 0 is a pole, so the sweep must not cross it --
:attr:~geomotif.PolarMotif.theta_start therefore defaults to a small
positive angle rather than zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Radial scale, and the height of the horizontal asymptote. |
200.0
|
Lituus
dataclass
¶
Lituus(a: float = 200.0, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = pi / 6.0, theta_span: float = 3.0 * tau)
Bases: SpiralBase
The spiral r = a / sqrt(theta), named for the Roman augur's staff.
The reciprocal of :class:FermatSpiral, and its complement: it sweeps
equal areas in equal angles, so where Fermat's arms crowd outward this
one's crowd inward. Like :class:HyperbolicSpiral it has a pole at
theta = 0 and its sweep must avoid it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Radial scale. |
200.0
|
LogarithmicSpiral
dataclass
¶
LogarithmicSpiral(a: float = 5.0, b: float = 0.15, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = 0.0, theta_span: float = 3.0 * tau)
Bases: SpiralBase
The equiangular spiral r = a * exp(b*theta).
Every turn is a constant multiple of the one inside it, so the shape is the same at every scale -- the nautilus shell, the spiral galaxy, the low pressure system. The tangent meets the radius at the same angle everywhere, which is where "equiangular" comes from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
Radius at |
5.0
|
b
|
float
|
Growth rate. The radius multiplies by |
0.15
|
SpiralBase
dataclass
¶
SpiralBase(*, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = 0.0, theta_span: float = 3.0 * tau)
Bases: PolarMotif, ABC
Shared ground for spirals defined by a radius that grows with theta.
Everything a spiral needs is already on :class:~geomotif.PolarMotif --
:attr:~geomotif.PolarMotif.center, theta_start and theta_span.
This adds only the family's default sweep, three turns rather than one,
since a single revolution of a spiral is barely recognizable as one.
Say the sweep in revolutions with
:meth:~geomotif.PolarMotif.with_turns::
LogarithmicSpiral(b=0.2).with_turns(5, clockwise=True)
A concrete spiral is then its formula and nothing else::
@register("spiral.archimedean", family="spiral")
@dataclass(frozen=True, slots=True)
class ArchimedeanSpiral(SpiralBase):
a: float = 0.0
b: float = 10.0
def radius(self, theta: float) -> float:
return self.a + self.b * theta
SpiralBetween
dataclass
¶
SpiralBetween(start: Point, end: Point, center: Point = (0.0, 0.0), clockwise: bool = True, turns: int = 0, resolution: int | None = None)
Bases: Motif
The endpoint-constrained arithmetic spiral: r = a + b*theta.
Winds from start to end around center, interpolating the
radius linearly against a linearly interpolated angle. Both endpoints are
hit exactly, which is what makes this the useful form when you know where
the curve has to begin and end rather than what its growth rate should be.
:class:ArchimedeanSpiral is the same curve parameterized the other way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
(float, float)
|
First point of the spiral. |
required |
end
|
(float, float)
|
Last point of the spiral. |
required |
center
|
(float, float)
|
Point the spiral winds around. Default |
(0.0, 0.0)
|
clockwise
|
bool
|
Rotational direction of the sweep. Default |
True
|
turns
|
int
|
Extra full revolutions beyond the direct angular sweep from start to
end. |
0
|
resolution
|
int
|
Number of segments used to measure the curve. Defaults to a density that scales with the number of turns, which is nearly always right. |
None
|
Examples:
::
SpiralBetween((200, 0), (20, 0), turns=3).generate(120)
TheodorusSpiral
dataclass
¶
Bases: PolygonMotif
The square-root spiral: a chain of right triangles, drawn exactly.
Each triangle stands on the hypotenuse of the last with a new leg of
length size, so the n-th vertex sits at distance
size * sqrt(n) from the center. The result is a polyline by nature,
not a sampled curve -- there is nothing between the vertices to measure --
which is why this is a :class:~geomotif.PolygonMotif.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
triangles
|
int
|
How many triangles to stack. The classic figure stops at 16, where the spiral has just about closed its first turn. |
16
|
size
|
float
|
Length of each triangle's new leg. |
20.0
|
center
|
(float, float)
|
Point the chain winds around. |
(0.0, 0.0)
|
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)
|
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.
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)
|
SymmetricPointSet
dataclass
¶
SymmetricPointSet(count: int = 15, group: str = 'D5', radius: float = 120.0, relax: int = 200, connect: Connection = 'equal-distance', neighbors: int = 3, tolerance: float = 0.08, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = True)
Bases: SegmentMotif
Points arranged by a symmetry group, spaced by iterative relaxation.
The points are laid out in orbits -- sets the group carries onto
themselves. Under Cn an orbit holds n points; under Dn a
general orbit holds 2n and one sitting on the mirror lines holds
n. A single point at the center is fixed by either group, so a count
that is one more than a multiple of n gets one.
That is the whole constraint on count: it must be a multiple of the
group's order, or one more than one. Anything else cannot be arranged
symmetrically at all, and is refused with the nearest two counts that can.
Relaxation then equalizes the distances. Each orbit contributes one representative point, which is pushed away from neighbours closer than the mean nearest-neighbour distance and pulled toward those further away; the group replicates whatever it does. Because only representatives move, the figure cannot drift off its symmetry, and no random numbers are involved anywhere -- the same parameters always give the same points.
The relaxation is local, so it finds an even arrangement rather than
proving one. Most counts come out exactly equal; a few settle for a figure
that is well spread but not uniform, usually where two orbits of the same
size want the same radius. Raising relax does not rescue those -- they
are settled, not unfinished. This is the part of the motif that is still
experimental.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
How many points to place. |
15
|
group
|
str
|
|
'D5'
|
radius
|
float
|
Radius of the outermost point. The relaxed figure is scaled to this, so it is a size rather than a constraint on the solution. |
120.0
|
relax
|
int
|
Relaxation iterations. Zero returns the seeded rings untouched, which is worth looking at to see what the relaxation is doing. |
200
|
connect
|
('none', 'nearest', 'equal-distance', 'all-pairs')
|
What to draw between the points:
|
"none"
|
neighbors
|
int
|
How many neighbours each point is relaxed against, and how many
|
3
|
tolerance
|
float
|
How far above the shortest distance a pair may be and still count as equal, as a fraction of it. Relaxation converges rather than lands, so this is never zero. |
0.08
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Examples:
Methods:
| Name | Description |
|---|---|
orbit_sizes |
Return how many points each orbit holds, innermost first. |
orbit_sizes
¶
Return how many points each orbit holds, innermost first.
Source code in src/geomotif/motifs/symmetry.py
AmmannBeenker
dataclass
¶
AmmannBeenker(size: float = 16.0, radius: float = 150.0, offsets: tuple[float, ...] = (0.5, 0.5, 0.5, 0.5), center: Point = (0.0, 0.0))
Bases: Motif
The octagonal quasicrystal: squares and 45-degree rhombs, never repeating.
Built by de Bruijn's multigrid rather than by substitution. Four families of evenly spaced parallel lines are drawn across each other at 45 degrees; every crossing of a line from one family with a line from another names one tile, and the tile is the rhombus spanned by those two families' directions. Families a right angle apart give the squares, families 45 degrees apart give the rhombs, and there is nothing else -- which is a property you can check on the output rather than trust.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Edge length, shared by the square and the rhomb. |
16.0
|
radius
|
float
|
How far from the middle to tile. Tiles whose middle falls outside are dropped, so the patch comes out round. |
150.0
|
offsets
|
tuple of float
|
Where each family of lines sits relative to the origin. These choose which tiling of the family you get; every choice is locally the same as every other, and the default is the eightfold symmetric one. |
(0.5, 0.5, 0.5, 0.5)
|
center
|
(float, float)
|
Middle of the patch. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
rhombs |
Return every tile as its four corners, counter-clockwise. |
rhombs
¶
Return every tile as its four corners, counter-clockwise.
Exposed because the tiles themselves are often what you want -- to count them, to sort squares from rhombs, or to color them.
Source code in src/geomotif/motifs/tilings.py
CairoPentagonal
dataclass
¶
CairoPentagonal(size: float = 34.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
The Cairo tiling: pentagons in fours, spinning like a pinwheel.
Named for the paving of Cairo's streets. The pentagon has four equal sides and one shorter, two right angles and three of 120 degrees -- the right angles are where four pentagons meet head on, and the rest is where three do.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Length of one of the four equal sides. |
34.0
|
HerringboneTiling
dataclass
¶
HerringboneTiling(length: float = 60.0, width: float = 30.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Rectangles laid in chevrons, each one's end against the next one's side.
The parquet floor and the brick path. Any proportion works, not only the usual two-to-one: the lattice follows from the brick.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
float
|
The brick's two sides. |
60.0
|
width
|
float
|
The brick's two sides. |
60.0
|
HexagonalTiling
dataclass
¶
HexagonalTiling(size: float = 40.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
The honeycomb: regular hexagons, three to a corner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Distance from a hexagon's middle to one of its corners, which is also the length of a side. |
40.0
|
PenroseP2
dataclass
¶
Bases: PenroseTiling
Penrose's kite and dart, the tiling that cannot repeat.
Each tile is two Robinson triangles glued along a leg rather than a
base: two acute ones make a kite, two obtuse ones make a dart. The same
two triangles glued the other way give :class:PenroseP3, which is why
both live here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Subdivision rounds. Tile count grows by about |
5
|
radius
|
float
|
Circumradius of the starting wheel, and so of the finished patch. |
160.0
|
center
|
(float, float)
|
Middle of the wheel. |
(0.0, 0.0)
|
PenroseP3
dataclass
¶
Bases: PenroseTiling
Penrose's rhombs: one thin, one thick, and no repeating pattern ever.
Each rhombus is two Robinson triangles glued along their base, so the strokes drawn are the legs -- draw the base as well and every tile would have a line down its middle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Subdivision rounds. Tile count grows by about |
5
|
radius
|
float
|
Circumradius of the starting wheel, and so of the finished patch. |
160.0
|
center
|
(float, float)
|
Middle of the wheel. |
(0.0, 0.0)
|
PenroseTiling
dataclass
¶
Bases: SubstitutionTiling[RobinsonTriangle]
Shared scaffolding for the two Penrose tilings: the seed and the scale.
The seed is ten acute triangles in a wheel, which is five whole tiles
however they are glued -- five thick rhombs for :class:PenroseP3, five
kites for :class:PenroseP2. Subclasses supply the substitution rule and
say which edges to draw.
RhombilleTiling
dataclass
¶
RhombilleTiling(size: float = 40.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Tumbling blocks: each hexagon split into three rhombi.
The oldest optical illusion in tiling. Every rhombus is a face of a cube seen in isometric projection, and which cubes stick out and which are hollow is up to the eye.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Side of a rhombus, which is also the hexagon's circumradius. |
40.0
|
RobinsonTriangle
dataclass
¶
Half of a Penrose tile: an isosceles triangle in the complex plane.
Two shapes only. kind 0 is the acute one, 36-72-72, whose legs are
phi times its base; kind 1 is the obtuse one, 36-36-108, whose
base is phi times its legs. Everything Penrose is made of these.
:attr:apex is the corner between the two legs. What :attr:first and
:attr:second mean depends on the tiling: :class:PenroseP3 glues
triangles along the base :attr:first--:attr:second, and
:class:PenroseP2 glues them along the leg :attr:apex--:attr:first.
Complex numbers rather than points because the substitution is entirely
a + (b - a) / phi -- one expression each way, instead of one per
coordinate, with the rotations falling out of the arithmetic.
SnubSquare
dataclass
¶
SnubSquare(size: float = 34.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Squares and triangles, two of each at every corner -- the 3.3.4.3.4.
The squares come in two orientations thirty degrees apart, and the triangles pair up into rhombi that fill what is left. Four triangles and two squares repeat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Length of a side, shared by both shapes. |
34.0
|
SquareTiling
dataclass
¶
SquareTiling(size: float = 40.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Squares edge to edge: the graph paper of tilings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Length of a side. |
40.0
|
TriangularTiling
dataclass
¶
TriangularTiling(size: float = 40.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Equilateral triangles, alternately point up and point down.
The cell is one of each, which together make the rhombus that repeats.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Length of a side. |
40.0
|
TruchetTiling
dataclass
¶
TruchetTiling(size: float = 30.0, cols: int = 10, rows: int = 10, seed: int = 0, center: Point = (0.0, 0.0))
Bases: Motif
Quarter-circles in square cells, each turned at random.
Two arcs cross every cell, joining the midpoints of its sides; whether they curl one way or the other is decided by the toss of a coin. The arcs always meet at cell borders, so what comes out is a single tangle of smooth curves -- Sebastien Truchet's tiles of 1704, and still the cheapest way to make a plotter draw something that looks designed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Side of one cell. |
30.0
|
cols
|
int
|
How many cells across and down. |
10
|
rows
|
int
|
How many cells across and down. |
10
|
seed
|
int
|
Fixes the tosses. The same seed always draws the same tiles, and the generator is private to the call, so nothing else in the program can change what you get. |
0
|
center
|
(float, float)
|
Middle of the finished patch. |
(0.0, 0.0)
|
TruncatedSquare
dataclass
¶
TruncatedSquare(size: float = 34.0, *, region: Bounds, clip: bool = True)
Bases: LatticeTiling
Octagons with small squares in the gaps -- the 4.8.8 tiling.
What you get by cutting the corners off every square of a square tiling: the squares become octagons and the cut corners leave a smaller square behind, stood on its point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
float
|
Length of a side, shared by both shapes. |
34.0
|
Delaunay
dataclass
¶
Bases: SegmentMotif
The triangulation that joins points whose Voronoi cells touch.
Of all the ways to cut a point set into triangles, this is the one that avoids thin ones: no point ever falls inside another triangle's circumcircle, which maximizes the smallest angle in the whole mesh. That is why it is what meshers, terrain models and low-poly renderers use, and why a scatter drawn this way reads as a surface rather than a tangle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
sequence of (float, float)
|
The sites to triangulate. At least three, not all on one line. |
required |
LloydRelaxation
dataclass
¶
LloydRelaxation(points: Sequence[Point], iterations: int = 3, region: Bounds | None = None)
Bases: Motif
Points nudged toward the middle of their own cells, over and over.
Lloyd's algorithm, and the cheapest way to turn a clumped scatter into an even one that still looks unplanned. Each pass replaces every point with the center of area of its Voronoi cell; a point in a crowd is off-center in its own cell and drifts away from the crowd, a point in a gap is already central and stays. The fixed point of that -- reached in a handful of passes -- is a centroidal diagram, which is what stippling, dot art and object scattering all want.
Produces loose points, not strokes: the result is the input to the other motifs here rather than a drawing of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
sequence of (float, float)
|
The sites to even out. At least three, not all on one line. |
required |
iterations
|
int
|
Passes to run. Most of the work happens in the first three. |
3
|
region
|
Bounds
|
The area the points are kept inside. Defaults to their own extent, grown by a tenth, and is fixed at the start so the set cannot creep. |
None
|
Methods:
| Name | Description |
|---|---|
relaxed |
Return the points after the relaxation, ready to feed another motif. |
relaxed
¶
Return the points after the relaxation, ready to feed another motif.
Voronoi
dataclass
¶
Voronoi(points: Sequence[Point], region: Bounds | None = None, *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
The map of which point is nearest, drawn as its borders.
Each border is drawn once, however many cells meet along it, so the
result is a plotter's diagram rather than a stack of outlines --
merge=True then chains those borders into long strokes.
:class:VoronoiCells is the same figure when each region matters more
than the lines between them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
sequence of (float, float)
|
The sites. At least three, not all on one line. |
required |
region
|
Bounds
|
Where to cut off the cells of the outermost sites, whose borders otherwise run to infinity. Defaults to the points' own extent, grown by a tenth. |
None
|
Methods:
| Name | Description |
|---|---|
cells |
Return one convex cell per site, clipped to the region. |
corners |
Return the shared corner table and each cell as indices into it. |
VoronoiCells
dataclass
¶
VoronoiCells(points: Sequence[Point], region: Bounds | None = None, inset: float = 0.0)
Bases: PolygonMotif
The same map, drawn one closed region at a time.
Each cell is its own closed path, so a border shared by two of them is
drawn twice -- the price of having each region be a thing in itself,
which is what you want to fill, color, or cut. inset pulls every
cell back from its neighbours and gives the cracked-mud look the diagram
is usually drawn for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
sequence of (float, float)
|
The sites. At least three, not all on one line. |
required |
region
|
Bounds
|
Where to cut off the outermost cells. Defaults to the points' own extent, grown by a tenth. |
None
|
inset
|
float
|
Fraction of the way each cell is pulled toward its own middle.
|
0.0
|