Skip to content

geomotif.motifs.girih

Islamic geometric patterns: girih tiles, strapwork and the star rosette.

The patterns on Persian and Timurid tilework are not drawn freehand and they are not drawn on a square grid. They are drawn with girih tiles: five shapes whose sides are all the same length and whose angles are all multiples of 36 degrees, each carrying a fixed set of lines. Lay the tiles down, draw the lines they carry, then rub the tiles out -- what is left is the pattern. The five shapes are the regular decagon, the regular pentagon, an elongated hexagon, a bowtie and a rhombus, and :class:GirihTile draws any of them.

The lines themselves come from an older rule, usually named for Ernest Hankin, who worked it out from the monuments: put a point at the middle of every edge and send two lines out from it, each making the same angle with that edge, and let every line run until it meets another. Because neighbouring tiles use the same angle at the shared edge midpoint, the two lines meeting there are exactly opposite and continue straight across -- which is why the finished pattern shows no trace of the tiles that generated it. That rule is what :class:InterlockingDecagons applies to the tiling :class:TenfoldGirih lays down.

:class:Rosette is the other half of the tradition: the shamsa, a star nested inside a blunter star inside a blunter one still, each reaching exactly as far as the last one's valleys. :class:RosetteTiling repeats it on a lattice, and :class:HexStarLattice is the six-fold pattern that needs no strapwork at all -- six-pointed stars with rhombi in the gaps.

Classes:

Name Description
Rosette

The shamsa: a star, a blunter star inside it, and so on inward.

GirihTile

One of the five girih tiles, with the strapwork it carries.

TenfoldGirih

Regular decagons laid edge to edge, with a bowtie in every gap.

InterlockingDecagons

The pattern the tenfold tiling carries: ten-pointed stars, interlocked.

HexStarLattice

Six-pointed stars on a triangular lattice, rhombi filling the gaps.

RosetteTiling

A field of rosettes, touching point to point.

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 k in {n/k}: how many corners each edge skips. Higher is spikier, and a spikier star nests faster.

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.

nesting property

nesting: float

Radius of one layer as a fraction of the layer outside it.

star

star(layer: int) -> tuple[Point, ...]

Return the corners of one layer, points and valleys alternating.

Source code in src/geomotif/motifs/girih.py
def star(self, layer: int) -> tuple[Point, ...]:
    """Return the corners of one layer, points and valleys alternating."""
    outer = self.radius * self.nesting**layer
    # Half a step of turn per layer is what puts this star's points in the
    # valleys of the one outside it rather than on top of its points.
    rotation = self.rotation + layer * math.pi / self.points
    inner = outer * self.nesting
    step = math.tau / self.points
    corners: list[Point] = []
    cx, cy = self.center
    for i in range(self.points):
        angle = rotation + i * step
        corners.append((cx + outer * math.cos(angle), cy + outer * math.sin(angle)))
        valley = angle + step / 2.0
        corners.append((cx + inner * math.cos(valley), cy + inner * math.sin(valley)))
    return tuple(corners)

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", "pentagon", "hexagon" (the elongated one), "bowtie" or "rhombus".

'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

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

Return the tile's corners, turned and placed.

Source code in src/geomotif/motifs/girih.py
def corners(self) -> tuple[Point, ...]:
    """Return the tile's corners, turned and placed."""
    cos, sin = math.cos(self.rotation), math.sin(self.rotation)
    cx, cy = self.center
    return tuple(
        (cx + x * cos - y * sin, cy + x * sin + y * cos)
        for x, y in _walk(_INTERIOR[self.shape], self.size)
    )

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

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

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

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:Rosette.

6
sharpness int

Passed straight to :class:Rosette.

6
layers int

Passed straight to :class:Rosette.

6
radius float

Circumradius of each rosette. Neighbours are two radii apart, so their points touch.

45.0
lattice str

"hex" for the triangular lattice, "square" for the square one.

'hex'

Methods:

Name Description
unit

Return the rosette this tiling repeats.

unit

unit() -> Rosette

Return the rosette this tiling repeats.

Source code in src/geomotif/motifs/girih.py
def unit(self) -> Rosette:
    """Return the rosette this tiling repeats."""
    return Rosette(
        points=self.points,
        sharpness=self.sharpness,
        layers=self.layers,
        radius=self.radius,
    )