geomotif.motifs.knots
¶
Celtic knotwork: strands that cross over and under and never end.
A knot drawing is a closed curve plus a decision at every crossing about which
strand is on top. This module keeps those two things apart. Each motif hands
:func:_interlace a set of closed loops; the interlacer finds where they
cross, works out the over-and-under, and draws the strand that goes underneath
with a gap in it. Nothing is filled and nothing is hidden, which is what a pen
plotter can actually draw and what the eye reads as weaving anyway.
Which strand goes over is not a free choice. Along one strand the crossings must alternate over, under, over, under -- and at each crossing the two strands must disagree. Written down, those are two kinds of "must differ", so the whole question is a two-coloring, and it has a solution for a single closed curve however tangled: between the two passes through any one crossing lies a closed loop, and a closed loop meets the rest of the curve an even number of times, so the two passes are always an odd number of crossings apart. What the interlacer does is that argument, run as a breadth-first search.
Four figures and one engine. :class:Triquetra is three circles.
:class:CircularCelticKnot and :class:SquareCelticKnot are one strand wound
several times round a ring or a square frame. :class:EndlessKnot is the woven
grid whose ends are looped back so that the whole figure is a single strand.
:class:CelticGrid is the plait: strands set off at 45 degrees, bounce off the
frame, and bounce off whatever barriers you place inside it.
Classes:
| Name | Description |
|---|---|
Triquetra |
The trinity knot: three arcs that turn out to be one strand. |
CircularCelticKnot |
One strand wound several times round a ring, weaving with itself. |
SquareCelticKnot |
The same winding, but round a square frame instead of a ring. |
EndlessKnot |
The endless knot: four strands each way, woven, with their ends looped back. |
CelticGrid |
The plait: strands at 45 degrees, bouncing off the frame and off barriers. |
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
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.
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.
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
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.