Truchet Tiles

(0 comments)

Truchet Tiles are decorated squares which can tile a plane to create a pattern. For example, the four tiles:

enter image description here

can be placed at random orientations to yield a non-repeating pattern:

enter image description here

Similarly the two types of tile consisting of two quarter-circles in diagonally-opposite vertices of a square:

enter image description here

can be used to create the following pleasing pattern:

enter image description here

The code used to create these images consists of three files: truchet.py defines a base class, Truchet, which is derived in two further classes, TruchetTriangles in truchet_triangles.py and TruchetArcs in truchet_arcs.py. We use a Python decorator to wrap the SVG style definitions in the necessary SVG <defs> block. There are many tutorials dealing with Python decorators on the internet, so no detailed explanation will be given here: the function defs_decorator takes the function svg_styles defined in each derived class and returns a modified version of that function in which the opening and closing SVG tags are written around the CSS styles.

truchet.py:

class Truchet:
    """Base class for a Truchet tiling."""

    def __init__(self, width, height, s):
        """Initialize the class with image size and tile size, s."""

        self.width, self.height = width, height
        self.s = s
        self.nx, self.ny = int(width // s), int(height // s)
        self.fo = None

    def preamble(self):
        """The usual SVG preamble, including the image size."""

        print('<?xml version="1.0" encoding="utf-8"?>\n'

        '<svg xmlns="http://www.w3.org/2000/svg"\n' + ' '*5 +
           'xmlns:xlink="http://www.w3.org/1999/xlink" width="{}" height="{}" >'
                .format(self.width, self.height), file=self.fo)

    def defs_decorator(func):
        """For convenience, wrap the CSS styles with the needed SVG tags."""

        def wrapper(self):
            print("""
            <defs>
            <style type="text/css"><![CDATA[""", file=self.fo)

            func(self)

            print("""]]></style>
            </defs>""", file=self.fo)
        return wrapper

    def svg_shape(self, *args, **kwargs):
        """Override this function in the derived class."""

    def make_svg(self, filename, *args, **kwargs):
        """Create the tiling image as an SVG file with name filename.

        Custom arguments are passed to the derived class's svg_shape method.

        """

        self.fo = open(filename, 'w')
        self.preamble()
        self.svg_styles()
        self.svg_shape(*args, **kwargs)
        print('</svg>', file=self.fo)

truchet_triangles.py:

import random
from truchet import Truchet

class TruchetTriangles(Truchet):
    """A class for creating a Truchet tiling of triangles."""

    def __init__(self, width, height, s, colour):
        super(TruchetTriangles, self).__init__(width, height, s)
        self.colour = colour

    @Truchet.defs_decorator
    def svg_styles(self):
        print('.tri {{ stroke: none; fill: {}; }}'.format(self.colour),
              file=self.fo)


    def svg_shape(self, rule=None):
        """A Truchet figure based on triangles.

        The four triangle orientations to choose from in each square are:
                                xx x. xx .x
                                .x xx x. xx

        """

        if rule is None:
            rule = lambda ix, iy: random.randint(0,4)

        def triangle_path(A, B, C):
            """Output a triangular path with vertices at A, B, C."""

            print('<path d="M{},{} L{},{} L{},{}z" class="tri"/>'.format(
                    *A, *B, *C), file=self.fo)

        for ix in range(self.nx):
            for iy in range(self.ny):
                x0, y0 = ix*self.s, iy*self.s
                x1, y1 = (ix+1)*self.s, (iy+1)*self.s
                p = rule(ix, iy)
                if p == 0:
                    triangle_path((x0, y0), (x1, y0), (x1, y1))
                elif p == 1:
                    triangle_path((x0, y0), (x0, y1), (x1, y1))
                elif p == 2:
                    triangle_path((x0, y0), (x1, y0), (x0, y1))
                else:
                    triangle_path((x1, y0), (x1, y1), (x0, y1))

if __name__ == '__main__':
    truchet = TruchetTriangles(600, 400, 20, colour='#882ecf')
    truchet.make_svg('triangles.svg')

truchet_arcs.py:

import random
from truchet import Truchet

class TruchetArcs(Truchet):
    """A class for creating a Truchet tiling of arcs."""

    def __init__(self, width, height, s, colour):
        super(TruchetArcs, self).__init__(width, height, s)
        self.colour = colour

    @Truchet.defs_decorator
    def svg_styles(self):
        print('.arc {{ stroke: {}; stroke-width: 3px; fill: none; }}'
              .format(self.colour), file=self.fo)


    def svg_shape(self, r=None, rule=None):
        """A Truchet figure based on interlinking circular arcs."""

        def arc_path(A, B, r):
            """Semicircular arc path from A=(x0,y0) to B=(x1,y1), radius r."""

            print('<path d="M{},{} A{},{} 0 0 1 {} {}" class="arc"/>'.format(
                  *A, r, r, *B), file=self.fo)

        if rule is None:
            rule = lambda ix, iy: random.randint(0,1)

        if not r:
            r = self.s / 2
        for ix in range(self.nx):
            for iy in range(self.ny):
                p = rule(ix, iy)
                x0, y0 = ix*self.s, iy*self.s
                A, B = (x0,y0 + r), (x0 + r,y0 + self.s),
                C, D = (x0 + self.s,y0 + r), (x0 + r,y0)
                if p:
                    arc_path(A, B, r)
                    arc_path(C, D, r)
                else:
                    arc_path(D, A, r)
                    arc_path(B, C, r)

if __name__ == '__main__':
    truchet = TruchetArcs(600, 400, 20, colour='#2e88cf')
    truchet.make_svg('arcs.svg')
Current rating: 5

Comments

Comments are pre-moderated. Please be patient and your comment will appear soon.

There are currently no comments

New Comment

required

required (not published)

optional

required