Skip to content

geomotif.motifs.sacred

Sacred geometry: circles on a hexagonal grid, and what people drew on them.

Almost everything in this module is the same idea repeated. Start with a circle. Put another the same size through its middle -- that is the :class:VesicaPiscis. Keep going until the first circle is ringed by six more and you have the :class:SeedOfLife; keep going outward and you have the :class:FlowerOfLife. Thin the flower down to the thirteen circles that touch without crossing and you have the :class:FruitOfLife; join all thirteen middles to each other and you have :class:MetatronsCube. One construction, five figures, each of which somebody has carved into a temple.

The two outliers are :class:SriYantra, which is nine interlocking triangles rather than circles, and :class:GoldenRectangle, which is the one figure here with an actual theorem in it.

These are cheap to draw and they plot beautifully, because every stroke is a full circle or a straight line -- there is nothing for a pen to stutter over.

Classes:

Name Description
VesicaPiscis

Two circles, each through the other's middle.

SeedOfLife

Seven circles: one in the middle, six around it, all the same size.

FlowerOfLife

The seed of life continued outward: circles on a hexagonal grid.

FruitOfLife

The thirteen circles of the flower that touch without overlapping.

MetatronsCube

Thirteen circles with every pair of middles joined by a line.

SriYantra

Nine interlocking triangles inside a circle, four pointing up and five down.

GoldenRectangle

A rectangle that keeps its shape when you cut a square off it.

VesicaPiscis dataclass

VesicaPiscis(radius: float = 90.0, center: Point = (0.0, 0.0), *, lens: bool = False)

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

centers() -> tuple[Point, Point]

Return the two circle middles, left then right.

Source code in src/geomotif/motifs/sacred.py
def centers(self) -> tuple[Point, Point]:
    """Return the two circle middles, left then right."""
    cx, cy = self.center
    half = self.radius / 2.0
    return ((cx - half, cy), (cx + half, cy))

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
def lens_path(self) -> Path:
    """Return the almond: two arcs of 120 degrees, meeting at the points."""
    left, right = self.centers()
    third = math.tau / 3.0
    # Each arc is the far circle's, swept between the two crossings.
    upper = arc_points(left, self.radius, -third / 2.0, third)
    lower = arc_points(right, self.radius, math.pi - third / 2.0, third)
    return Path(upper[:-1] + lower[:-1], closed=True)

SeedOfLife dataclass

SeedOfLife(radius: float = 60.0, center: Point = (0.0, 0.0), rotation: float = pi / 2.0)

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

centers() -> tuple[Point, ...]

Return the seven circle middles, the shared one first.

Source code in src/geomotif/motifs/sacred.py
def centers(self) -> tuple[Point, ...]:
    """Return the seven circle middles, the shared one first."""
    return (
        self.center,
        *ring_points(6, self.radius, center=self.center, rotation=self.rotation),
    )

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.

centers

centers() -> tuple[Point, ...]

Return every circle's middle.

Source code in src/geomotif/motifs/sacred.py
def centers(self) -> tuple[Point, ...]:
    """Return every circle's middle."""
    return _hex_lattice(self.radius, self.rings, self.center)

FruitOfLife dataclass

FruitOfLife(radius: float = 40.0, center: Point = (0.0, 0.0), rotation: float = pi / 2.0)

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 2 * radius apart, so they touch rather than cross.

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

centers() -> tuple[Point, ...]

Return the thirteen middles: the shared one, then each ring outward.

Source code in src/geomotif/motifs/sacred.py
def centers(self) -> tuple[Point, ...]:
    """Return the thirteen middles: the shared one, then each ring outward."""
    step = 2.0 * self.radius
    inner = ring_points(6, step, center=self.center, rotation=self.rotation)
    # The outer six sit between the inner ones, further out by sqrt(3):
    # the same lattice, one shell along.
    outer = ring_points(
        6,
        step * math.sqrt(3.0),
        center=self.center,
        rotation=self.rotation + math.pi / 6.0,
    )
    return (self.center, *inner, *outer)

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:FruitOfLife places them.

centers

centers() -> tuple[Point, ...]

Return the thirteen middles, as :class:FruitOfLife places them.

Source code in src/geomotif/motifs/sacred.py
def centers(self) -> tuple[Point, ...]:
    """Return the thirteen middles, as :class:`FruitOfLife` places them."""
    return FruitOfLife(radius=self.radius, center=self.center, rotation=self.rotation).centers()

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

triangles() -> tuple[tuple[Point, Point, Point], ...]

Return the nine triangles, base corners first then the apex.

Source code in src/geomotif/motifs/sacred.py
def triangles(self) -> tuple[tuple[Point, Point, Point], ...]:
    """Return the nine triangles, base corners first then the apex."""
    radius = self.size / 2.0
    cx, cy = self.center
    return tuple(
        (
            (cx - half * radius, cy + base * radius),
            (cx + half * radius, cy + base * radius),
            (cx, cy + apex * radius),
        )
        for base, apex, half in self.bands
    )

GoldenRectangle dataclass

GoldenRectangle(size: float = 280.0, depth: int = 8, center: Point = (0.0, 0.0))

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

squares() -> tuple[tuple[Point, Point], ...]

Return each cut as the two ends of the line that makes it.

Source code in src/geomotif/motifs/sacred.py
def squares(self) -> tuple[tuple[Point, Point], ...]:
    """Return each cut as the two ends of the line that makes it."""
    width = self.size
    height = self.size / _PHI
    cx, cy = self.center
    left, bottom = cx - width / 2.0, cy - height / 2.0
    right, top = left + width, bottom + height
    cuts: list[tuple[Point, Point]] = []
    # Cut from the left, then the bottom, then the right, then the top,
    # so the leftover rectangle spirals inward the way the squares do.
    for step in range(self.depth):
        side = min(right - left, top - bottom)
        match step % 4:
            case 0:
                left += side
                cuts.append(((left, bottom), (left, top)))
            case 1:
                bottom += side
                cuts.append(((left, bottom), (right, bottom)))
            case 2:
                right -= side
                cuts.append(((right, bottom), (right, top)))
            case _:
                top -= side
                cuts.append(((left, top), (right, top)))
    return tuple(cuts)