Tiling patterns for 'XYZ function', using Tessagon 0.8. #104726

Open
Chris Want wants to merge 1 commits from ChrisWant/blender-addons:tessagon_3d_tiling into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
52 changed files with 5715 additions and 32 deletions

View File

@ -7,7 +7,7 @@
# dreampainter, cotejrp1, liero, Kayo Phoenix, sugiany, dommetysk, Jambay #
# Phymec, Anthony D'Agostino, Pablo Vazquez, Richard Wilks, lijenstina, #
# Sjaak-de-Draak, Phil Cote, cotejrp1, xyz presets by elfnor, revolt_randy, #
# Vladimir Spivak (cwolf3d), #
# Vladimir Spivak (cwolf3d), Chris Want #
bl_info = {

View File

@ -4,7 +4,10 @@
# Original by Buerbaum Martin (Pontiac), Elod Csirmaz
import sys
import os
import bpy
import bmesh
import math
import numpy
from mathutils import *
@ -15,8 +18,29 @@ from bpy.props import (
IntProperty,
FloatProperty,
BoolProperty,
EnumProperty
)
# Default: look in current directory for embedded Tessagon
TESSAGON_DIRECTORY = os.environ.get('TESSAGON_DIRECTORY',
os.path.dirname(os.path.abspath(__file__)))
sys.path.append(TESSAGON_DIRECTORY)
from tessagon.core.tessagon_discovery import TessagonDiscovery
from tessagon.adaptors.list_adaptor import ListAdaptor
# Create tiling menu with items in nice order
find_tilings = TessagonDiscovery()
tilings = find_tilings.with_classification('regular').to_list() + \
find_tilings.with_classification('archimedean').to_list() + \
find_tilings.with_classification('laves').to_list() + \
find_tilings.with_classification('non_edge').to_list() + \
find_tilings.with_classification('non_convex').to_list()
tiling_items = [('None', 'No tiling', 'No tiling')]
for counter, tessagon in enumerate(tilings):
name = tessagon.metadata.name
tiling_items.append((str(counter), name, name))
# List of safe functions for eval()
safe_list = ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
@ -48,7 +72,7 @@ safe_dict['min'] = min
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, verts, edges, faces, name):
def create_mesh_object(context, verts, edges, faces, name, recalculate_normals):
# Create new mesh
mesh = bpy.data.meshes.new(name)
@ -56,6 +80,12 @@ def create_mesh_object(context, verts, edges, faces, name):
# Make a mesh from a list of verts/edges/faces
mesh.from_pydata(verts, edges, faces)
if recalculate_normals:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces = bm.faces)
bm.to_mesh(mesh)
# Update mesh geometry after adding stuff
mesh.update()
@ -228,7 +258,7 @@ class AddZFunctionSurface(Operator):
edgeloop_prev = edgeloop_cur
base = create_mesh_object(context, verts, [], faces, "Z Function")
base = create_mesh_object(context, verts, [], faces, "Z Function", false)
else:
self.report({'WARNING'}, "Z Equation - No expression is given")
@ -240,7 +270,8 @@ class AddZFunctionSurface(Operator):
def xyz_function_surface_faces(self, x_eq, y_eq, z_eq,
range_u_min, range_u_max, range_u_step, wrap_u,
range_v_min, range_v_max, range_v_step, wrap_v,
a_eq, b_eq, c_eq, f_eq, g_eq, h_eq, n, close_v):
a_eq, b_eq, c_eq, f_eq, g_eq, h_eq, n, close_v,
tiling_type):
verts = []
faces = []
@ -305,36 +336,56 @@ def xyz_function_surface_faces(self, x_eq, y_eq, z_eq,
print("\n[Add X, Y, Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return [], []
def eval_f(u, v):
safe_dict['u'] = u
safe_dict['v'] = v
# Try to evaluate the equations.
try:
safe_dict['a'] = float(eval(*expr_args_a))
safe_dict['b'] = float(eval(*expr_args_b))
safe_dict['c'] = float(eval(*expr_args_c))
safe_dict['f'] = float(eval(*expr_args_f))
safe_dict['g'] = float(eval(*expr_args_g))
safe_dict['h'] = float(eval(*expr_args_h))
return (float(eval(*expr_args_x)),
float(eval(*expr_args_y)),
float(eval(*expr_args_z)))
except:
import traceback
self.report({'WARNING'}, "Error evaluating expression(s) - "
"Check the console for more info")
print("\n[Add X, Y, Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return None
if tiling_type != 'None':
tessagon_class = tilings[int(tiling_type)]
options = {
'function': eval_f,
'u_range': [range_u_min, range_u_max],
'v_range': [range_v_min, range_v_max],
'u_num': range_u_step,
'v_num': range_v_step,
'u_cyclic': wrap_u,
'v_cyclic': wrap_v,
'adaptor_class' : ListAdaptor
}
tessagon = tessagon_class(**options)
hash = tessagon.create_mesh()
return hash['vert_list'], hash['face_list']
for vN in range(vRange):
v = range_v_min + (vN * vStep)
for uN in range(uRange):
u = range_u_min + (uN * uStep)
safe_dict['u'] = u
safe_dict['v'] = v
safe_dict['n'] = n
# Try to evaluate the equations.
try:
safe_dict['a'] = float(eval(*expr_args_a))
safe_dict['b'] = float(eval(*expr_args_b))
safe_dict['c'] = float(eval(*expr_args_c))
safe_dict['f'] = float(eval(*expr_args_f))
safe_dict['g'] = float(eval(*expr_args_g))
safe_dict['h'] = float(eval(*expr_args_h))
verts.append((
float(eval(*expr_args_x)),
float(eval(*expr_args_y)),
float(eval(*expr_args_z))))
except:
import traceback
self.report({'WARNING'}, "Error evaluating expression(s) - "
"Check the console for more info")
print("\n[Add X, Y, Z Function Surface]:\n\n", traceback.format_exc(limit=1))
xyz = eval_f(u, v)
if xyz is None:
return [], []
verts.append(xyz)
for vN in range(range_v_step):
vNext = vN + 1
@ -366,7 +417,6 @@ def xyz_function_surface_faces(self, x_eq, y_eq, z_eq,
return verts, faces
# Original Script "Parametric.py" by Ed Mackey.
# -> http://www.blinken.com/blender-plugins.php
# Partly converted for Blender 2.5 by tuga3d.
@ -470,6 +520,12 @@ class AddXYZFunctionSurface(Operator):
"V values (only if U is wrapped)",
default=False
)
tiling_type : EnumProperty(
name='Tiling',
description='Tiling type',
items=tiling_items,
default='None'
)
n_eq: IntProperty(
name="Number of objects (n=0..N-1)",
description="The parameter n will be the index "
@ -511,7 +567,7 @@ class AddXYZFunctionSurface(Operator):
show_wire : BoolProperty(
name="Show wireframe",
default=True,
description="Add the objects wireframe over solid drawing"
description="Add the object's wireframe over solid drawing"
)
edit_mode : BoolProperty(
name="Show in edit mode",
@ -520,6 +576,9 @@ class AddXYZFunctionSurface(Operator):
)
def execute(self, context):
tiling_type = self.tiling_type
recalculate_normals = (tiling_type != 'None')
for n in range(0, self.n_eq):
verts, faces = xyz_function_surface_faces(
self,
@ -541,12 +600,14 @@ class AddXYZFunctionSurface(Operator):
self.g_eq,
self.h_eq,
n,
self.close_v
self.close_v,
self.tiling_type
)
if not verts:
return {'CANCELLED'}
obj = create_mesh_object(context, verts, [], faces, "XYZ Function")
obj = create_mesh_object(context, verts, [], faces, "XYZ Function",
recalculate_normals)
if self.show_wire:
context.active_object.show_wire = True

View File

@ -0,0 +1,2 @@
from .version import __version__
from .core.tessagon_discovery import TessagonDiscovery # noqa: F401

View File

@ -0,0 +1,27 @@
import bmesh
class BlenderAdaptor:
def __init__(self, **kwargs):
self.bm = None
def create_empty_mesh(self):
self.bm = bmesh.new()
def initialize_colors(self):
pass
def create_vert(self, coords):
return self.bm.verts.new(coords)
def create_face(self, verts):
return self.bm.faces.new(verts)
def color_face(self, face, color_index):
face.material_index = color_index
def finish_mesh(self):
bmesh.ops.recalc_face_normals(self.bm, faces=self.bm.faces)
def get_mesh(self):
return self.bm

View File

@ -0,0 +1,33 @@
class ListAdaptor:
def __init__(self, **kwargs):
self.vert_list = None
self.face_list = None
self.color_list = None
def create_empty_mesh(self):
self.vert_list = []
self.face_list = []
self.color_list = []
def initialize_colors(self):
self.color_list = [0]*len(self.face_list)
def create_vert(self, coords):
self.vert_list.append(coords)
return (len(self.vert_list) - 1)
def create_face(self, verts):
self.face_list.append(verts)
return (len(self.face_list) - 1)
def color_face(self, face, color_index):
self.color_list[face] = color_index
def finish_mesh(self):
pass
def get_mesh(self):
return {'vert_list': self.vert_list,
'face_list': self.face_list,
'color_list': self.color_list}

View File

@ -0,0 +1,48 @@
import vtk
class VtkAdaptor:
def __init__(self, **kwargs):
self.point_count = 0
self.face_count = 0
self.points = None
self.polys = None
self.poly_data = None
self.scalars = None
def create_empty_mesh(self):
self.pcoords = vtk.vtkFloatArray()
self.pcoords.SetNumberOfComponents(3)
self.points = vtk.vtkPoints()
self.polys = vtk.vtkCellArray()
self.poly_data = vtk.vtkPolyData()
def initialize_colors(self):
self.scalars = vtk.vtkFloatArray()
self.scalars.SetNumberOfComponents(1)
self.scalars.SetNumberOfTuples(self.face_count)
def create_vert(self, coords):
self.pcoords.InsertNextTuple3(*coords)
index = self.point_count
self.point_count += 1
return index
def create_face(self, verts):
self.polys.InsertNextCell(len(verts), verts)
index = self.face_count
self.face_count += 1
return index
def color_face(self, face, color_index):
self.scalars.SetTuple1(face, color_index)
def finish_mesh(self):
self.points.SetData(self.pcoords)
self.poly_data.SetPoints(self.points)
self.poly_data.SetPolys(self.polys)
if self.scalars:
self.poly_data.GetCellData().SetScalars(self.scalars)
def get_mesh(self):
return self.poly_data

View File

@ -0,0 +1,22 @@
import re
# A couple of general purpose functions for manipulating
# camel case class names and snake case function/method names.
# (Mostly used for some questionable dynamic method creation ...).
# TODO: setup demos to use this
def class_name_to_method_name(class_name, prefix=''):
method_name = prefix
method_name += re.sub(r'(?<!^)(?=[A-Z])', '_', class_name).lower()
return method_name
def class_to_method_name(cls, prefix=''):
# E.g., if cls is HexTessagon and prefix is 'whatever_',
# this function returns 'whatever_hex_tessagon'
return class_name_to_method_name(cls.__name__, prefix)
def method_name_to_class_name(method_name):
return ''.join(word.title() for word in method_name.split('_'))

View File

@ -0,0 +1,236 @@
from tessagon.core.value_blend import ValueBlend
class AbstractTile(ValueBlend):
def __init__(self, tessagon, **kwargs):
self.tessagon = tessagon
self.f = tessagon.f
# Verts/faces indexed with 'left', 'right', 'center'
self.u_symmetric = kwargs.get('u_symmetric', False)
# Verts/faces indexed with 'bottom', 'middle', 'top'
self.v_symmetric = kwargs.get('v_symmetric', False)
# Verts/faces with 'rotate' and a number.
# Only rot_symmetric = 180 supported
# TODO: implement rot_symmetric = 90 as needed
self.rot_symmetric = kwargs.get('rot_symmetry', None)
self.id = None
# This is not necessary to any of the calculations, just
# makes debugging easier
if 'id' in kwargs:
self.id = kwargs['id']
# This is an identifier that is set by the generator (a tuple)
# Really this should be merged with the id thingy above
self.fingerprint = kwargs.get('fingerprint') or None
# Corners is list of tuples:
# [bottomleft, bottomright, topleft, topright]
self.corners = None
self._init_corners(**kwargs)
self.neighbors = {
'top': None,
'bottom': None,
'left': None,
'right': None
}
# Are the neighbors ordered backwards?
# e.g., a tile with twist['right'] set to True:
# self tile: right edge has v=0 at the bottom and v=1 at the top
# right neighbor: left edge has v=1 at the bottom and v=0 at the top
# (the right tile has twist['left'] true
self.twist = {
'top': False,
'bottom': False,
'left': False,
'right': False
}
def set_neighbors(self, **kwargs):
if 'top' in kwargs:
self.neighbors['top'] = kwargs['top']
if 'bottom' in kwargs:
self.neighbors['bottom'] = kwargs['bottom']
if 'left' in kwargs:
self.neighbors['left'] = kwargs['left']
if 'right' in kwargs:
self.neighbors['right'] = kwargs['right']
def get_neighbor_tile(self, neighbor_keys):
tile = self
for key in self._neighbor_path(neighbor_keys):
if not tile.neighbors[key]:
return None
tile = tile.neighbors[key]
return tile
@property
def left(self):
return self.get_neighbor_tile(["left"])
@property
def right(self):
return self.get_neighbor_tile(["right"])
@property
def top(self):
return self.get_neighbor_tile(["top"])
@property
def bottom(self):
return self.get_neighbor_tile(["bottom"])
def inspect(self, **kwargs):
# For debugging topology
if not self.id:
return
prefix = 'Tile'
if 'tile_number' in kwargs:
prefix += " #%s" % (kwargs['tile_number'])
print("%s (%s):" % (prefix, self.__class__.__name__))
print(" - self: %s" % (self.id))
print(' - neighbors:')
for key in ['top', 'left', 'right', 'bottom']:
if self.neighbors[key]:
tile = self.neighbors[key]
if tile.id:
print(" - %s" % (self._neighbor_str(key)))
print(" - corners: (%2.4f, %2.4f) (%2.4f, %2.4f)" %
tuple(self.corners[2] + self.corners[3]))
print(" (%2.4f, %2.4f) (%2.4f, %2.4f)" %
tuple(self.corners[0] + self.corners[1]))
print(" - twist:", self.twist)
if self.fingerprint:
print(" - fingerprint:", self.fingerprint)
print('')
# Below are protected
# A couple of abstract methods that will be useful for finding
# and setting the vertices and faces on a tile
def _get_nested_list_value(self, nested_list, index_keys):
if not isinstance(index_keys, list):
return nested_list[index_keys]
value = nested_list
for index in index_keys:
value = value[index]
return value
def _set_nested_list_value(self, nested_list, index_keys, value):
if not isinstance(index_keys, list):
nested_list[index_keys] = value
return
reference = nested_list
for index in index_keys[0:-1]:
reference = reference[index]
reference[index_keys[-1]] = value
def _neighbor_path(self, neighbor_keys):
# Note: it is assumed that len(neighbor_keys) in [1, 2]
# if len(neighbor_keys) == 1, the neighbor meets on an edge
# if len(neighbor_keys) == 2, the neighbor meets at a corner,
# and are diagonal for each other, e.g., ['left', 'top']
# If the boundary is twisted, need to be careful because
# left and become right, or top can become bottom on the
# other side of the twisted boundary: try to traverse the
# non-twisted boundary first to make the math easier
if len(neighbor_keys) < 2:
return neighbor_keys
if self._should_twist_u(neighbor_keys):
if (neighbor_keys[0] in ['top', 'bottom']):
return [neighbor_keys[1], neighbor_keys[0]]
elif self._should_twist_v(neighbor_keys):
if (neighbor_keys[0] in ['left', 'right']):
return [neighbor_keys[1], neighbor_keys[0]]
return neighbor_keys
def _index_path(self, index_keys, neighbor_keys):
path = index_keys
if self._should_twist_u(neighbor_keys):
path = self._u_flip(path)
if self._should_twist_v(neighbor_keys):
path = self._v_flip(path)
return path
def _permute_value(self, index_keys, vals):
# abstract function to permute values in a list
# e.g., 'left' and 'right' in u_flip below
if isinstance(index_keys, list):
return [self._permute_value(u, vals) for u in index_keys]
for i in range(len(vals)):
if index_keys == vals[i]:
return vals[(i+1) % len(vals)]
return index_keys
def _swap_value(self, index_keys, val1, val2):
# abstract function to swap two values in a list
# e.g., 'left' and 'right' in u_flip below
return self._permute_value(index_keys, [val1, val2])
def _u_flip(self, index_keys):
# swap each left with right (and vice versa) in list
if not self.u_symmetric:
return index_keys
return self._swap_value(index_keys, 'left', 'right')
def _v_flip(self, index_keys):
# swap each top with bottom (and vice versa) in list
if not self.v_symmetric:
return index_keys
return self._swap_value(index_keys, 'bottom', 'top')
def _rotate_index(self, index_keys):
# rotate
if not self.rot_symmetric:
return index_keys
elif self.rot_symmetric == 180:
keys = self._permute_value(index_keys, ['rotate0', 'rotate180'])
keys = self._permute_value(keys, ['left', 'right'])
keys = self._permute_value(keys, ['top', 'bottom'])
return keys
elif self.rot_symmetric == 90:
return self._permute_value(index_keys,
['rotate0', 'rotate90'
'rotate180', 'rotate270'])
def _v_index(self, index_keys):
# find either 'top' or 'bottom' in the list
if ('bottom' in index_keys):
return 'bottom'
if ('top' in index_keys):
return 'top'
raise ValueError("no v_index found in %s" % (index_keys))
def _u_index(self, index_keys):
# find either 'right' or 'left' in the list
if ('left' in index_keys):
return 'left'
if ('right' in index_keys):
return 'right'
raise ValueError("no u_index found in %s" % (index_keys))
def _should_twist_u(self, neighbor_keys):
# e.g., twist['bottom'] is True, and neigbor_keys has 'bottom' in it
for twist in ['top', 'bottom']:
if self.twist[twist] and twist in neighbor_keys:
return True
return False
def _should_twist_v(self, neighbor_keys):
# e.g., twist['left'] is True, and neigbor_keys has 'left' in it
for twist in ['left', 'right']:
if self.twist[twist] and twist in neighbor_keys:
return True
return False
def _neighbor_str(self, key):
tile = self.neighbors[key]
if tile:
return "%-9s%s" % ("%s:" % (key), tile.id)
return "%s: None" % (key)

View File

@ -0,0 +1,15 @@
from tessagon.core.tile import Tile
class AlternatingTile(Tile):
def validate(self):
this_tile_type = self.tile_type
for name in self.neighbors:
neighbor = self.neighbors[name]
if neighbor and (neighbor.tile_type + this_tile_type != 1):
raise ValueError("Tiles have bad parity "
"(hint: maybe use an even number of tiles)")
@property
def tile_type(self):
return sum(self.fingerprint) % 2

View File

@ -0,0 +1,83 @@
from tessagon.core.tile_generator import TileGenerator
class GridTileGenerator(TileGenerator):
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.tiles = None
def initialize_tiles(self, **kwargs):
tiles = [[None for i in range(self.v_num)] for j in range(self.u_num)]
for u in range(self.u_num):
u_ratio0 = float(u) / self.u_num
u_ratio1 = float(u + 1) / self.u_num
v_shear0 = u * self.v_shear
v_shear1 = (u + 1) * self.v_shear
for v in range(self.v_num):
v_ratio0 = float(v) / self.v_num
v_ratio1 = float(v + 1) / self.v_num
u_shear0 = v * self.u_shear
u_shear1 = (v + 1) * self.u_shear
corners = [self.blend(u_ratio0 + u_shear0 + self.u_phase,
v_ratio0 + v_shear0 + self.v_phase),
self.blend(u_ratio1 + u_shear0 + self.u_phase,
v_ratio0 + v_shear1 + self.v_phase),
self.blend(u_ratio0 + u_shear1 + self.u_phase,
v_ratio1 + v_shear0 + self.v_phase),
self.blend(u_ratio1 + u_shear1 + self.u_phase,
v_ratio1 + v_shear1 + self.v_phase)]
tiles[u][v] = self.create_tile(u, v, corners, **kwargs)
self.tiles = tiles
return tiles
def initialize_neighbors(self, **kwargs):
tiles = self.tiles
for u in range(self.u_num):
u_prev = (u - 1) % self.u_num
u_next = (u + 1) % self.u_num
for v in range(self.v_num):
v_prev = (v - 1) % self.v_num
v_next = (v + 1) % self.v_num
tile = tiles[u][v]
if not self.u_cyclic and u == 0:
left = None
elif self.v_twist and u == 0:
left = tiles[u_prev][self.v_num - v - 1]
tile.twist['left'] = True
else:
left = tiles[u_prev][v]
if not self.v_cyclic and v == self.v_num - 1:
top = None
elif self.u_twist and v == self.v_num - 1:
top = tiles[self.u_num - u - 1][v_next]
tile.twist['top'] = True
else:
top = tiles[u][v_next]
if not self.u_cyclic and u == self.u_num - 1:
right = None
elif self.v_twist and u == self.u_num - 1:
right = tiles[u_next][self.v_num - v - 1]
tile.twist['right'] = True
else:
right = tiles[u_next][v]
if not self.v_cyclic and v == 0:
bottom = None
elif self.u_twist and v == 0:
bottom = tiles[self.u_num - u - 1][v_prev]
tile.twist['bottom'] = True
else:
bottom = tiles[u][v_prev]
tile.set_neighbors(left=left, right=right, top=top,
bottom=bottom)
def get_tiles(self):
return [j for i in self.tiles for j in i]

View File

@ -0,0 +1,198 @@
from tessagon.core.tile_generator import TileGenerator
class ParallelogramTileGenerator(TileGenerator):
# This generates tiles that are rotated and combined
# with a sheer transformation
# (Turning a collection of tiles into a parallelogram.)
# This is done so that the tile patterns can still be cyclic.
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
# parallelogram_vectors is a pair of pairs, e.g, [[9,1], [-1, 3]]
# This means:
# * For one side of the paralellogram go 9 tiles across, 1 tile up
# * From there, the next side you get by going -1 tiles across,
# 3 tiles up
# (Other sides are obvious. It's a parallelogram.)
self.p = kwargs['parallelogram_vectors']
self.determinant = \
self.p[0][0] * self.p[1][1] - self.p[1][0] * self.p[0][1]
self.validate_parallelogram()
# Rows
self.inverse = [[self.p[1][1] / (self.determinant * self.u_num),
-self.p[1][0] / (self.determinant * self.u_num)],
[-self.p[0][1] / (self.determinant * self.v_num),
self.p[0][0] / (self.determinant * self.v_num)]]
self.color_pattern = kwargs.get('color_pattern') or None
self.id_prefix = 'parallelogram_tiles'
# Mapped via a fingerprint
self.tiles = {}
def validate_parallelogram(self):
error = None
if self.p[0][0] <= 0:
error = "First parallelogram vector can't have negative u"
elif self.p[1][1] <= 0:
error = "Second parallelogram vector can't have negative v"
if self.determinant == 0:
error = "Parallelogram vector are colinear"
elif self.determinant < 0:
error = "First parallelogram vector is to the left of second"
if error:
raise ValueError(error)
def initialize_tiles(self):
tiles = {}
fingerprint_range = self.fingerprint_range()
for u in fingerprint_range[0]:
for v in fingerprint_range[1]:
fingerprint = self.normalize_fingerprint(u, v)
fingerprint_str = str(fingerprint)
if fingerprint_str not in tiles:
if self.valid_fingerprint(*fingerprint):
tiles[fingerprint_str] = self.make_tile(*fingerprint)
self.tiles = tiles
return tiles
def initialize_neighbors(self):
for tile in self.get_tiles():
u = tile.fingerprint[0]
v = tile.fingerprint[1]
fingerprint = self.normalize_fingerprint(u - 1, v)
fingerprint_str = str(fingerprint)
left = self.tiles.get(fingerprint_str)
fingerprint = self.normalize_fingerprint(u + 1, v)
fingerprint_str = str(fingerprint)
right = self.tiles.get(fingerprint_str)
fingerprint = self.normalize_fingerprint(u, v - 1)
fingerprint_str = str(fingerprint)
bottom = self.tiles.get(fingerprint_str)
fingerprint = self.normalize_fingerprint(u, v + 1)
fingerprint_str = str(fingerprint)
top = self.tiles.get(fingerprint_str)
tile.set_neighbors(left=left, right=right, top=top,
bottom=bottom)
def get_tiles(self):
return self.tiles.values()
def make_tile(self, *fingerprint):
corners = self.make_corners(*fingerprint)
return self.create_tile(fingerprint[0],
fingerprint[1],
corners)
def make_corners(self, *fingerprint):
u = fingerprint[0]
v = fingerprint[1]
return [
self.make_corner(u, v),
self.make_corner(u + 1, v),
self.make_corner(u, v + 1),
self.make_corner(u + 1, v + 1),
]
def make_corner(self, u, v):
c0 = self.inverse[0][0] * u + self.inverse[0][1] * v
c1 = self.inverse[1][0] * u + self.inverse[1][1] * v
return self.blend(c0, c1)
def valid_fingerprint(self, u, v):
# Valid = all corners of tile with this fingerprint
# are in the parallelogram (may be wrapped if cyclic)
# Assume u, v have been normalized already
if not self.point_in_parallelogram(u, v):
return False
fingerprint = self.normalize_fingerprint(u + 1, v)
if not self.point_in_parallelogram(*fingerprint):
return False
fingerprint = self.normalize_fingerprint(u, v + 1)
if not self.point_in_parallelogram(*fingerprint):
return False
fingerprint = self.normalize_fingerprint(u + 1, v + 1)
if not self.point_in_parallelogram(*fingerprint):
return False
return True
def parallelogram_coord(self, u, v):
# Convert to be in [0, self.u_num] x [0, self.v_num]
# (ideally if in the parallelogram)
u_coord = (u * self.p[1][1] - v * self.p[1][0]) / self.determinant
v_coord = (v * self.p[0][0] - u * self.p[0][1]) / self.determinant
return (u_coord, v_coord)
def point_in_parallelogram(self, u, v):
parallelogram_uv = self.parallelogram_coord(u, v)
if 0.0 <= parallelogram_uv[0] <= self.u_num:
if 0.0 <= parallelogram_uv[1] <= self.v_num:
return True
return False
def normalize_fingerprint(self, u, v):
# Return a canonical fingerprint for tile with this fingerprint
# Tiles that are essentually the same (due to cyclic/wrapping)
# will have the same fingerprint.
# The goal is to not create such tiles more than once
while (True):
u_old = u
v_old = v
parallelogram_uv = self.parallelogram_coord(u, v)
if (self.u_cyclic):
if parallelogram_uv[0] >= self.u_num:
u -= (self.u_num * self.p[0][0])
v -= (self.u_num * self.p[0][1])
elif parallelogram_uv[0] < 0.0:
u += (self.u_num * self.p[0][0])
v += (self.u_num * self.p[0][1])
if (self.v_cyclic):
if parallelogram_uv[1] >= self.v_num:
u -= (self.v_num * self.p[1][0])
v -= (self.v_num * self.p[1][1])
elif parallelogram_uv[1] < 0.0:
u += (self.v_num * self.p[1][0])
v += (self.v_num * self.p[1][1])
if u == u_old and v == v_old:
return (u, v)
def fingerprint_range(self):
# Maximum extents of what the ranges can be ...
# (Then we can loop over these ranges and see if tiles
# with these fingerprints are valid.)
u_min = min(0,
self.u_num * self.p[0][0],
self.v_num * self.p[1][0],
self.u_num * self.p[0][0] + self.v_num * self.p[1][0])
u_max = max(0,
self.u_num * self.p[0][0],
self.v_num * self.p[1][0],
self.u_num * self.p[0][0] + self.v_num * self.p[1][0])
v_min = min(0,
self.u_num * self.p[0][1],
self.v_num * self.p[1][1],
self.u_num * self.p[0][1] + self.v_num * self.p[1][1])
v_max = max(0,
self.u_num * self.p[0][1],
self.v_num * self.p[1][1],
self.u_num * self.p[0][1] + self.v_num * self.p[1][1])
return (range(u_min, u_max), range(v_min, v_max))

View File

@ -0,0 +1,161 @@
from tessagon.core.grid_tile_generator import GridTileGenerator
from tessagon.core.parallelogram_tile_generator \
import ParallelogramTileGenerator
class Tessagon:
tile_class = None
metadata = None
def __init__(self, **kwargs):
if 'tile_generator' in kwargs:
self.tile_generator = kwargs['tile_generator'](self, **kwargs)
elif 'rot_factor' in kwargs:
# Deprecated?
rot_factor = kwargs['rot_factor']
extra_args = {'parallelogram_vectors':
[[rot_factor, -1], [1, rot_factor]]}
self.tile_generator = \
ParallelogramTileGenerator(self,
**{**kwargs, **extra_args})
elif 'parallelogram_vectors' in kwargs:
self.tile_generator = ParallelogramTileGenerator(self, **kwargs)
else:
self.tile_generator = GridTileGenerator(self, **kwargs)
self._initialize_function(**kwargs)
# Optional post processing function
self.post_process = None
if 'post_process' in kwargs:
self.post_process = kwargs['post_process']
if 'adaptor_class' in kwargs:
adaptor_class = kwargs['adaptor_class']
self.mesh_adaptor = adaptor_class(**kwargs)
else:
raise ValueError('Must provide a mesh adaptor class')
self.color_pattern = kwargs.get('color_pattern') or None
self.tiles = None
self.face_types = {}
self.vert_types = {}
self._process_extra_parameters(**kwargs)
def create_mesh(self):
self._initialize_tiles()
self.mesh_adaptor.create_empty_mesh()
self._calculate_verts()
self._calculate_faces()
if self.color_pattern:
self._calculate_colors()
self.mesh_adaptor.finish_mesh()
if self.post_process:
# Run user defined post-processing code
# Need to pass self here (this could be designed better)
self.post_process(self)
return self.mesh_adaptor.get_mesh()
def inspect(self):
print("\n=== %s ===\n" % (self.__class__.__name__))
for i in range(len(self.tiles)):
self.tiles[i].inspect(tile_number=i)
# Note, would like these to be a class properties,
# but the designers of Python flip-flop about
# how to implement it.
@classmethod
def num_color_patterns(cls):
if cls.metadata is None:
return 0
return cls.metadata.num_color_patterns
@classmethod
def num_extra_parameters(cls):
if cls.metadata is None:
return 0
return len(cls.metadata.extra_parameters)
# Below are protected
def _initialize_function(self, **kwargs):
self.f = None
if 'simple_2d' in kwargs:
u_multiplier_2d = 1.0
if self.metadata and self.metadata.uv_ratio:
v_multiplier_2d = 1.0 / self.metadata.uv_ratio
else:
v_multiplier_2d = 1.0
tile_aspect = self.tile_generator.v_num / self.tile_generator.u_num
multiplier_2d = kwargs.get('multiplier_2d', 1.0)
u_multiplier_2d *= multiplier_2d
v_multiplier_2d *= multiplier_2d * tile_aspect
translate_2d = kwargs.get('translate_2d', (0, 0))
# Simple xy-plane
self.f = lambda u, v: (translate_2d[0] + u_multiplier_2d * u,
translate_2d[1] + v_multiplier_2d * v,
0.0)
# Just to test how the corners are going to map ...
# top_left = self.tile_generator.corners[0]
# bottom_right = self.tile_generator.corners[3]
# print(self.f(*top_left), self.f(*bottom_right))
elif 'function' in kwargs:
self.f = kwargs['function']
else:
raise ValueError('Must specify a function')
def _initialize_tiles(self):
self.tiles = self.tile_generator.create_tiles()
def _calculate_verts(self):
for tile in self.tiles:
tile.calculate_verts()
def _calculate_faces(self):
for tile in self.tiles:
tile.calculate_faces()
def _calculate_colors(self):
self.mesh_adaptor.initialize_colors()
for tile in self.tiles:
tile.calculate_colors()
def _process_extra_parameters(self, **kwargs):
self.extra_parameters = {}
parameters_info = self.metadata.extra_parameters
if not parameters_info:
return
for parameter in parameters_info:
parameter_info = parameters_info[parameter]
if parameter not in kwargs:
continue
value = kwargs.get(parameter)
if parameter_info['type'] == 'float':
self._process_float_extra_parameter(parameter,
value,
parameter_info)
def _process_float_extra_parameter(self, parameter, value, parameter_info):
max_value = parameter_info.get('max')
min_value = parameter_info.get('min')
if max_value is not None and value > max_value:
raise ValueError('Parameter {} ({}) exceeds maximum ({})'
.format(parameter, value, max_value))
if min_value is not None and value < min_value:
raise ValueError('Parameter {} ({}) below minimum ({})'
.format(parameter, value, min_value))
self.extra_parameters[parameter] = value

View File

@ -0,0 +1,116 @@
from tessagon.types.hex_tessagon import HexTessagon
from tessagon.types.tri_tessagon import TriTessagon
from tessagon.types.octo_tessagon import OctoTessagon
from tessagon.types.rhombus_tessagon import RhombusTessagon
from tessagon.types.hex_tri_tessagon import HexTriTessagon
from tessagon.types.hex_square_tri_tessagon import HexSquareTriTessagon
from tessagon.types.square_tessagon import SquareTessagon
from tessagon.types.pythagorean_tessagon import PythagoreanTessagon
from tessagon.types.brick_tessagon import BrickTessagon
from tessagon.types.dodeca_tessagon import DodecaTessagon
from tessagon.types.square_tri_tessagon import SquareTriTessagon
from tessagon.types.weave_tessagon import WeaveTessagon
from tessagon.types.floret_tessagon import FloretTessagon
from tessagon.types.hex_big_tri_tessagon import HexBigTriTessagon
from tessagon.types.zig_zag_tessagon import ZigZagTessagon
from tessagon.types.dissected_square_tessagon import DissectedSquareTessagon
from tessagon.types.square_tri2_tessagon import SquareTri2Tessagon
from tessagon.types.dodeca_tri_tessagon import DodecaTriTessagon
from tessagon.types.dissected_triangle_tessagon \
import DissectedTriangleTessagon
from tessagon.types.dissected_hex_quad_tessagon \
import DissectedHexQuadTessagon
from tessagon.types.dissected_hex_tri_tessagon \
import DissectedHexTriTessagon
from tessagon.types.penta_tessagon import PentaTessagon
from tessagon.types.penta2_tessagon import Penta2Tessagon
from tessagon.types.big_hex_tri_tessagon import BigHexTriTessagon
from tessagon.types.stanley_park_tessagon import StanleyParkTessagon
from tessagon.types.valemount_tessagon import ValemountTessagon
from tessagon.types.cloverdale_tessagon import CloverdaleTessagon
from tessagon.types.islamic_hex_stars_tessagon import IslamicHexStarsTessagon
from tessagon.types.islamic_stars_crosses_tessagon \
import IslamicStarsCrossesTessagon
ALL = [SquareTessagon,
HexTessagon,
TriTessagon,
OctoTessagon,
HexTriTessagon,
HexSquareTriTessagon,
DodecaTessagon,
SquareTriTessagon,
SquareTri2Tessagon,
DodecaTriTessagon,
BigHexTriTessagon,
RhombusTessagon,
FloretTessagon,
DissectedSquareTessagon,
DissectedTriangleTessagon,
DissectedHexQuadTessagon,
DissectedHexTriTessagon,
PentaTessagon,
Penta2Tessagon,
PythagoreanTessagon,
BrickTessagon,
WeaveTessagon,
HexBigTriTessagon,
ZigZagTessagon,
ValemountTessagon,
CloverdaleTessagon,
StanleyParkTessagon,
IslamicHexStarsTessagon,
IslamicStarsCrossesTessagon]
class TessagonDiscovery:
def __init__(self, **kwargs):
self.classes = kwargs.get('classes', ALL)
def count(self):
return len(self.classes)
def to_list(self):
return self.classes
def inverse(self):
other_classes = list(set(ALL) - set(self.classes))
return TessagonDiscovery(classes=other_classes)
def __add__(self, other):
new_classes = list(set(self.classes) | set(other.classes))
return TessagonDiscovery(classes=new_classes)
def __sub__(self, other):
new_classes = list(set(self.classes) - set(other.classes))
return TessagonDiscovery(classes=new_classes)
def with_color_patterns(self):
results = []
for klass in self.classes:
if klass.metadata is None:
continue
if klass.metadata.has_color_patterns:
results.append(klass)
return TessagonDiscovery(classes=results)
def with_classification(self, classification):
results = []
for klass in self.classes:
if klass.metadata is None:
continue
if klass.metadata.has_classification(classification):
results.append(klass)
return TessagonDiscovery(classes=results)
@classmethod
def get_class(cls, class_name):
if class_name in globals():
klass = globals()[class_name]
if klass in ALL:
return klass
raise ValueError(class_name + ' is not recognized by Tessagon')

View File

@ -0,0 +1,61 @@
class TessagonMetadata:
CLASSIFICATION_MAP = {
'regular': 'Regular tiling',
'archimedean': 'Archimedean tiling',
'laves': 'Laves tiling',
'non_edge': 'Non-edge-to-edge tiling',
'non_convex': 'Non-convex tiling'
}
def __init__(self, **kwargs):
self._name = kwargs.get('name')
if not self._name:
raise ValueError('No name set')
self._num_color_patterns = kwargs.get('num_color_patterns', 0)
self._classification = kwargs.get('classification', 'misc')
self._shapes = kwargs.get('shapes', [])
self._sides = kwargs.get('sides', [])
self._uv_ratio = kwargs.get('uv_ratio', None)
self._extra_parameters = kwargs.get('extra_parameters', {})
@property
def name(self):
return self._name
@property
def num_color_patterns(self):
return self._num_color_patterns
@property
def has_color_patterns(self):
return self._num_color_patterns > 0
def has_shape(self, shape):
if shape in self._shapes:
return True
return False
@property
def classification(self):
return self._classification
def has_classification(self, classification):
return self._classification == classification
@property
def human_readable_classification(self):
return self.__class__.CLASSIFICATION_MAP[self._classification]
@property
def uv_ratio(self):
# Aspect ratio U/V for best looking proportions
# Roughly, assuming a uniform input function,
# we would want to select the u, v inputs so that:
# (u_range[1] - u_range[0]) / u_num
# = uv_ratio * (v_range[1] - v_range[0]) / v_num
# Or scale the input function so get similar proportions.
return self._uv_ratio
@property
def extra_parameters(self):
return self._extra_parameters

View File

@ -0,0 +1,338 @@
from tessagon.core.abstract_tile import AbstractTile
class Tile(AbstractTile):
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.mesh_adaptor = tessagon.mesh_adaptor
self.verts = self.init_verts()
self.faces = self.init_faces()
self.color_pattern = kwargs.get('color_pattern') or None
if self.faces and self.color_pattern:
self.face_paths = self.all_face_paths()
def validate(self):
# Subclass decides if this should be done
pass
def add_vert(self, index_keys, ratio_u, ratio_v, **kwargs):
# Use the mesh adaptor to create a vertex.
# In reality, multiple vertices may get defined if symmetry is declared
vert = self._get_vert(index_keys)
if vert is None:
coords = self.f(*self.blend(ratio_u, ratio_v))
vert = self.mesh_adaptor.create_vert(coords)
self._set_vert(index_keys, vert)
if 'vert_type' in kwargs:
if not kwargs['vert_type'] in self.tessagon.vert_types:
self.tessagon.vert_types[kwargs['vert_type']] = []
self.tessagon.vert_types[kwargs['vert_type']].append(vert)
if vert is not None:
equivalent_verts = kwargs.get('equivalent', [])
for equivalent_vert in equivalent_verts:
self.set_equivalent_vert(*equivalent_vert, vert)
# We add additional vertices by flipping 'left', 'right' etc
# if the tile has some kind of symmetry defined
self._create_symmetric_verts(index_keys, ratio_u, ratio_v, **kwargs)
# On the boundary, make sure equivalent vertices are set on
# neighbor tiles
self._set_equivalent_neighbor_verts(index_keys, vert, **kwargs)
return vert
def set_equivalent_vert(self, neighbor_keys, index_keys, vert, **kwargs):
# On boundary, the vert on a neighbor is equivalent to this vert
# This is usually only called indirectly via add_vert, but check out
# PythagoreanTile for an example of direct usage
if vert is None:
return None
tile = self.get_neighbor_tile(neighbor_keys)
if tile is None:
return None
tile._set_vert(self._index_path(index_keys, neighbor_keys), vert)
def add_face(self, index_keys, vert_index_keys_list, **kwargs):
# Use the mesh adaptor to create a face.
# In reality, multiple faces may get defined if symmetry is declared
face = self._get_face(index_keys)
if face is None:
verts = self._get_verts_from_list(vert_index_keys_list)
if verts is not None:
face = \
self._make_face(index_keys, verts, **kwargs)
if face is not None:
equivalent_faces = kwargs.get('equivalent', [])
for equivalent_face in equivalent_faces:
self.set_equivalent_face(*equivalent_face, face)
# We add additional faces by flipping 'left', 'right' etc
# if the tile has some kind of symmetry defined
self._create_symmetric_faces(index_keys, vert_index_keys_list,
**kwargs)
return face
def _make_face(self, index_keys, verts, **kwargs):
face = self.mesh_adaptor.create_face(verts)
self._set_face(index_keys, face)
# The tessagon might keep a list of specific face types
if 'face_type' in kwargs:
if not kwargs['face_type'] in self.tessagon.face_types:
self.tessagon.face_types[kwargs['face_type']] = []
self.tessagon.face_types[kwargs['face_type']].append(face)
# On the boundary, make sure equivalent faces are set on neighbor tiles
self._set_equivalent_neighbor_faces(index_keys, face, **kwargs)
return face
def num_color_patterns(self):
return self.tessagon.num_color_patterns()
def calculate_colors(self):
if self.color_pattern > self.num_color_patterns():
raise ValueError("color_pattern must be below %d" %
(self.num_color_patterns()))
method_name = "color_pattern%d" % (self.color_pattern)
method = getattr(self, method_name)
if not callable(method):
raise ValueError("%s is not a callable color pattern" %
(method_name))
method()
def color_face(self, index_keys, color_index):
face = self._get_face(index_keys)
if face is None:
return
self.mesh_adaptor.color_face(face, color_index)
def set_equivalent_face(self, neighbor_keys, index_keys, face, **kwargs):
# On boundary, the face on a neighbor is equivalent to this face
# This is usually only called indirectly via add_face, but check out
# PythagoreanTile for an example of direct usage
tile = self.get_neighbor_tile(neighbor_keys)
if tile is None:
return None
tile._set_face(self._index_path(index_keys, neighbor_keys), face)
def all_face_paths(self, faces=None, base_path=None):
if faces is None:
faces = self.faces
if base_path is None:
base_path = []
paths = []
for index in faces:
new_base_path = base_path + [index]
if type(faces[index]) is dict:
paths += self.all_face_paths(faces[index], new_base_path)
else:
paths.append(new_base_path)
return paths
def color_paths(self, paths, color, color_other=None):
for path in self.face_paths:
if path in paths:
self.color_face(path, color)
elif color_other:
self.color_face(path, color_other)
def color_paths_hash(self, hash, color_other=None):
for path in self.face_paths:
for color in hash:
done = False
if path in hash[color]:
self.color_face(path, color)
done = True
break
if color_other and not done:
self.color_face(path, color_other)
# Below are protected
def _get_vert(self, index_keys):
return self._get_nested_list_value(self.verts, index_keys)
def _set_vert(self, index_keys, value):
self._set_nested_list_value(self.verts, index_keys, value)
def _get_face(self, index_keys):
return self._get_nested_list_value(self.faces, index_keys)
def _set_face(self, index_keys, value):
self._set_nested_list_value(self.faces, index_keys, value)
def _get_neighbor_vert(self, neighbor_keys, index_keys):
# See comment about neighbors in AbstractTile
tile = self.get_neighbor_tile(neighbor_keys)
if tile is None:
return None
return tile._get_vert(self._index_path(index_keys, neighbor_keys))
def _create_symmetric_verts(self, index_keys, ratio_u, ratio_v, **kwargs):
# The 'symmetry' keyword is just to ensure we don't recurse forever
if 'symmetry' not in kwargs:
extra_args = {'symmetry': True}
if self.rot_symmetric == 180:
rot_keys = self._rotate_index(index_keys)
self.add_vert(rot_keys, 1.0 - ratio_u, 1 - ratio_v,
**{**kwargs, **extra_args})
elif self.rot_symmetric == 90:
rot_keys = self._rotate_index(index_keys)
self.add_vert(rot_keys, 1.0 - ratio_v, ratio_u,
**{**kwargs, **extra_args})
rot_keys = self._rotate_index(rot_keys)
self.add_vert(rot_keys, 1.0 - ratio_u, 1 - ratio_v,
**{**kwargs, **extra_args})
rot_keys = self._rotate_index(rot_keys)
self.add_vert(rot_keys, ratio_v, 1 - ratio_u,
**{**kwargs, **extra_args})
if self.u_symmetric:
# Add reflection about u
u_flip_keys = self._u_flip(index_keys)
self.add_vert(u_flip_keys, 1.0 - ratio_u, ratio_v,
**{**kwargs, **extra_args})
if self.v_symmetric:
# Add diagonally across
uv_flip_keys = self._v_flip(u_flip_keys)
self.add_vert(uv_flip_keys, 1.0 - ratio_u, 1.0 - ratio_v,
**{**kwargs, **extra_args})
if self.v_symmetric:
# Add reflection about v
v_flip_keys = self._v_flip(index_keys)
self.add_vert(v_flip_keys, ratio_u, 1.0 - ratio_v,
**{**kwargs, **extra_args})
def _set_equivalent_neighbor_verts(self, index_keys, vert, **kwargs):
if 'u_boundary' in kwargs:
self._set_u_equivalent_vert(index_keys, vert, **kwargs)
if 'v_boundary' in kwargs:
self._set_v_equivalent_vert(index_keys, vert, **kwargs)
if 'corner' in kwargs:
self._set_u_equivalent_vert(index_keys, vert, **kwargs)
self._set_v_equivalent_vert(index_keys, vert, **kwargs)
self._set_uv_equivalent_vert(index_keys, vert, **kwargs)
# Handle vert on left/right boundary
def _set_u_equivalent_vert(self, index_keys, vert, **kwargs):
u_index = self._u_index(index_keys)
u_flip_keys = self._u_flip(index_keys)
self.set_equivalent_vert([u_index], u_flip_keys, vert, **kwargs)
# Handle vert on top/bottom boundary
def _set_v_equivalent_vert(self, index_keys, vert, **kwargs):
v_index = self._v_index(index_keys)
v_flip_keys = self._v_flip(index_keys)
self.set_equivalent_vert([v_index], v_flip_keys, vert, **kwargs)
# Handle vert on corner, equivalent to vert on diagonal tile
def _set_uv_equivalent_vert(self, index_keys, vert, **kwargs):
u_index = self._u_index(index_keys)
v_index = self._v_index(index_keys)
u_flip_keys = self._u_flip(index_keys)
uv_flip_keys = self._v_flip(u_flip_keys)
self.set_equivalent_vert([u_index, v_index], uv_flip_keys, vert,
**kwargs)
def _get_verts_from_list(self, vert_index_keys_list):
verts = []
for vert_index_keys in vert_index_keys_list:
if isinstance(vert_index_keys, list) \
and isinstance(vert_index_keys[0], list):
vert = self._get_neighbor_vert(vert_index_keys[0],
vert_index_keys[1])
else:
vert = self._get_vert(vert_index_keys)
if vert is None:
return None
verts.append(vert)
return verts
def _create_symmetric_faces(self, index_keys, vert_index_keys_list,
**kwargs):
# The 'symmetry' keyword is just to ensure we don't recurse forever
if 'symmetry' not in kwargs:
extra_args = {'symmetry': True}
if self.rot_symmetric == 180:
rot_keys = self._rotate_index(index_keys)
rot_vert_index_keys_list \
= self._rotate_index(vert_index_keys_list)
if 'equivalent' in kwargs:
equivalent_faces = kwargs['equivalent']
kwargs = kwargs.copy()
kwargs['equivalent'] = \
[self._rotate_index(equivalent_face)
for equivalent_face in equivalent_faces]
self.add_face(rot_keys, rot_vert_index_keys_list,
**{**kwargs, **extra_args})
if self.u_symmetric:
# Add reflection about u
u_flip_keys = self._u_flip(index_keys)
u_flip_vert_index_keys_list \
= self._u_flip(vert_index_keys_list)
self.add_face(u_flip_keys, u_flip_vert_index_keys_list,
**{**kwargs, **extra_args})
if self.v_symmetric:
# Add diagonally across
uv_flip_keys = self._v_flip(u_flip_keys)
uv_flip_vert_index_keys_list \
= self._v_flip(u_flip_vert_index_keys_list)
self.add_face(uv_flip_keys, uv_flip_vert_index_keys_list,
**{**kwargs, **extra_args})
if self.v_symmetric:
# Add reflection about v
v_flip_keys = self._v_flip(index_keys)
v_flip_vert_index_keys_list \
= self._v_flip(vert_index_keys_list)
self.add_face(v_flip_keys, v_flip_vert_index_keys_list,
**{**kwargs, **extra_args})
def _set_equivalent_neighbor_faces(self, index_keys, face, **kwargs):
if 'u_boundary' in kwargs:
self._set_u_equivalent_face(index_keys, face, **kwargs)
if 'v_boundary' in kwargs:
self._set_v_equivalent_face(index_keys, face, **kwargs)
if 'corner' in kwargs:
self._set_u_equivalent_face(index_keys, face, **kwargs)
self._set_v_equivalent_face(index_keys, face, **kwargs)
self._set_uv_equivalent_face(index_keys, face, **kwargs)
# Handle face on left/right boundary
def _set_u_equivalent_face(self, index_keys, face, **kwargs):
u_index = self._u_index(index_keys)
u_flip_keys = self._u_flip(index_keys)
self.set_equivalent_face([u_index], u_flip_keys, face, **kwargs)
# Handle face on top/bottom boundary
def _set_v_equivalent_face(self, index_keys, face, **kwargs):
v_index = self._v_index(index_keys)
v_flip_keys = self._v_flip(index_keys)
self.set_equivalent_face([v_index], v_flip_keys, face, **kwargs)
# Handle face on corner, equivalent to face on diagonal tile
def _set_uv_equivalent_face(self, index_keys, face, **kwargs):
u_index = self._u_index(index_keys)
v_index = self._v_index(index_keys)
u_flip_keys = self._u_flip(index_keys)
uv_flip_keys = self._v_flip(u_flip_keys)
self.set_equivalent_face([u_index, v_index], uv_flip_keys, face,
**kwargs)

View File

@ -0,0 +1,69 @@
from tessagon.core.value_blend import ValueBlend
class TileGenerator(ValueBlend):
# This is intended to be an abstract class to generate tiles,
# but it's quite tied to a grid structure, so it might make
# sense to merge with GridTileGenerator
def __init__(self, tessagon, **kwargs):
self.tessagon = tessagon
# Corners is list of tuples:
# [topleft, topright, bottomleft, bottomright]
self.corners = None
self._init_corners(**kwargs)
self.u_num = kwargs.get('u_num')
self.v_num = kwargs.get('v_num')
if not self.u_num or not self.v_num:
raise ValueError("Make sure u_num and v_num intervals are set")
self.u_cyclic = kwargs.get('u_cyclic', True)
self.v_cyclic = kwargs.get('v_cyclic', True)
self.v_twist = kwargs.get('v_twist', False)
self.u_twist = kwargs.get('u_twist', False)
# TODO: delete these?
self.u_phase = kwargs.get('u_phase', 0.0)
self.v_phase = kwargs.get('v_phase', 0.0)
self.u_shear = kwargs.get('u_shear', 0.0)
self.v_shear = kwargs.get('v_shear', 0.0)
# Note: id_prefix is not used for calculation, just debugging
self.id_prefix = self.tessagon.__class__.__name__
if 'id_prefix' in kwargs:
self.id_prefix = kwargs['id_prefix']
self.fingerprint_offset = kwargs.get('fingerprint_offset') or None
self.color_pattern = kwargs.get('color_pattern') or None
def create_tile(self, u, v, corners, **kwargs):
extra_args = {'corners': corners,
'fingerprint': [u, v]}
if self.fingerprint_offset:
extra_args['fingerprint'][0] += self.fingerprint_offset[0]
extra_args['fingerprint'][1] += self.fingerprint_offset[1]
if self.id_prefix:
extra_args['id'] = "%s[%d][%d]" % (self.id_prefix, u, v)
if self.color_pattern:
extra_args['color_pattern'] = self.color_pattern
extra_parameters = self.tessagon.extra_parameters
if extra_parameters:
for parameter in extra_parameters:
extra_args[parameter] = extra_parameters[parameter]
tile_class = self.tessagon.__class__.tile_class
return tile_class(self.tessagon,
**{**kwargs, **extra_args})
def create_tiles(self):
self.initialize_tiles()
self.initialize_neighbors()
tiles = self.get_tiles()
for tile in tiles:
tile.validate()
return tiles

View File

@ -0,0 +1,46 @@
def right_tile(index_keys):
return [['right'], index_keys]
def left_tile(index_keys):
return [['left'], index_keys]
def top_tile(index_keys):
return [['top'], index_keys]
def bottom_tile(index_keys):
return [['bottom'], index_keys]
def left_top_tile(index_keys):
return [['left', 'top'], index_keys]
def left_bottom_tile(index_keys):
return [['left', 'bottom'], index_keys]
def right_top_tile(index_keys):
return [['right', 'top'], index_keys]
def right_bottom_tile(index_keys):
return [['right', 'bottom'], index_keys]
def bottom_left_tile(index_keys):
return [['bottom', 'left'], index_keys]
def bottom_right_tile(index_keys):
return [['bottom', 'right'], index_keys]
def top_left_tile(index_keys):
return [['top', 'left'], index_keys]
def top_right_tile(index_keys):
return [['top', 'right'], index_keys]

View File

@ -0,0 +1,35 @@
class ValueBlend:
def _init_corners(self, **kwargs):
# Corners is list of tuples:
# [bottomleft, bottomright, topleft, topright]
if 'corners' in kwargs:
self.corners = kwargs['corners']
if len(self.corners) != 4 or \
any(len(v) != 2 for v in self.corners):
raise ValueError("corner should be a list of four tuples, "
"set either option 'corners' "
"or options 'u_range' and 'v_range'")
elif 'u_range' in kwargs and 'v_range' in kwargs:
self.corners = [[kwargs['u_range'][0], kwargs['v_range'][0]],
[kwargs['u_range'][1], kwargs['v_range'][0]],
[kwargs['u_range'][0], kwargs['v_range'][1]],
[kwargs['u_range'][1], kwargs['v_range'][1]]]
else:
raise ValueError("Must set either option "
"'corners' or options 'u_range' and 'v_range'")
def _blend_tuples(self, tuple1, tuple2, ratio):
out = [None, None]
for i in range(2):
out[i] = (1 - ratio) * tuple1[i] + ratio * tuple2[i]
return out
def blend(self, ratio_u, ratio_v):
uv0 = self._blend_tuples(self.corners[0],
self.corners[1],
ratio_u)
uv1 = self._blend_tuples(self.corners[2],
self.corners[3],
ratio_u)
return self._blend_tuples(uv0, uv1, ratio_v)

View File

@ -0,0 +1,157 @@
from math import sin, cos, sqrt, pi
def plane(u, v):
# u_cyclic = False, v_cyclic = False
return [u, v, 0]
def other_plane(u, v):
# u_cyclic = False, v_cyclic = False
return [v, u, 0]
def general_torus(r1, r2, u, v):
x = (r1 + r2*cos(v*2*pi))*cos(u*2*pi)
y = (r1 + r2*cos(v*2*pi))*sin(u*2*pi)
z = r2*sin(v*2*pi)
return [x, y, z]
def normalize_value(v):
if (v < 0.0):
while (v < 0.0):
v += 1.0
else:
while (v > 1.0):
v -= 1.0
return v
def warp_var(v, factor):
# For any factor, maps 0-->0, 1-->1
# factor = 0 is identity
# factor > 0 for a wee pinch at v = 1/2
v = normalize_value(v)
h = 2 * (v - 0.5)
i = h + factor*h**3
return 0.5*(1.0 + i / (1.0 + factor))
def torus(u, v):
# u_cyclic = True, v_cyclic = True
r1 = 5.0
r2 = 1.0
return general_torus(r1, r2, u, warp_var(v, 0.2))
def other_torus(u, v):
# u_cyclic = True, v_cyclic = True
return torus(v, u)
def general_cylinder(r, h, u, v):
x = r*cos(u*2*pi)
y = r*sin(u*2*pi)
z = h*(v - 0.5)
return [x, y, z]
def cylinder(u, v):
# u_cyclic = True, v_cyclic = False
r = 5.0
h = 3.5
return general_cylinder(r, h, u, v)
def other_cylinder(u, v):
# u_cyclic = False, v_cyclic = True
return cylinder(v, u)
def general_paraboloid(scale1, scale2, displace, u, v):
return [scale1*u, scale1*v, displace + scale2 * (u**2 + v**2)]
def paraboloid(u, v):
# u_cyclic = False, v_cyclic = False
return general_paraboloid(4, 3, -3, u, v)
def general_one_sheet_hyperboloid(scale1, scale2, u, v):
c = scale1 * sqrt(1 + u**2)
v1 = 2*pi*v
x = c * cos(v1)
y = c * sin(v1)
z = scale2 * u
return [x, y, z]
def one_sheet_hyperboloid(u, v):
# u_cyclic = False, v_cyclic = True
return general_one_sheet_hyperboloid(3, 2, u, v)
def general_ellipsoid(r1, r2, r3, u, v):
# u_cyclic = True, v_cyclic = False
u1 = 2*pi*u
v1 = pi*normalize_value(warp_var(v + 0.5, 0.8)-0.5)
sinv1 = sin(v1)
return [r1 * cos(u1) * sinv1, r2 * sin(u1) * sinv1, r3 * cos(v1)]
def sphere(u, v):
return general_ellipsoid(4, 4, 4, u, v)
def general_mobius(r, h, u, v):
offset = h*(v-0.5)*sin(u*pi)
x = (r + offset)*cos(u*2*pi)
y = (r + offset)*sin(u*2*pi)
z = h*(v-0.5)*cos(u*pi)
return [x, y, z]
def mobius(u, v):
# u_cyclic = False, v_cyclic = True
# u_twist = True, v_twist = False
r = 5.0
h = 2.0
return general_mobius(r, h, v, u)
def other_mobius(u, v):
# u_cyclic = True, v_cyclic = False
# u_twist = False, v_twist = True
return mobius(v, u)
def general_klein(scale, u, v):
# Adapted from http://paulbourke.net/geometry/klein/
u1 = 2*pi*normalize_value(warp_var(u + 0.5, 0.6)-0.5)
v1 = 2*pi*normalize_value(v+0.25)
c1 = cos(u1)
c2 = sin(u1)
r = 4.0 - 2.0*c1
if u1 <= pi:
x = 6*c1*(1.0 + c2) + r*c1*cos(v1)
y = 16*c2 + r*c2*cos(v1)
else:
x = 6*c1*(1.0 + c2) + r*cos(v1+pi)
y = 16*c2
z = r * sin(v1)
return [scale*x, scale*y, scale*z]
def klein(u, v):
# u_cyclic = True, v_cyclic = True
# u_twist = False, v_twist = True
return general_klein(0.25, u, v)
def other_klein(u, v):
# u_cyclic = True, v_cyclic = True
# u_twist = True, v_twist = False
return general_klein(0.25, v, u)

View File

@ -0,0 +1,175 @@
from math import atan2, sqrt, sin, cos, pi
from tessagon.core.tessagon import Tessagon
from tessagon.core.alternating_tile import AlternatingTile
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import right_tile, left_tile, \
top_tile, top_left_tile, bottom_tile
metadata = TessagonMetadata(name='Big Hexagons and Triangles',
num_color_patterns=1,
classification='archimedean',
shapes=['hexagons', 'triangles'],
sides=[6, 3],
uv_ratio=1.0/sqrt(3),
extra_parameters={
'hexagon_ratio': {
'type': 'float',
'min': 0.0,
# Any higher than 0.70, and verts are
# pushed to neighboring tiles
'max': 0.70,
'default': 0.5,
'description':
'Control the size of the Hexagons'
}
})
class BigHexTriTile(AlternatingTile):
# See the SVG for decomposition:
# https://raw.githubusercontent.com/cwant/tessagon/master/documentation/code/big_hex_tri.svg
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = False
self.v_symmetric = False
self.hexagon_ratio = kwargs.get('hexagon_ratio', 0.5)
# in u units
self.hex_radius = 4 * self.hexagon_ratio / sqrt(7)
# multiplier to get v units ...
self.uv_ratio = self.tessagon.metadata.uv_ratio
# Tilt
self.theta_offset = -atan2(1, 3 * sqrt(3)) + pi/6
self.hex_theta = [(self.theta_offset + number * pi / 3.0)
for number in range(6)]
def hex_vert_coord(self, center, number):
# number in range(6)
return [center[0] +
self.hex_radius * cos(self.hex_theta[number]),
center[1] +
self.hex_radius * sin(self.hex_theta[number]) * self.uv_ratio]
def init_verts(self):
if self.tile_type == 0:
verts = {0: None,
1: None}
else:
verts = {2: None,
3: None,
4: None,
5: None}
return verts
def init_faces(self):
if self.tile_type == 0:
faces = {'A': None,
'B': None,
'C': None,
'D': None,
'E': None,
'F': None,
'G': None,
'H': None}
else:
faces = {'I': None,
'J': None,
'K': None,
'L': None,
'M': None,
'N': None,
'O': None,
'P': None,
'Q': None,
'R': None}
return faces
def calculate_verts(self):
if self.tile_type == 0:
self.add_vert([0], *self.hex_vert_coord([0, 1], 5))
self.add_vert([1], *self.hex_vert_coord([1, 0], 2))
else:
self.add_vert([2], *self.hex_vert_coord([1, 1], 3))
self.add_vert([3], *self.hex_vert_coord([1, 1], 4))
self.add_vert([4], *self.hex_vert_coord([0, 0], 1))
self.add_vert([5], *self.hex_vert_coord([0, 0], 0))
def calculate_faces(self):
if self.tile_type == 0:
self.add_face('A',
[0,
top_tile(5),
top_tile(4),
top_left_tile(1),
left_tile(2),
left_tile(3)],
equivalent=[top_tile('T'),
top_left_tile('H'),
left_tile('I')])
self.add_face('B',
[0,
right_tile(2),
top_tile(5)],
equivalent=[top_tile('S'),
right_tile('K')])
self.add_face('C',
[0,
right_tile(4),
right_tile(2)],
equivalent=[right_tile('L')])
self.add_face('D',
[0,
1,
right_tile(4)],
equivalent=[right_tile('M')])
self.add_face('E',
[1,
0,
left_tile(3)],
equivalent=[left_tile('P')])
self.add_face('F',
[1,
left_tile(3),
left_tile(5)],
equivalent=[left_tile('Q')])
self.add_face('G',
[1,
left_tile(5),
bottom_tile(2)],
equivalent=[left_tile('R')])
else:
self.add_face('N',
[2,
4,
3])
self.add_face('O',
[3,
4,
5])
def color_pattern1(self):
if self.tile_type == 0:
self.color_face('A', 2)
self.color_face('B', 1)
self.color_face('D', 1)
self.color_face('F', 1)
else:
self.color_face('N', 1)
class BigHexTriTessagon(Tessagon):
tile_class = BigHexTriTile
metadata = metadata

View File

@ -0,0 +1,76 @@
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, bottom_tile
metadata = TessagonMetadata(name='Bricks',
num_color_patterns=1,
classification='non_edge',
shapes=['rectangles'],
sides=[4],
uv_ratio=1.0)
class BrickTile(Tile):
# top
#
# -o..o- -o..o- r
# ^ .|..|. l .|..|. i
# | .o--o. e .o--o. g
# | .|..|. f .|..|. h
# | -o..o- t -o..o- t
# V
# U ---> bottom
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': None, 'middle': None, 'bottom': None},
'right': {'top': None, 'middle': None, 'bottom': None}}
def init_faces(self):
return {'left': None, 'right': None, 'top': None, 'bottom': None}
def calculate_verts(self):
# corners: set bottom-left ... symmetry takes care of other 3 corners
self.add_vert(['left', 'bottom'], 0.25, 0.0, v_boundary=True)
# left middle, symmetry also creates right middle
self.add_vert(['left', 'middle'], 0.25, 0.5)
def calculate_faces(self):
# Add left, symmetry gives the right side face
self.add_face('left',
[['left', 'top'],
['left', 'middle'],
['left', 'bottom'],
# Verts on neighbor tiles:
left_tile(['right', 'bottom']),
left_tile(['right', 'middle']),
left_tile(['right', 'top'])],
u_boundary=True)
# Add bottom, symmetry gives the top face
self.add_face('bottom',
[['right', 'bottom'],
['right', 'middle'],
['left', 'middle'],
['left', 'bottom'],
# Verts on neighbor tiles:
bottom_tile(['left', 'middle']),
bottom_tile(['right', 'middle'])],
v_boundary=True)
def color_pattern1(self):
self.color_paths([
['left'],
['right']
], 1, 0)
class BrickTessagon(Tessagon):
tile_class = BrickTile
metadata = metadata

View File

@ -0,0 +1,143 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, top_left_tile, top_tile
metadata = TessagonMetadata(name='Cloverdale',
num_color_patterns=1,
classification='non_edge',
shapes=['squares', 'pentagons'],
sides=[4, 5],
uv_ratio=1.0)
class CloverdaleTile(Tile):
# See the SVG for decomposition:
# https://raw.githubusercontent.com/cwant/tessagon/master/documentation/code/cloverdale.svg
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'inner': None,
'outer': None,
'u_border': None,
'v_border': None},
'middle': {'inner': None,
'outer': None},
'bottom': {'inner': None,
'outer': None,
'u_border': None,
'v_border': None}},
'center': {'top': {'inner': None,
'outer': None},
'middle': None,
'bottom': {'inner': None,
'outer': None}},
'right': {'top': {'inner': None,
'outer': None,
'u_border': None,
'v_border': None},
'middle': {'inner': None,
'outer': None},
'bottom': {'inner': None,
'outer': None,
'u_border': None,
'v_border': None}}}
def init_faces(self):
return {'left': {'top': {'square': None,
'u_pentagon': None,
'v_pentagon': None},
'bottom': {'square': None,
'u_pentagon': None,
'v_pentagon': None},
'middle': {'square': None}},
'center': {'top': {'square': None},
'bottom': {'square': None}},
'right': {'top': {'square': None,
'u_pentagon': None,
'v_pentagon': None},
'bottom': {'square': None,
'u_pentagon': None,
'v_pentagon': None},
'middle': {'square': None}}}
def calculate_verts(self):
# a is the side length of square
# c is half diagonal of square
c = 1.0 / (sqrt(2.0) + 4.0)
a = sqrt(2.0) * c
# left top corner
self.add_vert(['left', 'top', 'inner'],
a / 2.0 + c, 1.0 - (a / 2.0 + c))
self.add_vert(['left', 'top', 'outer'], a / 2.0, 1.0 - a / 2.0)
self.add_vert(['left', 'top', 'u_border'],
0.0, 1.0 - a / 2.0, u_boundary=True)
self.add_vert(['left', 'top', 'v_border'],
a / 2.0, 1.0, v_boundary=True)
self.add_vert(['left', 'middle', 'inner'], a / 2.0, 0.5)
self.add_vert(['left', 'middle', 'outer'], 0.0, 0.5, u_boundary=True)
self.add_vert(['center', 'top', 'inner'], 0.5, 1.0 - a / 2.0)
self.add_vert(['center', 'top', 'outer'], 0.5, 1.0, v_boundary=True)
self.add_vert(['center', 'middle'], 0.5, 0.5)
def calculate_faces(self):
# Middle star
self.add_face(['left', 'top', 'square'],
[['left', 'top', 'v_border'],
['left', 'top', 'outer'],
['left', 'top', 'u_border'],
left_tile(['right', 'top', 'outer']),
left_tile(['right', 'top', 'v_border']),
top_left_tile(['right', 'bottom', 'outer']),
top_tile(['left', 'bottom', 'u_border']),
top_tile(['left', 'bottom', 'outer'])],
face_type='star',
corner=True)
self.add_face(['left', 'top', 'u_pentagon'],
[['left', 'top', 'u_border'],
['left', 'top', 'outer'],
['left', 'top', 'inner'],
['left', 'middle', 'inner'],
['left', 'middle', 'outer']])
self.add_face(['left', 'top', 'v_pentagon'],
[['left', 'top', 'v_border'],
['center', 'top', 'outer'],
['center', 'top', 'inner'],
['left', 'top', 'inner'],
['left', 'top', 'outer']])
self.add_face(['center', 'top', 'square'],
[['center', 'middle'],
['left', 'top', 'inner'],
['center', 'top', 'inner'],
['right', 'top', 'inner']])
self.add_face(['left', 'middle', 'square'],
[['center', 'middle'],
['left', 'bottom', 'inner'],
['left', 'middle', 'inner'],
['left', 'top', 'inner']])
def color_pattern1(self):
self.color_face(['left', 'top', 'square'], 1)
self.color_face(['left', 'middle', 'square'], 1)
self.color_face(['center', 'top', 'square'], 1)
self.color_face(['right', 'middle', 'square'], 1)
self.color_face(['center', 'bottom', 'square'], 1)
self.color_face(['left', 'top', 'u_pentagon'], 0)
self.color_face(['left', 'top', 'v_pentagon'], 0)
class CloverdaleTessagon(Tessagon):
tile_class = CloverdaleTile
metadata = metadata

View File

@ -0,0 +1,167 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile
metadata = TessagonMetadata(name='Hexagons Dissected with Quads',
num_color_patterns=2,
classification='laves',
shapes=['quads'],
sides=[4],
uv_ratio=1.0/sqrt(3.0))
class DissectedHexQuadTile(Tile):
# 19 verts, 14 quad faces (10 internal, 4 on boundary)
#
# O+++++++++++++++++++O+++++++++++++++++++O
# + + +
# + + +
# + + +
# + + +
# + + +
# + +O+ +
# + ++ ++ +
# + ++ ++ +
# ++O O++
# ++ + + ++
# ++ ++ ++ ++
# O + + O
# + + + +
# + + + +
# + + + +
# + + + +
# O+++++++++++++++++++O+++++++++++++++++++O
# + + + +
# + + + +
# + + + +
# + + + +
# + + + +
# O + + O
# ++ ++ ++ ++
# ++ + + ++
# ++O O++
# + ++ ++ +
# + ++ ++ +
# + + + +
# + O +
# + + +
# + + +
# + + +
# + + +
# O+++++++++++++++++++O+++++++++++++++++++O
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'corner': None,
'interior': None,
'u_boundary': None},
'middle': None,
'bottom': {'corner': None,
'interior': None,
'u_boundary': None}},
'right': {'top': {'corner': None,
'interior': None,
'u_boundary': None},
'middle': None,
'bottom': {'corner': None,
'interior': None,
'u_boundary': None}},
'center': {'middle': None,
'top': {'v_boundary': None,
'interior': None},
'bottom': {'v_boundary': None,
'interior': None}}}
def init_faces(self):
return {'left': {'top': {'v_boundary': None,
'u_boundary': None,
'middle': None},
'bottom': {'v_boundary': None,
'u_boundary': None,
'middle': None}},
'right': {'top': {'v_boundary': None,
'u_boundary': None,
'middle': None},
'bottom': {'v_boundary': None,
'u_boundary': None,
'middle': None}},
'center': {'top': None,
'bottom': None}}
def calculate_verts(self):
self.add_vert(['left', 'top', 'corner'], 0, 1, corner=True)
self.add_vert(['left', 'top', 'interior'], 0.25, 0.75)
self.add_vert(['left', 'top', 'u_boundary'], 0, 2.0/3.0,
u_boundary=True)
self.add_vert(['left', 'middle'], 0, 0.5, u_boundary=True)
self.add_vert(['center', 'middle'], 0.5, 0.5)
self.add_vert(['center', 'top', 'v_boundary'], 0.5, 1.0,
v_boundary=True)
self.add_vert(['center', 'top', 'interior'], 0.5, 5.0/6.0)
def calculate_faces(self):
self.add_face(['left', 'top', 'v_boundary'],
[['left', 'top', 'corner'],
['center', 'top', 'v_boundary'],
['center', 'top', 'interior'],
['left', 'top', 'interior']])
self.add_face(['left', 'top', 'u_boundary'],
[['left', 'top', 'corner'],
['left', 'top', 'interior'],
['left', 'top', 'u_boundary'],
left_tile(['right', 'top', 'interior'])],
u_boundary=True)
self.add_face(['left', 'top', 'middle'],
[['left', 'top', 'interior'],
['center', 'middle'],
['left', 'middle'],
['left', 'top', 'u_boundary']])
self.add_face(['center', 'top'],
[['center', 'middle'],
['left', 'top', 'interior'],
['center', 'top', 'interior'],
['right', 'top', 'interior']])
def color_pattern1(self):
self.color_paths([['left', 'top', 'middle'],
['center', 'top'],
['right', 'top', 'middle'],
['left', 'bottom', 'middle'],
['center', 'bottom'],
['right', 'bottom', 'middle']], 1, 0)
def color_pattern2(self):
if self.fingerprint[0] % 3 == 0:
self.color_paths([['left', 'top', 'middle'],
['center', 'top'],
['right', 'top', 'middle'],
['left', 'bottom', 'middle'],
['center', 'bottom'],
['right', 'bottom', 'middle']], 1, 0)
elif self.fingerprint[0] % 3 == 1:
self.color_paths([
['right', 'top', 'v_boundary'],
['right', 'bottom', 'v_boundary']], 1, 0)
elif self.fingerprint[0] % 3 == 2:
self.color_paths([
['left', 'top', 'v_boundary'],
['left', 'top', 'u_boundary'],
['left', 'bottom', 'v_boundary'],
['left', 'bottom', 'u_boundary']], 1, 0)
class DissectedHexQuadTessagon(Tessagon):
tile_class = DissectedHexQuadTile
metadata = metadata

View File

@ -0,0 +1,135 @@
from math import sqrt
from tessagon.types.dissected_hex_quad_tessagon import DissectedHexQuadTile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
metadata = TessagonMetadata(name='Hexagons Dissected with Triangles',
num_color_patterns=1,
classification='laves',
shapes=['triangles'],
sides=[3],
uv_ratio=1.0/sqrt(3.0))
# Uses the same configuration of vertices as DissectedHexQuadTile
class DissectedHexTriTile(DissectedHexQuadTile):
# 19 verts, 24 internal triangle faces
#
# O+++++++++++++++++++O+++++++++++++++++++O
# ++ ++ + ++ ++
# + + ++ + ++ + +
# + + ++ + ++ + +
# + + + + + + +
# + + ++ + ++ + +
# + + +O+ + +
# + + ++ + ++ + +
# + + ++ + ++ + +
# + ++O + O++ +
# + ++ + + + ++ +
# +++ ++ + ++ +++
# O+ + + + +O
# + ++ + + + ++ +
# + ++ + + + ++ +
# + ++ + + + ++ +
# + ++ + + + ++ +
# + +++++ +
# O+++++++++++++++++++O+++++++++++++++++++O
# + ++ + + + ++ +
# + ++ + + + ++ +
# + ++ + + + ++ +
# + ++ + + + ++ +
# ++ + + + ++
# O++ ++ + ++ ++O
# + ++ + + + ++ +
# + ++O + O++ +
# + + + + ++ + +
# + + ++ + ++ + +
# + + +O+ + +
# + + ++ + ++ + +
# + + + + + + +
# + + ++ + ++ + +
# + + ++ + ++ + +
# ++ ++ + ++ ++
# O+++++++++++++++++++++++++++++++++++++++O
def init_faces(self):
return {'left': {'top': {'v_boundary': None,
'u_boundary': None,
'middle': None,
'center': None,
'interior1': None, # Touches corner
'interior2': None},
'bottom': {'v_boundary': None,
'u_boundary': None,
'middle': None,
'center': None,
'interior1': None,
'interior2': None}},
'right': {'top': {'v_boundary': None,
'u_boundary': None,
'middle': None,
'center': None,
'interior1': None,
'interior2': None},
'bottom': {'v_boundary': None,
'u_boundary': None,
'middle': None,
'center': None,
'interior1': None,
'interior2': None}}}
def calculate_faces(self):
self.add_face(['left', 'top', 'v_boundary'],
[['left', 'top', 'corner'],
['center', 'top', 'v_boundary'],
['center', 'top', 'interior']])
self.add_face(['left', 'top', 'interior1'],
[['left', 'top', 'corner'],
['center', 'top', 'interior'],
['left', 'top', 'interior']])
self.add_face(['left', 'top', 'u_boundary'],
[['left', 'top', 'corner'],
['left', 'top', 'interior'],
['left', 'top', 'u_boundary']])
self.add_face(['left', 'top', 'middle'],
[['center', 'middle'],
['left', 'middle'],
['left', 'top', 'u_boundary']])
self.add_face(['left', 'top', 'interior2'],
[['left', 'top', 'interior'],
['center', 'middle'],
['left', 'top', 'u_boundary']])
self.add_face(['left', 'top', 'center'],
[['center', 'middle'],
['left', 'top', 'interior'],
['center', 'top', 'interior']])
def color_pattern1(self):
self.color_paths([
['left', 'top', 'v_boundary'],
['left', 'top', 'u_boundary'],
['left', 'top', 'middle'],
['left', 'top', 'center'],
['right', 'top', 'interior1'],
['right', 'top', 'interior2'],
['right', 'bottom', 'v_boundary'],
['right', 'bottom', 'u_boundary'],
['right', 'bottom', 'middle'],
['right', 'bottom', 'center'],
['left', 'bottom', 'interior1'],
['left', 'bottom', 'interior2'],
], 1, 0)
class DissectedHexTriTessagon(Tessagon):
tile_class = DissectedHexTriTile
metadata = metadata

View File

@ -0,0 +1,112 @@
from tessagon.core.tessagon import Tessagon
from tessagon.core.tile import Tile
from tessagon.core.tessagon_metadata import TessagonMetadata
metadata = TessagonMetadata(name='Dissected Square',
num_color_patterns=2,
classification='laves',
shapes=['triangles'],
sides=[3],
uv_ratio=1.0)
class DissectedSquareTile(Tile):
# VERTS: a = ['top', 'left']
# a---b---c b = ['top', 'center']
# ^ |\..|../| c = ['top', 'right']
# | |.\.|./.| d = ['middle', 'left']
# | d---e---f e = ['middle', 'center']
# | |../|\..| f = ['middle', 'right']
# |./.|.\.| g = ['bottom', 'left']
# V g---h---i h = ['bottom', 'center']
# i = ['bottom', 'right']
# U --->
# FACES:
# o---o---o A = ['top', 'left', 'middle']
# ^ |\.B|C./| B = ['top', 'left', 'center']
# | |A\.|./D| C = ['top', 'right', 'center']
# | o---o---o D = ['top', 'right', 'middle']
# | |E./|\.H| E = ['bottom', 'left', 'middle']
# |./F|G\.| F = ['bottom', 'left', 'center']
# V o-------o G = ['bottom', 'right', 'center']
# H = ['bottom', 'right', 'middle']
# U --->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'top': {'left': None,
'center': None,
'right': None},
'middle': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None}}
def init_faces(self):
return {'top': {'left': {'middle': None, 'center': None},
'right': {'middle': None, 'center': None}},
'bottom': {'left': {'middle': None, 'center': None},
'right': {'middle': None, 'center': None}}}
def calculate_verts(self):
# Symmetry allow you to get nine verts for the price of four.
self.add_vert(['top', 'left'], 0, 1.0, corner=True)
self.add_vert(['middle', 'left'], 0, 0.5, u_boundary=True)
self.add_vert(['top', 'center'], 0.5, 1.0, v_boundary=True)
self.add_vert(['middle', 'center'], 0.5, 0.5)
def calculate_faces(self):
# Symmetry allows you to create eight faces for the price of two
self.add_face(['top', 'left', 'middle'],
[['top', 'left'],
['middle', 'left'],
['middle', 'center']])
self.add_face(['top', 'left', 'center'],
[['top', 'left'],
['middle', 'center'],
['top', 'center']])
def color_pattern1(self):
self.color_paths([['top', 'left', 'center'],
['top', 'right', 'middle'],
['bottom', 'right', 'center'],
['bottom', 'left', 'middle']], 1, 0)
def color_pattern2(self):
if (self.fingerprint[0] // 2 + self.fingerprint[1] // 2) % 2 == 0:
self.color_tiles(1, 0)
else:
self.color_tiles(0, 1)
def color_tiles(self, color1, color2):
if self.fingerprint[0] % 2 == 0:
if self.fingerprint[1] % 2 == 0:
self.color_paths([['top', 'left', 'center'],
['bottom', 'right', 'middle']],
color2, color1)
else:
self.color_paths([['bottom', 'left', 'center'],
['top', 'right', 'middle']],
color2, color1)
else:
if self.fingerprint[1] % 2 == 0:
self.color_paths([['top', 'right', 'center'],
['bottom', 'left', 'middle']],
color2, color1)
else:
self.color_paths([['bottom', 'right', 'center'],
['top', 'left', 'middle']],
color2, color1)
class DissectedSquareTessagon(Tessagon):
tile_class = DissectedSquareTile
metadata = metadata

View File

@ -0,0 +1,130 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import top_tile
metadata = TessagonMetadata(name='Dissected Triangle',
num_color_patterns=1,
classification='laves',
shapes=['triangles'],
sides=[3],
uv_ratio=sqrt(3.0))
class DissectedTriangleTile(Tile):
# 11 verts:
#
# O++++++++++++O + O++++++++++++O
# ++ ++ ++ + ++ ++ ++
# + + +++ + + + +++ + +
# + + ++ + + + ++ + +
# + + ++ + + + ++ + +
# + O++++++++++++++O++++++++++++++O +
# + + ++ + + + ++ + +
# + + ++ + + + ++ + +
# + + +++ + + + +++ + +
# ++ ++ ++ + ++ ++ ++
# O++++++++++++O + O++++++++++++O
#
# 14 faces (10 internal, 4 on boundary)
#
# O++++++++++++O + O++++++++++++O
# ++ ++ ++ + ++ ++ ++
# + + +++ 3 + 4 + 5 + 6 +++ + +
# + + 2 ++ + + + ++ 7 + +
# + + ++ + + + ++ + +
# + 1 O++++++++++++++O++++++++++++++O 8 +
# + + ++ + + + ++ + +
# + + 9 ++ + + + ++ 14 + +
# + + +++ 10 + + + 13 +++ + +
# ++ ++ ++ + ++ ++ ++
# O++++++++++++O 11 + 12 O++++++++++++O
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'corner': None,
'v_boundary': None},
'middle': None,
'bottom': {'corner': None,
'v_boundary': None}},
'right': {'top': {'corner': None,
'v_boundary': None},
'middle': None,
'bottom': {'corner': None,
'v_boundary': None}},
'center': None}
def init_faces(self):
return {'left': {'top': {'center': None,
'interior1': None, # closest to center
'interior2': None},
'middle': None,
'bottom': {'center': None,
'interior1': None, # closest to center
'interior2': None}},
'right': {'top': {'center': None,
'interior1': None, # closest to center
'interior2': None},
'middle': None,
'bottom': {'center': None,
'interior1': None, # closest to center
'interior2': None}}}
def calculate_verts(self):
# Four corners, via symmetry
self.add_vert(['left', 'top', 'corner'], 0, 1, corner=True)
# The center vert
self.add_vert('center', 0.5, 0.5)
# Vert on boundary
self.add_vert(['left', 'top', 'v_boundary'], 1.0/3.0, 1,
v_boundary=True)
# Interior vert
self.add_vert(['left', 'middle'], 1.0/6.0, 0.5)
def calculate_faces(self):
self.add_face(['left', 'middle'],
[['left', 'middle'],
['left', 'top', 'corner'],
['left', 'bottom', 'corner']])
self.add_face(['left', 'top', 'center'],
[['center'],
['left', 'top', 'v_boundary'],
top_tile(['center'])], v_boundary=True)
self.add_face(['left', 'top', 'interior1'],
[['center'],
['left', 'top', 'v_boundary'],
['left', 'top', 'corner']])
self.add_face(['left', 'top', 'interior2'],
[['center'],
['left', 'middle'],
['left', 'top', 'corner']])
def color_pattern1(self):
if self.fingerprint[1] % 3 == 0:
self.color_paths([['left', 'middle'],
['right', 'middle']], 1, 0)
elif self.fingerprint[1] % 3 == 2:
self.color_paths([['left', 'top', 'interior2'],
['right', 'top', 'interior2'],
['left', 'top', 'interior1'],
['right', 'top', 'interior1']], 1, 0)
self.color_paths([['left', 'bottom', 'center'],
['right', 'bottom', 'center']], 1, 0)
else:
self.color_paths([['left', 'bottom', 'interior2'],
['right', 'bottom', 'interior2'],
['left', 'bottom', 'interior1'],
['right', 'bottom', 'interior1']], 1, 0)
class DissectedTriangleTessagon(Tessagon):
tile_class = DissectedTriangleTile
metadata = metadata

View File

@ -0,0 +1,208 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import top_tile, left_tile, left_top_tile
# Will my brain survive this one?
metadata = TessagonMetadata(name='Dodecagons, Hexagons, and Squares',
num_color_patterns=1,
classification='archimedean',
shapes=['dodecagons', 'hexagons', 'squares'],
sides=[12, 6, 4],
uv_ratio=1.0/sqrt(3.0))
class DodecaTile(Tile):
# 24 verts, 19 faces (7 internal, 12 on boundary)
# The angles make it hard to draw all edges, some excluded
# .......|.4.|.......
# . o---o .
# . 12 / \ 12 .
# . o 6 o .
# --o ..\ /.. o--
# . \.4.o---o.4./ .
# ^ . 6 o o 6 .
# | . / \ .
# | --o o--
# | .4| 12 |4. Number is verts in face
# | --o o--
# . \ / .
# V . 6 o o 6 .
# . /...o---o...\ .
# --o.4./ \.4.o--
# . o 6 o .
# . \ / .
# . 12 o---o 12 .
# .......|.4.|.......
#
# U ---->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
# u_square means on the square that is on the U-boundary
return {'top': {'left': {'u_square': None,
'v_square': None,
# Inner square, sort v-distance from middle
'sq1': None,
'sq2': None,
'sq3': None,
'sq4': None},
'right': {'u_square': None,
'v_square': None,
'sq1': None,
'sq2': None,
'sq3': None,
'sq4': None}},
'bottom': {'left': {'u_square': None,
'v_square': None,
'sq1': None,
'sq2': None,
'sq3': None,
'sq4': None},
'right': {'u_square': None,
'v_square': None,
'sq1': None,
'sq2': None,
'sq3': None,
'sq4': None}}}
def init_faces(self):
return {'dodec': {'top': {'left': None,
'right': None},
'bottom': {'left': None,
'right': None},
'middle': None},
'hex': {'top': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None}},
'square': {'top': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None},
'middle': {'left': None,
'right': None}}}
def calculate_verts(self):
# u_unit is the length of the edges expressed as a
# proportion of the tile
u_unit = 1.0 / (3.0 + sqrt(3))
u_h = 0.5*sqrt(3)*u_unit # height of triangle of side u_unit
u1 = 0.5*u_unit
u2 = 0.5 - u1 - u_h
u3 = 0.5 - u_unit
u4 = 0.5 - u1
v_unit = 1.0 / (3.0*(1.0 + sqrt(3)))
v_h = 0.5*sqrt(3)*v_unit # height of triangle of side v_unit
v1 = 1.0 - 0.5*v_unit
v2 = v1 - v_h
v3 = 0.5 + 2*v_h + 0.5*v_unit
v4 = 0.5 + v_h + v_unit
v5 = 0.5 + v_h + 0.5*v_unit
v6 = 0.5 + 0.5*v_unit
# Define top left region, other verts defined through symmetry
self.add_vert(['top', 'left', 'v_square'], u4, v1)
self.add_vert(['top', 'left', 'u_square'], u1, v6)
self.add_vert(['top', 'left', 'sq1'], u2, v5)
self.add_vert(['top', 'left', 'sq2'], u4, v4)
self.add_vert(['top', 'left', 'sq3'], u1, v3)
self.add_vert(['top', 'left', 'sq4'], u3, v2)
def calculate_faces(self):
# Top left Dodecagon
self.add_face(['dodec', 'top', 'left'],
[['top', 'left', 'v_square'],
['top', 'left', 'sq4'],
['top', 'left', 'sq3'],
left_tile(['top', 'right', 'sq3']),
left_tile(['top', 'right', 'sq4']),
left_tile(['top', 'right', 'v_square']),
left_top_tile(['bottom', 'right', 'v_square']),
left_top_tile(['bottom', 'right', 'sq4']),
left_top_tile(['bottom', 'right', 'sq3']),
top_tile(['bottom', 'left', 'sq3']),
top_tile(['bottom', 'left', 'sq4']),
top_tile(['bottom', 'left', 'v_square'])],
face_type='dodecagon', corner=True)
# Middle Dodecagon
self.add_face(['dodec', 'middle'],
[['top', 'left', 'u_square'],
['top', 'left', 'sq1'],
['top', 'left', 'sq2'],
['top', 'right', 'sq2'],
['top', 'right', 'sq1'],
['top', 'right', 'u_square'],
['bottom', 'right', 'u_square'],
['bottom', 'right', 'sq1'],
['bottom', 'right', 'sq2'],
['bottom', 'left', 'sq2'],
['bottom', 'left', 'sq1'],
['bottom', 'left', 'u_square']],
face_type='dodecagon')
# Upper square
self.add_face(['square', 'top', 'center'],
[['top', 'left', 'v_square'],
['top', 'right', 'v_square'],
top_tile(['bottom', 'right', 'v_square']),
top_tile(['bottom', 'left', 'v_square'])],
face_type='square', v_boundary=True)
# Left square
self.add_face(['square', 'middle', 'left'],
[['top', 'left', 'u_square'],
['bottom', 'left', 'u_square'],
left_tile(['bottom', 'right', 'u_square']),
left_tile(['top', 'right', 'u_square'])],
face_type='square', u_boundary=True)
# Interior square
self.add_face(['square', 'top', 'left'],
[['top', 'left', 'sq1'],
['top', 'left', 'sq2'],
['top', 'left', 'sq4'],
['top', 'left', 'sq3']],
face_type='square')
# Top Hex
self.add_face(['hex', 'top', 'center'],
[['top', 'left', 'sq2'],
['top', 'left', 'sq4'],
['top', 'left', 'v_square'],
['top', 'right', 'v_square'],
['top', 'right', 'sq4'],
['top', 'right', 'sq2']],
face_type='hexagon')
# Left Hex
self.add_face(['hex', 'top', 'left'],
[['top', 'left', 'sq3'],
['top', 'left', 'sq1'],
['top', 'left', 'u_square'],
left_tile(['top', 'right', 'u_square']),
left_tile(['top', 'right', 'sq1']),
left_tile(['top', 'right', 'sq3'])],
face_type='hexagon', u_boundary=True)
def color_pattern1(self):
self.color_face(['dodec', 'middle'], 1)
self.color_face(['dodec', 'top', 'left'], 1)
self.color_face(['hex', 'top', 'left'], 2)
self.color_face(['hex', 'top', 'center'], 2)
self.color_face(['hex', 'bottom', 'left'], 2)
self.color_face(['hex', 'bottom', 'center'], 2)
class DodecaTessagon(Tessagon):
tile_class = DodecaTile
metadata = metadata

View File

@ -0,0 +1,145 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import bottom_tile, left_tile, bottom_left_tile
metadata = TessagonMetadata(name='Dodecagons and Triangles',
num_color_patterns=1,
classification='archimedean',
shapes=['dodecagons', 'triangles'],
sides=[12, 3],
uv_ratio=sqrt(3.0))
class DodecaTriTile(Tile):
# 14 verts, 11 faces (3 internal, 8 on boundary)
# The angles make it hard to draw all edges, some excluded
# . ....|3.o---o.3|......
# ^ . o o .
# | . 12 / \ 12 .
# | . o o .
# | .-o3| 12 |3o-. Number is verts in face
# | . o o .
# . \ / .
# V . 12 o o 12 .
# . ....|3.o---o.3|......
#
# U ---->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
# u_square means on the square that is on the U-boundary
return {'left': {'top': {'v_boundary': None,
'diag': None,
'tri': None}, # on the triangle
'middle': None,
'bottom': {'v_boundary': None,
'diag': None,
'tri': None}},
'right': {'top': {'v_boundary': None,
'diag': None,
'tri': None}, # on the triangle
'middle': None,
'bottom': {'v_boundary': None,
'diag': None,
'tri': None}}}
def init_faces(self):
return {'dodec': {'left': {'top': None,
'bottom': None},
'right': {'top': None,
'bottom': None},
'center': None},
'tri': {'left': {'top': None,
'middle': None,
'bottom': None},
'right': {'top': None,
'middle': None,
'bottom': None}}}
def calculate_verts(self):
# u_unit is the length of the edges expressed as a
# proportion of the tile
u_unit = 1.0 / (3.0 + 2.0 * sqrt(3))
u_h = 0.5*sqrt(3)*u_unit # height of triangle of side u_unit
u1 = 0.5*u_unit
u2 = u1 + u_h
u3 = u2 + u1
u4 = u3 + u_h
v_unit = 1.0 / (2.0 + sqrt(3))
v_h = 0.5*sqrt(3)*v_unit # height of triangle of side v_unit
v1 = 0
v2 = 0.5 * v_unit
v3 = v2 + v_h
v4 = 0.5
# Sweet symmetry makes this easy work
self.add_vert(['left', 'middle'], u1, v4) # 2 verts added
self.add_vert(['left', 'bottom', 'v_boundary'], u4, v1,
v_boundary=True) # 4 verts
self.add_vert(['left', 'bottom', 'diag'], u3, v2) # 4 verts
self.add_vert(['left', 'bottom', 'tri'], u2, v3) # 4 verts
def calculate_faces(self):
# Top left Dodecagon
self.add_face(['dodec', 'left', 'bottom'],
[['left', 'middle'],
['left', 'bottom', 'tri'],
['left', 'bottom', 'diag'],
bottom_tile(['left', 'top', 'diag']),
bottom_tile(['left', 'top', 'tri']),
bottom_tile(['left', 'middle']),
bottom_left_tile(['right', 'middle']),
bottom_left_tile(['right', 'top', 'tri']),
bottom_left_tile(['right', 'top', 'diag']),
left_tile(['right', 'bottom', 'diag']),
left_tile(['right', 'bottom', 'tri']),
left_tile(['right', 'middle'])],
face_type='dodecagon', corner=True)
# Middle Dodecagon
self.add_face(['dodec', 'center'],
[['left', 'bottom', 'tri'],
['left', 'bottom', 'diag'],
['left', 'bottom', 'v_boundary'],
['right', 'bottom', 'v_boundary'],
['right', 'bottom', 'diag'],
['right', 'bottom', 'tri'],
['right', 'top', 'tri'],
['right', 'top', 'diag'],
['right', 'top', 'v_boundary'],
['left', 'top', 'v_boundary'],
['left', 'top', 'diag'],
['left', 'top', 'tri']],
face_type='dodecagon')
# Left triangle
self.add_face(['tri', 'left', 'middle'],
[['left', 'top', 'tri'],
['left', 'bottom', 'tri'],
['left', 'middle']],
face_type='triangle')
# bottom-left triangle
self.add_face(['tri', 'left', 'bottom'],
[['left', 'bottom', 'diag'],
['left', 'bottom', 'v_boundary'],
bottom_tile(['left', 'top', 'diag'])],
face_type='triangle', v_boundary=True)
def color_pattern1(self):
self.color_paths([['dodec', 'left', 'bottom'],
['dodec', 'center']], 1, 0)
class DodecaTriTessagon(Tessagon):
tile_class = DodecaTriTile
metadata = metadata

View File

@ -0,0 +1,192 @@
from math import atan2, sqrt, sin, cos, pi
from tessagon.core.tessagon import Tessagon
from tessagon.core.alternating_tile import AlternatingTile
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import right_tile, left_tile, \
top_tile, top_left_tile, top_right_tile, \
bottom_tile, bottom_right_tile
metadata = TessagonMetadata(name='Florets',
num_color_patterns=3,
classification='laves',
shapes=['pentagons'],
sides=[5],
uv_ratio=1.0/sqrt(3))
class FloretTile(AlternatingTile):
# See the SVG for decomposition:
# https://raw.githubusercontent.com/cwant/tessagon/master/documentation/code/floret.svg
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = False
self.v_symmetric = False
# multiplier to get v units ...
self.uv_ratio = self.tessagon.metadata.uv_ratio
# Tilt
theta_offset1 = pi/3 - atan2(sqrt(3), 9)
theta_offset2 = pi/6
# No hexagons in this pattern, but verts lie of hexagons
# radius is in u inits
self.hexagons = [
{'radius': 4 / sqrt(21),
'hex_theta': [(theta_offset1 + number * pi / 3.0)
for number in range(6)]},
# Just guessing ...
{'radius': 2 / (3 * self.uv_ratio),
'hex_theta': [(theta_offset2 + number * pi / 3.0)
for number in range(6)]},
]
def hex_vert_coord(self, hexagon_num, center, number):
# hexagon_num in range(2)
# number in range(6)
hexagon = self.hexagons[hexagon_num]
return [center[0] +
hexagon['radius'] * cos(hexagon['hex_theta'][number]),
center[1] +
hexagon['radius'] * sin(hexagon['hex_theta'][number]) *
self.uv_ratio]
def init_verts(self):
if self.tile_type == 0:
verts = {i: None for i in range(6)}
else:
verts = {i: None for i in range(6, 14)}
return verts
def init_faces(self):
if self.tile_type == 0:
faces = {c: None for c in ['A', 'B', 'C', 'D']}
else:
faces = {c: None for c in ['E', 'F', 'G', 'H', 'I', 'J', 'K', 'L']}
return faces
def calculate_verts(self):
if self.tile_type == 0:
self.add_vert([2], *self.hex_vert_coord(0, [0, 0], 0))
self.add_vert([3], *self.hex_vert_coord(0, [1, 1], 3))
else:
self.add_vert([6], 1, 0,
equivalent=[right_tile(0),
bottom_right_tile(13),
bottom_tile(5)])
self.add_vert([7], *self.hex_vert_coord(0, [1, 0], 2))
self.add_vert([8], *self.hex_vert_coord(1, [0, 1], 4),
equivalent=[left_tile(1)])
self.add_vert([9], *self.hex_vert_coord(0, [0, 1], 4))
self.add_vert([10], *self.hex_vert_coord(0, [1, 0], 1))
self.add_vert([11], *self.hex_vert_coord(1, [0, 1], 5),
equivalent=[right_tile(4)])
self.add_vert([12], *self.hex_vert_coord(0, [0, 1], 5))
self.add_vert([13], 0, 1,
equivalent=[left_tile(5),
top_left_tile(6),
top_tile(0)])
def calculate_faces(self):
# All of tile type 0 faces overlap tiles of type 1
if self.tile_type == 0:
return
# Tile 'E' is handled as Tile 'K' on another tile
# Tile 'F' is handled as Tile 'L' on another tile
self.add_face('G', [6,
10,
9,
8,
7])
self.add_face('H', [11,
10,
6,
right_tile(2),
right_tile(3)],
equivalent=[right_tile('B')])
self.add_face('I', [8,
9,
13,
left_tile(3),
left_tile(2)],
equivalent=[left_tile('C')])
self.add_face('J', [13,
9,
10,
11,
12])
self.add_face('K', [12,
11,
right_tile(3),
right_tile(5),
top_right_tile(7)],
equivalent=[right_tile('D'),
top_right_tile('E')])
self.add_face('L', [13,
12,
top_right_tile(7),
top_tile(1),
top_tile(2)],
equivalent=[top_right_tile('F'),
top_tile('A')])
def floret_fingerprint(self, face):
fingerprint = list(self.fingerprint.copy())
fingerprint[0] = fingerprint[0] // 2 + fingerprint[1] // 2
if face in ['F']:
fingerprint[0] -= 1
elif face in ['K']:
fingerprint[0] += 1
if self.fingerprint[0] % 2 == 0:
if face in ['A', 'B']:
fingerprint[0] -= 1
else:
if face in ['C', 'D']:
fingerprint[0] += 1
if face in ['A', 'B', 'E', 'F', 'G', 'H']:
fingerprint[1] -= 1
return fingerprint
def color_pattern1(self):
pattern = [0, 0, 1]
for face in self.faces:
fingerprint = self.floret_fingerprint(face)
offset = (fingerprint[0] + fingerprint[1]) % 3
self.color_face(face, pattern[offset])
def color_pattern2(self):
for face in self.faces:
fingerprint = self.floret_fingerprint(face)
color = (fingerprint[0] + fingerprint[1]) % 3
self.color_face(face, color)
def color_pattern3(self):
# Follow a straight line in the pattern to see this ...
pattern = [[2, 0, 2, 2, 0, 2],
[2, 1, 2, 0, 0, 0]]
for face in self.faces:
fingerprint = self.floret_fingerprint(face)
row = fingerprint[1] % 2
column = (fingerprint[0] - 2 * fingerprint[1]) % 6
self.color_face(face, pattern[row][column])
class FloretTessagon(Tessagon):
tile_class = FloretTile
metadata = metadata

View File

@ -0,0 +1,132 @@
from math import sqrt, atan2, sin, cos, pi
from tessagon.core.tessagon import Tessagon
from tessagon.core.alternating_tile import AlternatingTile
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import \
left_tile, top_tile, top_left_tile, \
right_tile, bottom_tile
metadata = TessagonMetadata(name='Hexagons and Big Triangles',
num_color_patterns=2,
classification='non_edge',
shapes=['hexagons', 'triangles'],
sides=[6, 3],
uv_ratio=1.0/sqrt(3.0))
class HexBigTriTile(AlternatingTile):
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = False
self.v_symmetric = False
# Future use to control hexagon size?
self.hexagon_ratio = 0.5
# in u units
self.hex_radius = 4 * self.hexagon_ratio / sqrt(7)
# multiplier to get v units ...
self.uv_ratio = self.tessagon.metadata.uv_ratio
# Tilt
self.theta_offset = -atan2(1, 3 * sqrt(3)) + pi/6
self.hex_theta = [(self.theta_offset + number * pi / 3.0)
for number in range(6)]
def hex_vert_coord(self, center, number):
# number in range(6)
return [center[0] +
self.hex_radius * cos(self.hex_theta[number]),
center[1] +
self.hex_radius * sin(self.hex_theta[number]) * self.uv_ratio]
def init_verts(self):
if self.tile_type == 0:
verts = {0: None,
1: None}
else:
verts = {2: None,
3: None,
4: None,
5: None}
return verts
def init_faces(self):
if self.tile_type == 0:
faces = {'A': None,
'B': None,
'C': None,
'D': None}
else:
faces = {'E': None,
'F': None,
'G': None,
'H': None,
'I': None,
'J': None}
return faces
def calculate_verts(self):
if self.tile_type == 0:
self.add_vert([0], *self.hex_vert_coord([0, 1], 5))
self.add_vert([1], *self.hex_vert_coord([1, 0], 2))
else:
self.add_vert([2], *self.hex_vert_coord([1, 1], 3))
self.add_vert([3], *self.hex_vert_coord([1, 1], 4))
self.add_vert([4], *self.hex_vert_coord([0, 0], 1))
self.add_vert([5], *self.hex_vert_coord([0, 0], 0))
def calculate_faces(self):
if self.tile_type != 0:
return
# Top Hexagon
self.add_face('A',
[0,
left_tile(3),
left_tile(2),
top_left_tile(1),
top_tile(4),
top_tile(5)],
equivalent=[left_tile('F'),
top_left_tile('D'),
top_tile('I')])
# Left Triangle
self.add_face('B',
[1,
0,
left_tile(3),
left_tile(4),
left_tile(5),
bottom_tile(2)],
equivalent=[bottom_tile('E'),
left_tile('H')])
# Right Triangle
self.add_face('C',
[0,
1,
right_tile(4),
right_tile(3),
right_tile(2),
top_tile(5)],
equivalent=[right_tile('G'),
top_tile('J')])
def color_pattern1(self):
if self.tile_type == 0:
self.color_face('A', 1)
def color_pattern2(self):
if self.tile_type == 0:
self.color_face('A', 1)
self.color_face('B', 2)
class HexBigTriTessagon(Tessagon):
tile_class = HexBigTriTile
metadata = metadata

View File

@ -0,0 +1,169 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, top_tile, left_top_tile
metadata = TessagonMetadata(name='Hexagons, Squares, and Triangles',
num_color_patterns=1,
classification='archimedean',
shapes=['hexagons', 'squares', 'triangles'],
sides=[6, 4, 3],
uv_ratio=1.0/sqrt(3.0))
class HexSquareTriTile(Tile):
# 14 verts, 19 faces (7 internal, 12 on boundary)
# The angles make it hard to draw all edges, some excluded
#
# ...|...|... 6..|.4.|..6
# ...o---o... ...o---o...
# ^ o ..\./...o o.4.\3/.4.o
# | .\...o.../. 3\...o.../3 Numbers are faces with # sides
# | --o.....o-- --o.....o--
# | ..|.....|.. 4.|..6..|.4
# | --o.... o-- --o.... o--
# ./.. o...\. 3/.. o...\3
# V o.../.\...o o.4./3\.4.o
# ...o---o... ...o---o...
# ...|...|... 6..|.4.|..6
#
# U ---->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
# u_square means on the square that is on the U-boundary
return {'top': {'left': {'u_boundary': None,
'u_square': None,
'v_square': None},
'right': {'u_boundary': None,
'u_square': None,
'v_square': None},
'center': None},
'bottom': {'left': {'u_boundary': None,
'u_square': None,
'v_square': None},
'right': {'u_boundary': None,
'u_square': None,
'v_square': None},
'center': None}}
def init_faces(self):
# Whelp!
return {'hex': {'top': {'left': None,
'right': None},
'bottom': {'left': None,
'right': None},
'middle': None},
'tri': {'top': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None}},
'square': {'top': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None},
'middle': {'left': None,
'right': None}}}
def calculate_verts(self):
# u_unit is the length of the edges expressed as a
# proportion of the tile
u_unit = 1.0 / (1.0 + sqrt(3))
u0 = 0
u1 = 0.5*u_unit
u2 = 0.5*(1.0-u_unit)
u3 = 0.5
v_unit = 1.0 / (3.0 + sqrt(3))
v0 = 1.0 - 0.5*v_unit
v1 = 1.0 - v_unit
v2 = 0.5 + v_unit
v3 = 0.5 + 0.5*v_unit
# Define top left square, other verts defined through symmetry
self.add_vert(['top', 'left', 'v_square'], u2, v0)
self.add_vert(['top', 'center'], u3, v2)
self.add_vert(['top', 'left', 'u_square'], u1, v3)
self.add_vert(['top', 'left', 'u_boundary'], u0, v1, u_boundary=True)
def calculate_faces(self):
# Middle hexagon
self.add_face(['hex', 'middle'],
[['top', 'center'],
['top', 'left', 'u_square'],
['bottom', 'left', 'u_square'],
['bottom', 'center'],
['bottom', 'right', 'u_square'],
['top', 'right', 'u_square']],
face_type='hexagon')
# Six top-left faces, rest defined via symmetry
# Top square
self.add_face(['square', 'top', 'center'],
[['top', 'left', 'v_square'],
['top', 'right', 'v_square'],
top_tile(['bottom', 'right', 'v_square']),
top_tile(['bottom', 'left', 'v_square'])],
face_type='square', v_boundary=True)
# Left square
self.add_face(['square', 'middle', 'left'],
[['top', 'left', 'u_square'],
['bottom', 'left', 'u_square'],
left_tile(['bottom', 'right', 'u_square']),
left_tile(['top', 'right', 'u_square'])],
face_type='square', u_boundary=True)
# Interior square
self.add_face(['square', 'top', 'left'],
[['top', 'left', 'v_square'],
['top', 'center'],
['top', 'left', 'u_square'],
['top', 'left', 'u_boundary']],
face_type='square')
# Upper triangle
self.add_face(['tri', 'top', 'center'],
[['top', 'center'],
['top', 'left', 'v_square'],
['top', 'right', 'v_square']],
face_type='triangle')
# Left triangle
self.add_face(['tri', 'top', 'left'],
[['top', 'left', 'u_square'],
['top', 'left', 'u_boundary'],
left_tile(['top', 'right', 'u_square'])],
face_type='triangle', u_boundary=True)
# Corner hexagon
self.add_face(['hex', 'top', 'left'],
[['top', 'left', 'v_square'],
['top', 'left', 'u_boundary'],
left_tile(['top', 'right', 'v_square']),
left_top_tile(['bottom', 'right', 'v_square']),
top_tile(['bottom', 'left', 'u_boundary']),
top_tile(['bottom', 'left', 'v_square'])],
face_type='hexagon', corner=True)
def color_pattern1(self):
self.color_face(['hex', 'middle'], 1)
self.color_face(['hex', 'top', 'left'], 1)
# TODO: I'm not sure why I have to explicitely color
# 'right' and 'bottom' faces (I thought symmetry would do this?)
self.color_face(['square', 'top', 'center'], 2)
self.color_face(['square', 'top', 'left'], 2)
self.color_face(['square', 'top', 'right'], 2)
self.color_face(['square', 'middle', 'left'], 2)
self.color_face(['square', 'bottom', 'left'], 2)
self.color_face(['square', 'bottom', 'right'], 2)
class HexSquareTriTessagon(Tessagon):
tile_class = HexSquareTriTile
metadata = metadata

View File

@ -0,0 +1,129 @@
from math import sqrt
from tessagon.core.tessagon import Tessagon
from tessagon.core.tile import Tile
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import top_tile, top_left_tile, left_tile
metadata = TessagonMetadata(name='Regular Hexagons',
num_color_patterns=2,
classification='regular',
shapes=['hexagons'],
sides=[6],
uv_ratio=1.0/sqrt(3.0))
class HexTile(Tile):
# VERTS:
# ..|..
# ..a.. a = ['top', 'center']
# ^ ./.\. b = ['top', 'left']
# | b...c c = ['top', 'right']
# | |...| d = ['bottom', 'left']
# | d...e e = ['bottom', 'right']
# | .\./. f = ['bottom', 'center']
# ..f..
# V ..|..
#
# U --->
# FACES:
# A.|.B
# ..o.. A = ['top', 'left']
# ^ ./.\. B = ['top', 'right']
# | o...o C = ['middle']
# | |.C.| D = ['bottom', 'left']
# | o...o E = ['bottom', 'right']
# | .\./.
# ..o..
# V D.|.E
#
# U --->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'top': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None}}
def init_faces(self):
return {'top': {'left': None,
'right': None},
'middle': None,
'bottom': {'left': None,
'right': None}}
def calculate_verts(self):
# Symmetry allow you to get six verts for the price of two.
# Next line also defines the vert at ['bottom', 'center']
self.add_vert(['top', 'center'], 0.5, 5.0/6.0)
# Next line also defines the verts at: ['bottom', 'left']
# ['bottom', 'right']
# ['top', 'right']
self.add_vert(['top', 'left'], 0, 2.0/3.0, u_boundary=True)
def calculate_faces(self):
# Symmetry allows you to create five faces for the price of two
self.add_face('middle', [['top', 'center'],
['top', 'left'],
['bottom', 'left'],
['bottom', 'center'],
['bottom', 'right'],
['top', 'right']])
# The next line also defines the faces at: ['top', 'right']
# ['bottom', 'right']
# ['bottom', 'left']
self.add_face(['top', 'left'],
# The first two verts of the face are on this tile
[['top', 'left'],
['top', 'center'],
# The other four verts are on neighboring tiles.
# E.g., the next one is the ['bottom', 'center']
# vert on the top neighbor tile.
top_tile(['bottom', 'center']),
top_tile(['bottom', 'left']),
top_left_tile(['bottom', 'center']),
left_tile(['top', 'center'])],
# Defining the face as a 'corner' also associates the
# created face as one that is shared with
# neighboring tiles.
corner=True)
def color_pattern1(self):
if self.fingerprint[0] % 3 == 0:
self.color_paths([['top', 'left'],
['bottom', 'left']], 1, 0)
elif self.fingerprint[0] % 3 == 1:
self.color_paths([['middle']], 1, 0)
else:
self.color_paths([['top', 'right'],
['bottom', 'right']], 1, 0)
def color_pattern2(self):
if self.fingerprint[0] % 3 == 0:
self.color_paths_hash({1: [['top', 'left'],
['bottom', 'left']],
2: [['top', 'right'],
['bottom', 'right']]}, 0)
elif self.fingerprint[0] % 3 == 1:
self.color_paths_hash({1: [['middle']],
2: [['top', 'left'],
['bottom', 'left']]}, 0)
else:
self.color_paths_hash({2: [['middle']],
1: [['top', 'right'],
['bottom', 'right']]}, 0)
class HexTessagon(Tessagon):
tile_class = HexTile
metadata = metadata

View File

@ -0,0 +1,101 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, left_top_tile, top_tile
metadata = TessagonMetadata(name='Hexagons and Triangles',
num_color_patterns=1,
classification='archimedean',
shapes=['hexagons', 'triangles'],
sides=[6, 3],
uv_ratio=1.0/sqrt(3.0))
class HexTriTile(Tile):
# ....o....
# .../.\...
# ^ --o---o--
# | ./.....\.
# | o.......o
# | .\...../.
# | --o---o--
# ...\./...
# V ....o ...
#
# U ------>
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'top': None,
'left': {'top': None,
'middle': None,
'bottom': None},
'right': {'top': None,
'middle': None,
'bottom': None},
'bottom': None}
def init_faces(self):
return {'center': {'top': None,
'middle': None,
'bottom': None},
'left': {'top': {'triangle': None, 'hexagon': None},
'bottom': {'triangle': None, 'hexagon': None}},
'right': {'top': {'triangle': None, 'hexagon': None},
'bottom': {'triangle': None, 'hexagon': None}}}
def calculate_verts(self):
# top left verts
self.add_vert('top', 0.5, 1, v_boundary=True)
self.add_vert(['left', 'top'], 0.25, 0.75)
self.add_vert(['left', 'middle'], 0, 0.5, u_boundary=True)
def calculate_faces(self):
# Middle hexagon
self.add_face(['center', 'middle'],
[['left', 'top'],
['left', 'middle'],
['left', 'bottom'],
['right', 'bottom'],
['right', 'middle'],
['right', 'top']],
face_type='hexagon')
# Interior top triangle
self.add_face(['center', 'top'],
[['top'],
['left', 'top'],
['right', 'top']],
face_type='triangle')
# Exterior left triangle
self.add_face(['left', 'top', 'triangle'],
[['left', 'top'],
['left', 'middle'],
# Verts on neighbor tiles
left_tile(['right', 'top'])],
face_type='triangle', u_boundary=True)
# Exterior top-left hexagon
self.add_face(['left', 'top', 'hexagon'],
[['top'],
['left', 'top'],
# Verts on neighbor tiles
left_tile(['right', 'top']),
left_tile('top'),
left_top_tile(['right', 'bottom']),
top_tile(['left', 'bottom'])],
face_type='hexagon', corner=True)
def color_pattern1(self):
# Color the hexagons
self.color_face(['center', 'middle'], 1)
self.color_face(['left', 'top', 'hexagon'], 1)
class HexTriTessagon(Tessagon):
tile_class = HexTriTile
metadata = metadata

View File

@ -0,0 +1,127 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, top_left_tile, top_tile
metadata = TessagonMetadata(name='Islamic Hexagons and Stars',
num_color_patterns=1,
classification='non_convex',
shapes=['hexagons', 'stars'],
sides=[6, 12],
uv_ratio=sqrt(3.0))
class IslamicHexStarsTile(Tile):
# See page 3 of "Islamic Design" by Daud Sutton
# See the SVG for decomposition:
# https://raw.githubusercontent.com/cwant/tessagon/master/documentation/code/islamic_hex_stars.svg
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'boundary': None,
'mid': {'outer': None,
'mid': None,
'inner': None}},
'middle': {'inner': None,
'outer': None},
'bottom': {'boundary': None,
'mid': {'outer': None,
'mid': None,
'inner': None}}},
'center': {'top': None,
'bottom': None},
'right': {'top': {'boundary': None,
'mid': {'outer': None,
'mid': None,
'inner': None}},
'middle': {'inner': None,
'outer': None},
'bottom': {'boundary': None,
'mid': {'outer': None,
'mid': None,
'inner': None}}},
}
def init_faces(self):
return {'left': {'top': {'star': None, 'hexagon': None},
'middle': {'hexagon': None},
'bottom': {'star': None, 'hexagon': None}},
'center': {'star': None},
'right': {'top': {'star': None, 'hexagon': None},
'middle': {'hexagon': None},
'bottom': {'star': None, 'hexagon': None}}}
def calculate_verts(self):
# left verts
self.add_vert(['left', 'top', 'boundary'], 2/12.0, 1, v_boundary=True)
self.add_vert(['left', 'top', 'mid', 'outer'], 1/12.0, 0.75)
self.add_vert(['left', 'top', 'mid', 'mid'], 3/12.0, 0.75)
self.add_vert(['left', 'top', 'mid', 'inner'], 5/12.0, 0.75)
self.add_vert(['left', 'middle', 'outer'], 0, 0.5, u_boundary=True)
self.add_vert(['left', 'middle', 'inner'], 4/12, 0.5)
# center vert
self.add_vert(['center', 'top'], 0.5, 1, v_boundary=True)
def calculate_faces(self):
self.add_face(['left', 'top', 'star'],
[['left', 'top', 'boundary'],
['left', 'top', 'mid', 'mid'],
['left', 'top', 'mid', 'outer'],
['left', 'middle', 'outer'],
left_tile(['right', 'top', 'mid', 'outer']),
left_tile(['right', 'top', 'mid', 'mid']),
left_tile(['right', 'top', 'boundary']),
top_left_tile(['right', 'bottom', 'mid', 'mid']),
top_left_tile(['right', 'bottom', 'mid', 'outer']),
top_tile(['left', 'middle', 'outer']),
top_tile(['left', 'bottom', 'mid', 'outer']),
top_tile(['left', 'bottom', 'mid', 'mid'])],
face_type='star', corner=True)
self.add_face(['left', 'top', 'hexagon'],
[['center', 'top'],
['left', 'top', 'mid', 'inner'],
['left', 'top', 'mid', 'mid'],
['left', 'top', 'boundary'],
top_tile(['left', 'bottom', 'mid', 'mid']),
top_tile(['left', 'bottom', 'mid', 'inner'])],
face_type='hexagon', v_boundary=True)
self.add_face(['left', 'middle', 'hexagon'],
[['left', 'middle', 'outer'],
['left', 'top', 'mid', 'outer'],
['left', 'top', 'mid', 'mid'],
['left', 'middle', 'inner'],
['left', 'bottom', 'mid', 'mid'],
['left', 'bottom', 'mid', 'outer']],
face_type='hexagon')
self.add_face(['center', 'star'],
[['center', 'top'],
['right', 'top', 'mid', 'inner'],
['right', 'top', 'mid', 'mid'],
['right', 'middle', 'inner'],
['right', 'bottom', 'mid', 'mid'],
['right', 'bottom', 'mid', 'inner'],
['center', 'bottom'],
['left', 'bottom', 'mid', 'inner'],
['left', 'bottom', 'mid', 'mid'],
['left', 'middle', 'inner'],
['left', 'top', 'mid', 'mid'],
['left', 'top', 'mid', 'inner']],
face_type='star')
def color_pattern1(self):
self.color_face(['left', 'top', 'star'], 1)
self.color_face(['center', 'star'], 1)
class IslamicHexStarsTessagon(Tessagon):
tile_class = IslamicHexStarsTile
metadata = metadata

View File

@ -0,0 +1,121 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, top_left_tile, top_tile
metadata = TessagonMetadata(name='Islamic Stars and Crosses',
num_color_patterns=1,
classification='non_convex',
shapes=['stars', 'crosses'],
sides=[16],
uv_ratio=1.0)
class IslamicStarsCrossesTile(Tile):
# See page 9 of "Islamic Design" by Daud Sutton
#
# ......o......
# ...../.\.....
# ..o-o...o-o..
# ..|.......|..
# ..o.......o..
# ./.........\.
# o...........o
# .\........./.
# ..o.......o..
# ..|.......|..
# ..o-o...o-o..
# .....\./.....
# ......o......
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'v_dominant': None,
'point': None,
'u_dominant': None},
'middle': None,
'bottom': {'v_dominant': None,
'point': None,
'u_dominant': None}},
'center': {'top': None,
'bottom': None},
'right': {'top': {'v_dominant': None,
'point': None,
'u_dominant': None},
'middle': None,
'bottom': {'v_dominant': None,
'point': None,
'u_dominant': None}}}
def init_faces(self):
return {'left': {'top': None,
'bottom': None},
'center': None,
'right': {'top': None,
'bottom': None}}
def calculate_verts(self):
c = 1.0 / (2 * (sqrt(2) + 1))
a = c / sqrt(2)
# left top corner
self.add_vert(['left', 'middle'], 0.0, 0.5, u_boundary=True)
self.add_vert(['left', 'top', 'u_dominant'], a, 0.5 + a)
self.add_vert(['left', 'top', 'point'], a, 1.0 - a)
self.add_vert(['left', 'top', 'v_dominant'], 0.5 - a, 1.0 - a)
self.add_vert(['center', 'top'], 0.5, 1.0, v_boundary=True)
def calculate_faces(self):
# Middle star
self.add_face(['center'],
[['left', 'middle'],
['left', 'top', 'u_dominant'],
['left', 'top', 'point'],
['left', 'top', 'v_dominant'],
['center', 'top'],
['right', 'top', 'v_dominant'],
['right', 'top', 'point'],
['right', 'top', 'u_dominant'],
['right', 'middle'],
['right', 'bottom', 'u_dominant'],
['right', 'bottom', 'point'],
['right', 'bottom', 'v_dominant'],
['center', 'bottom'],
['left', 'bottom', 'v_dominant'],
['left', 'bottom', 'point'],
['left', 'bottom', 'u_dominant']],
face_type='star')
# Top left cross
self.add_face(['left', 'top'],
[['center', 'top'],
['left', 'top', 'v_dominant'],
['left', 'top', 'point'],
['left', 'top', 'u_dominant'],
['left', 'middle'],
left_tile(['right', 'top', 'u_dominant']),
left_tile(['right', 'top', 'point']),
left_tile(['right', 'top', 'v_dominant']),
left_tile(['center', 'top']),
top_left_tile(['right', 'bottom', 'v_dominant']),
top_left_tile(['right', 'bottom', 'point']),
top_left_tile(['right', 'bottom', 'u_dominant']),
top_left_tile(['right', 'middle']),
top_tile(['left', 'bottom', 'u_dominant']),
top_tile(['left', 'bottom', 'point']),
top_tile(['left', 'bottom', 'v_dominant'])],
face_type='cross', corner=True)
def color_pattern1(self):
self.color_face(['left', 'top'], 1)
self.color_face(['center'], 0)
class IslamicStarsCrossesTessagon(Tessagon):
tile_class = IslamicStarsCrossesTile
metadata = metadata

View File

@ -0,0 +1,84 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, top_tile
# TODO: gulp, 'octagon' does not begin with 'octo'
metadata = TessagonMetadata(name='Octagons and Squares',
num_color_patterns=1,
classification='archimedean',
shapes=['octagons', 'squares'],
sides=[8, 4],
uv_ratio=1.0)
class OctoTile(Tile):
# ^ ..o-o..
# | ./...\.
# | o.....o
# | |.....|
# | o.....o
# | .\.../.
# ..o-o..
# V
# U ---->
CORNER_TO_VERT_RATIO = 1.0 / (2.0 + sqrt(2))
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'u_boundary': None,
'v_boundary': None},
'bottom': {'u_boundary': None,
'v_boundary': None}},
'right': {'top': {'u_boundary': None,
'v_boundary': None},
'bottom': {'u_boundary': None,
'v_boundary': None}}}
def init_faces(self):
return {'middle': None,
'left': {'top': None,
'bottom': None},
'right': {'top': None,
'bottom': None}}
def calculate_verts(self):
self.add_vert(['left', 'top', 'v_boundary'],
self.CORNER_TO_VERT_RATIO, 1, v_boundary=True)
self.add_vert(['left', 'top', 'u_boundary'],
0, 1.0 - self.CORNER_TO_VERT_RATIO, u_boundary=True)
def calculate_faces(self):
# Middle interior face
self.add_face('middle', [['left', 'top', 'v_boundary'],
['left', 'top', 'u_boundary'],
['left', 'bottom', 'u_boundary'],
['left', 'bottom', 'v_boundary'],
['right', 'bottom', 'v_boundary'],
['right', 'bottom', 'u_boundary'],
['right', 'top', 'u_boundary'],
['right', 'top', 'v_boundary']])
# Four faces, define top left corner, others via symmetry
self.add_face(['left', 'top'],
[['left', 'top', 'v_boundary'],
['left', 'top', 'u_boundary'],
# Verts on neighbor tiles
left_tile(['right', 'top', 'v_boundary']),
top_tile(['left', 'bottom', 'u_boundary'])],
corner=True)
def color_pattern1(self):
self.color_face(['middle'], 1)
class OctoTessagon(Tessagon):
tile_class = OctoTile
metadata = metadata

View File

@ -0,0 +1,97 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile
metadata = TessagonMetadata(name='Other Pentagons',
num_color_patterns=1,
classification='laves',
shapes=['pentagons'],
sides=[5],
uv_ratio=1.0/(2.0+sqrt(3.0)))
class Penta2Tile(Tile):
# 11 verts, 6 faces (2 internal, 4 on boundary)
#
# O---O
# |...|
# O...O
# .\./.
# ^ ..O..
# | ..|..
# | --O--
# | ..|..
# ..O..
# V ./.\.
# O...O
# |...|
# O---O
#
# U ----->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'corner': None,
'u_boundary': None},
'bottom': {'corner': None,
'u_boundary': None}},
'right': {'top': {'corner': None,
'u_boundary': None},
'bottom': {'corner': None,
'u_boundary': None}},
'center': {'top': None,
'middle': None,
'bottom': None}}
def init_faces(self):
return {'left': {'top': None,
'bottom': None},
'right': {'top': None,
'bottom': None},
'center': {'top': None,
'bottom': None}}
def calculate_verts(self):
v_unit = 1.0 / (2.0 + sqrt(3.0))
v0 = 0
v1 = v_unit * 0.5 * (1.0 + 1.0 / sqrt(3.0))
v2 = 0.5 - v1
self.add_vert(['left', 'bottom', 'corner'], 0, v0, corner=True)
self.add_vert(['left', 'bottom', 'u_boundary'], 0, v1,
u_boundary=True)
self.add_vert(['center', 'bottom'], 0.5, v2)
self.add_vert(['center', 'middle'], 0.5, 0.5)
def calculate_faces(self):
self.add_face(['center', 'bottom'],
[['left', 'bottom', 'corner'],
['left', 'bottom', 'u_boundary'],
['center', 'bottom'],
['right', 'bottom', 'u_boundary'],
['right', 'bottom', 'corner']])
self.add_face(['left', 'bottom'],
[['center', 'middle'],
['center', 'bottom'],
['left', 'bottom', 'u_boundary'],
left_tile(['center', 'bottom']),
left_tile(['center', 'middle'])],
u_boundary=True)
def color_pattern1(self):
self.color_paths([
['center', 'top'],
['left', 'bottom'],
['right', 'bottom']
], 1, 0)
class Penta2Tessagon(Tessagon):
tile_class = Penta2Tile
metadata = metadata

View File

@ -0,0 +1,137 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, bottom_tile
metadata = TessagonMetadata(name='Pentagons',
num_color_patterns=1,
classification='laves',
shapes=['pentagons'],
sides=[5],
uv_ratio=1.0)
class PentaTile(Tile):
# 16 verts, 12 faces (4 internal, 8 on boundaries)
#
# + O+++++++++O +
# + + + +
# O + + O
# ++ + + ++
# ++ + + ++
# O+ +O
# ++ ++ ++ ++
# + + + +
# + O +
# + + +
# ++++O + O++++
# + + +
# + O +
# + + + +
# ++ ++ ++ ++
# O+ +O
# ++ + + ++
# ++ + + ++
# O + + O
# + + + +
# + O+++++++++O +
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'u_boundary': None,
'v_boundary': None,
'interior': None},
'middle': None,
'bottom': {'u_boundary': None,
'v_boundary': None,
'interior': None}},
'center': {'top': None,
'bottom': None},
'right': {'top': {'u_boundary': None,
'v_boundary': None,
'interior': None},
'middle': None,
'bottom': {'u_boundary': None,
'v_boundary': None,
'interior': None}}}
def init_faces(self):
return {'left': {'top': {'u_boundary': None,
'v_boundary': None},
'middle': None,
'bottom': {'u_boundary': None,
'v_boundary': None}},
'center': {'top': None,
'bottom': None},
'right': {'top': {'u_boundary': None,
'v_boundary': None},
'middle': None,
'bottom': {'u_boundary': None,
'v_boundary': None}}}
def calculate_verts(self):
# u_unit is the length of the edges expressed as a
# proportion of the tile
u_unit = 1.0 / (1.0 + sqrt(3))
u0 = v0 = 0
u1 = v1 = u_unit/(2*sqrt(3))
u3 = v3 = (0.5 + 1/sqrt(3)) * u_unit
u2 = v2 = 0.5*(u1 + u3)
u4 = v4 = 0.5
self.add_vert(['left', 'bottom', 'u_boundary'], u0, v1,
u_boundary=True)
self.add_vert(['left', 'bottom', 'v_boundary'], u3, v0,
v_boundary=True)
self.add_vert(['left', 'bottom', 'interior'], u2, v2)
self.add_vert(['left', 'middle'], u1, v4)
self.add_vert(['center', 'bottom'], u4, v3)
def calculate_faces(self):
self.add_face(['left', 'bottom', 'u_boundary'],
[['left', 'bottom', 'u_boundary'],
['left', 'bottom', 'interior'],
['left', 'middle'],
left_tile(['right', 'middle']),
left_tile(['right', 'bottom', 'interior'])],
u_boundary=True)
self.add_face(['left', 'bottom', 'v_boundary'],
[['left', 'bottom', 'u_boundary'],
['left', 'bottom', 'interior'],
['left', 'bottom', 'v_boundary'],
bottom_tile(['left', 'top', 'interior']),
bottom_tile(['left', 'top', 'u_boundary'])],
v_boundary=True)
self.add_face(['left', 'middle'],
[['left', 'middle'],
['left', 'bottom', 'interior'],
['center', 'bottom'],
['center', 'top'],
['left', 'top', 'interior']])
self.add_face(['center', 'bottom'],
[['left', 'bottom', 'interior'],
['center', 'bottom'],
['right', 'bottom', 'interior'],
['right', 'bottom', 'v_boundary'],
['left', 'bottom', 'v_boundary']])
def color_pattern1(self):
self.color_paths([
['right', 'middle'],
['center', 'bottom'],
['right', 'bottom', 'v_boundary'],
['right', 'bottom', 'u_boundary'],
], 1, 0)
class PentaTessagon(Tessagon):
tile_class = PentaTile
metadata = metadata

View File

@ -0,0 +1,194 @@
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import top_tile, bottom_tile, \
left_tile, right_tile, left_bottom_tile, left_top_tile, \
right_bottom_tile, right_top_tile
metadata = TessagonMetadata(name='Pythagorean',
num_color_patterns=1,
classification='non_edge',
shapes=['squares'],
sides=[4],
uv_ratio=1.0)
class PythagoreanTile(Tile):
# 29 verts in six rows, six columns
# 12 faces (2 sets shared with neighbors)
#
# o...o-o-o-o row6 o.1.o-o-o-o
# |...|.|...| |...|2|...|
# o-o-o-o...o row5 o-o-o-o.3.o
# ^ |.|...|...| |4|...|...|
# | o-o...o-o-o row4 o-o.5.o-o-o
# | ..|...|.|.. ..|...|7|..
# | ..o-o-o-o.. row3 6.o-o-o-o.8
# ..|.|...|.. ..|9|...|..
# V o-o-o...o-o row2 o-o-o10.o-o
# |...|...|.| |...|...12|
# o...o-o-o-o row1 o11.o-o-o-o
#
# 1 2 3 4 5 6 <-cols
#
# U ------>
def init_verts(self):
# [col, row], these read like columns
return {1: {1: None, 2: None, 4: None, 5: None, 6: None},
2: {2: None, 3: None, 4: None, 5: None},
3: {1: None, 2: None, 3: None, 5: None, 6: None},
4: {1: None, 3: None, 4: None, 5: None, 6: None},
5: {1: None, 2: None, 3: None, 4: None, 6: None},
6: {1: None, 2: None, 4: None, 5: None, 6: None}}
def calculate_verts(self):
c = {1: 0.0,
2: 1/5.0,
3: 2/5.0,
4: 3/5.0,
5: 4/5.0,
6: 1.0}
for col in self.verts.keys():
for row in self.verts[col].keys():
# Some verts only get created if neighbors exist
if col == 1:
if not self.get_neighbor_tile(['left']):
if row == 6 and not self.get_neighbor_tile(['top']):
continue
if not self.get_neighbor_tile(['bottom']):
if row == 1 or row == 2:
continue
vert = self.add_vert([col, row], c[col], c[row])
if col == 1:
self.set_equivalent_vert(*left_tile([6, row]), vert)
if row == 6:
self.set_equivalent_vert(*left_top_tile([6, 1]), vert)
elif row == 1:
self.set_equivalent_vert(*left_bottom_tile([6, 6]),
vert)
elif col == 6:
self.set_equivalent_vert(*right_tile([1, row]), vert)
if row == 6:
self.set_equivalent_vert(*right_top_tile([1, 1]),
vert)
elif row == 1:
self.set_equivalent_vert(*right_bottom_tile([1, 6]),
vert)
if row == 6:
self.set_equivalent_vert(*top_tile([col, 1]), vert)
elif row == 1:
self.set_equivalent_vert(*bottom_tile([col, 6]), vert)
def init_faces(self):
return {1: None, 2: None, 3: None, 4: None, 5: None, 6: None,
7: None, 8: None, 9: None, 10: None, 11: None, 12: None}
def calculate_faces(self):
face = self.add_face(1, [[1, 6],
[1, 5],
[2, 5],
[3, 5],
[3, 6],
top_tile([3, 2]),
top_tile([2, 2]),
top_tile([1, 2])])
self.set_equivalent_face(*top_tile(11), face)
self.add_face(2, [[3, 6],
[4, 6],
[4, 5],
[3, 5]])
self.add_face(3, [[4, 6],
[5, 6],
[6, 6],
[6, 5],
[6, 4],
[5, 4],
[4, 4],
[4, 5]])
self.add_face(4, [[1, 5],
[2, 5],
[2, 4],
[1, 4]])
self.add_face(5, [[2, 5],
[3, 5],
[4, 5],
[4, 4],
[4, 3],
[3, 3],
[2, 3],
[2, 4]])
face = self.add_face(6, [[1, 4],
[2, 4],
[2, 3],
[2, 2],
[1, 2],
left_tile([5, 2]),
left_tile([5, 3]),
left_tile([5, 4])])
self.set_equivalent_face(*left_tile(8), face)
self.add_face(7, [[4, 4],
[5, 4],
[5, 3],
[4, 3]])
face = self.add_face(8, [[6, 4],
[5, 4],
[5, 3],
[5, 2],
[6, 2],
right_tile([2, 2]),
right_tile([2, 3]),
right_tile([2, 4])])
self.set_equivalent_face(*right_tile(6), face)
self.add_face(9, [[2, 3],
[3, 3],
[3, 2],
[2, 2]])
self.add_face(10, [[3, 3],
[4, 3],
[5, 3],
[5, 2],
[5, 1],
[4, 1],
[3, 1],
[3, 2]])
face = self.add_face(11, [[1, 1],
[1, 2],
[2, 2],
[3, 2],
[3, 1],
bottom_tile([3, 5]),
bottom_tile([2, 5]),
bottom_tile([1, 5])])
self.set_equivalent_face(*bottom_tile(1), face)
self.add_face(12, [[5, 2],
[6, 2],
[6, 1],
[5, 1]])
def color_pattern1(self):
# Color the big ones
self.color_face([1], 1)
self.color_face([3], 1)
self.color_face([5], 1)
self.color_face([6], 1)
self.color_face([8], 1)
self.color_face([10], 1)
self.color_face([11], 1)
class PythagoreanTessagon(Tessagon):
tile_class = PythagoreanTile
metadata = metadata

View File

@ -0,0 +1,93 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, top_tile
metadata = TessagonMetadata(name='Rhombuses',
num_color_patterns=2,
classification='laves',
shapes=['rhombuses'],
sides=[4],
uv_ratio=1.0/sqrt(3.0))
class RhombusTile(Tile):
# ..o..
# ./|\.
# o.|.o
# ^ |.o.|
# | |/.\|
# | o...o
# | |\./|
# | |.o.|
# | o.|.o
# .\|/.
# V ..o..
#
# U --->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': None, 'middle': None, 'bottom': None},
'center': {'top': {'boundary': None, 'interior': None},
'bottom': {'boundary': None, 'interior': None}},
'right': {'top': None, 'middle': None, 'bottom': None}}
def init_faces(self):
return {'middle': None,
'left': {'top': {'interior': None, 'exterior': None},
'bottom': {'interior': None, 'exterior': None}},
'right': {'top': {'interior': None, 'exterior': None},
'bottom': {'interior': None, 'exterior': None}}}
def calculate_verts(self):
# 10 verts, do top left quadrant, others via symmetry
self.add_vert(['center', 'top', 'boundary'], 0.5, 1, v_boundary=True)
self.add_vert(['left', 'top'], 0, 5.0/6.0, u_boundary=True)
self.add_vert(['center', 'top', 'interior'], 0.5, 2.0/3.0)
self.add_vert(['left', 'middle'], 0, 1.0/2.0, u_boundary=True)
def calculate_faces(self):
# One middle face
self.add_face('middle',
[['center', 'top', 'interior'],
['left', 'middle'],
['center', 'bottom', 'interior'],
['right', 'middle']], face_type='horizontal')
# Eight others, define only left top, others by symmetry
self.add_face(['left', 'top', 'interior'],
[['center', 'top', 'boundary'],
['left', 'top'],
['left', 'middle'],
['center', 'top', 'interior']], face_type='upward')
self.add_face(['left', 'top', 'exterior'],
[['center', 'top', 'boundary'],
['left', 'top'],
# Verts on neighbor tile
left_tile(['center', 'top', 'boundary']),
top_tile(['left', 'bottom'])],
face_type='horizontal', corner=True)
def color_pattern1(self):
self.color_face(['middle'], 1)
self.color_face(['left', 'top', 'exterior'], 1)
self.color_face(['left', 'top', 'interior'], 2)
self.color_face(['right', 'bottom', 'interior'], 2)
def color_pattern2(self):
self.color_face(['left', 'top', 'interior'], 1)
self.color_face(['right', 'top', 'interior'], 1)
self.color_face(['left', 'bottom', 'interior'], 2)
self.color_face(['right', 'bottom', 'interior'], 2)
class RhombusTessagon(Tessagon):
tile_class = RhombusTile
metadata = metadata

View File

@ -0,0 +1,104 @@
from tessagon.core.tessagon import Tessagon
from tessagon.core.tile import Tile
from tessagon.core.tessagon_metadata import TessagonMetadata
metadata = TessagonMetadata(name='Regular Squares',
num_color_patterns=8,
classification='regular',
shapes=['squares'],
sides=[4],
uv_ratio=1.0)
class SquareTile(Tile):
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'top': {'left': None, 'right': None},
'bottom': {'left': None, 'right': None}}
def init_faces(self):
return {'middle': None}
def calculate_verts(self):
self.add_vert(['top', 'left'], 0, 1, corner=True)
def calculate_faces(self):
self.add_face('middle', [['top', 'left'],
['top', 'right'],
['bottom', 'right'],
['bottom', 'left']])
def color_pattern1(self):
if (self.fingerprint[0] + self.fingerprint[1]) % 2 == 0:
self.color_face(['middle'], 0)
else:
self.color_face(['middle'], 1)
def color_pattern2(self):
if (self.fingerprint[0] + self.fingerprint[1]) % 2 == 0:
self.color_face(['middle'], 0)
elif self.fingerprint[0] % 2 == 0:
self.color_face(['middle'], 1)
else:
self.color_face(['middle'], 2)
def color_pattern3(self):
if (self.fingerprint[0] * self.fingerprint[1]) % 2 == 0:
self.color_face(['middle'], 0)
else:
self.color_face(['middle'], 1)
def color_pattern4(self):
if self.fingerprint[1] % 2 == 0:
self.color_face(['middle'], 0)
else:
if ((self.fingerprint[1] // 2) + self.fingerprint[0]) % 2 == 0:
self.color_face(['middle'], 0)
else:
self.color_face(['middle'], 1)
def color_pattern5(self):
if self.fingerprint[1] % 2 == 0:
self.color_face(['middle'], 0)
else:
self.color_face(['middle'], 1)
def color_pattern6(self):
if self.fingerprint[1] % 2 == 0:
self.color_face(['middle'], 0)
else:
if self.fingerprint[0] % 2 == 0:
self.color_face(['middle'], 1)
else:
self.color_face(['middle'], 2)
def color_pattern7(self):
if self.fingerprint[1] % 2 == 0:
self.color_face(['middle'], 0)
else:
if ((self.fingerprint[1] // 2) + self.fingerprint[0]) % 2 == 0:
self.color_face(['middle'], 1)
else:
self.color_face(['middle'], 2)
def color_pattern8(self):
if self.fingerprint[1] % 2 == 0:
if self.fingerprint[0] % 2 == 0:
self.color_face(['middle'], 0)
else:
self.color_face(['middle'], 1)
else:
if self.fingerprint[0] % 2 == 0:
self.color_face(['middle'], 2)
else:
self.color_face(['middle'], 3)
class SquareTessagon(Tessagon):
tile_class = SquareTile
metadata = metadata

View File

@ -0,0 +1,98 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
metadata = TessagonMetadata(name='Other Squares and Triangles',
num_color_patterns=1,
classification='archimedean',
shapes=['squares', 'triangles'],
sides=[4, 3],
uv_ratio=1.0/(2.0+sqrt(3.0)))
class SquareTri2Tile(Tile):
# 6 verts, 11 faces (3 internal, 8 on boundary)
#
# ^ ..|..
# | --O--
# | ./.\.
# | O---O
# |...|
# V O---O
# .\./.
# --O--
# ..|..
#
# U ----->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': {'u_boundary': None},
'bottom': {'u_boundary': None}},
'right': {'top': {'u_boundary': None},
'bottom': {'u_boundary': None}},
'center': {'top': None,
'bottom': None}}
def init_faces(self):
return {'left': {'top': {'corner': None,
'u_boundary': None},
'bottom': {'corner': None,
'u_boundary': None}},
'right': {'top': {'corner': None,
'u_boundary': None},
'bottom': {'corner': None,
'u_boundary': None}},
'center': {'top': None,
'middle': None,
'bottom': None}}
def calculate_verts(self):
v_unit = 1.0 / (2 + sqrt(3))
v1 = v_unit * 0.5
v2 = 0.5 - v1
# Other verts defined through symmetry
self.add_vert(['center', 'bottom'], 0.5, v1)
self.add_vert(['left', 'bottom', 'u_boundary'], 0, v2, u_boundary=True)
def calculate_faces(self):
self.add_face(['left', 'bottom', 'corner'],
[['center', 'bottom'],
[['left'], ['center', 'bottom']],
[['left', 'bottom'], ['center', 'top']],
[['bottom'], ['center', 'top']]],
face_type='square', corner=True)
self.add_face(['left', 'bottom', 'u_boundary'],
[['center', 'bottom'],
['left', 'bottom', 'u_boundary'],
[['left'], ['center', 'bottom']]],
face_type='triangle', u_boundary=True)
self.add_face(['center', 'bottom'],
[['left', 'bottom', 'u_boundary'],
['center', 'bottom'],
['right', 'bottom', 'u_boundary']],
face_type='triangle')
self.add_face(['center', 'middle'],
[['left', 'bottom', 'u_boundary'],
['right', 'bottom', 'u_boundary'],
['right', 'top', 'u_boundary'],
['left', 'top', 'u_boundary']],
face_type='square')
def color_pattern1(self):
self.color_paths([['left', 'bottom', 'corner'],
['center', 'middle']], 1, 0)
class SquareTri2Tessagon(Tessagon):
tile_class = SquareTri2Tile
metadata = metadata

View File

@ -0,0 +1,145 @@
from math import sqrt
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
metadata = TessagonMetadata(name='Squares and Triangles',
num_color_patterns=2,
classification='archimedean',
shapes=['squares', 'triangles'],
sides=[4, 3],
uv_ratio=1.0)
class SquareTriTile(Tile):
# 12 verts, 16 faces (8 internal, 8 on boundary)
# The angles make it hard to draw all edges, some excluded
#
#
# ^ ..o..|..o.. 3.o.3|3.o.3
# | ./...o...\. ./...o...\.
# | o.../.\...o o.4./3\.4.o
# | ...o---o... .3.o---o.3.
# o...\./...o o.4.\3/.4.o
# V .\...o.../. .\...o.../.
# ..o..|..o.. 3.o.3|3.o.3
# U ----->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
# u_square means on the square that is on the U-boundary
return {'top': {'left': {'u_boundary': None,
'v_boundary': None},
'right': {'u_boundary': None,
'v_boundary': None},
'center': None},
'bottom': {'left': {'u_boundary': None,
'v_boundary': None},
'right': {'u_boundary': None,
'v_boundary': None},
'center': None},
'middle': {'left': None,
'right': None}}
def init_faces(self):
return {'tri': {'top': {'left': {'u_boundary': None,
'v_boundary': None},
'right': {'u_boundary': None,
'v_boundary': None},
'center': None},
'bottom': {'left': {'u_boundary': None,
'v_boundary': None},
'right': {'u_boundary': None,
'v_boundary': None},
'center': None},
'middle': {'left': None,
'right': None}},
'square': {'top': {'left': None,
'right': None},
'bottom': {'left': None,
'right': None}}}
def calculate_verts(self):
# u_unit is the length of the edges expressed as a
# proportion of the tile
u_unit = 1.0 / (1.0 + sqrt(3))
u0 = 0
u1 = 0.5*u_unit
u2 = 0.5*(1.0-u_unit)
u3 = 0.5
v_unit = 1.0 / (1.0 + sqrt(3))
v0 = 0.5
v1 = 0.5 * (1.0 + v_unit)
v2 = 1.0 - 0.5*v_unit
v3 = 1.0
# Define top left square, other verts defined through symmetry
self.add_vert(['top', 'left', 'u_boundary'], u0, v1, u_boundary=True)
self.add_vert(['top', 'left', 'v_boundary'], u1, v3, v_boundary=True)
self.add_vert(['top', 'center'], u3, v2)
self.add_vert(['middle', 'left'], u2, v0)
def calculate_faces(self):
# 4 internal squares (others via symmetry)
self.add_face(['square', 'top', 'left'],
[['top', 'left', 'u_boundary'],
['top', 'left', 'v_boundary'],
['top', 'center'],
['middle', 'left']],
face_type='square')
# 4 u-boundary triangles
self.add_face(['tri', 'top', 'left', 'u_boundary'],
[['top', 'left', 'v_boundary'],
['top', 'left', 'u_boundary'],
[['left'], ['top', 'right', 'v_boundary']]],
face_type='triangle', u_boundary=True)
# 4 v-boundary triangles
self.add_face(['tri', 'top', 'left', 'v_boundary'],
[['top', 'left', 'v_boundary'],
['top', 'center'],
[['top'], ['bottom', 'center']]],
face_type='triangle', v_boundary=True)
# 2 internal center triangles
self.add_face(['tri', 'top', 'center'],
[['top', 'center'],
['middle', 'right'],
['middle', 'left']],
face_type='triangle')
# 2 internal middle triangles
self.add_face(['tri', 'middle', 'left'],
[['middle', 'left'],
['bottom', 'left', 'u_boundary'],
['top', 'left', 'u_boundary']],
face_type='triangle')
def color_pattern1(self):
self.color_face(['square', 'top', 'left'], 1)
self.color_face(['square', 'top', 'right'], 1)
self.color_face(['square', 'bottom', 'left'], 1)
self.color_face(['square', 'bottom', 'right'], 1)
def color_pattern2(self):
self.color_face(['square', 'top', 'left'], 1)
self.color_face(['square', 'top', 'right'], 1)
self.color_face(['square', 'bottom', 'left'], 1)
self.color_face(['square', 'bottom', 'right'], 1)
self.color_face(['tri', 'middle', 'left'], 2)
self.color_face(['tri', 'middle', 'right'], 2)
self.color_face(['tri', 'top', 'left', 'v_boundary'], 2)
self.color_face(['tri', 'top', 'right', 'v_boundary'], 2)
class SquareTriTessagon(Tessagon):
tile_class = SquareTriTile
metadata = metadata

View File

@ -0,0 +1,142 @@
from math import sqrt
from tessagon.core.tessagon import Tessagon
from tessagon.core.tile import Tile
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import top_tile, bottom_tile, \
top_left_tile, left_tile
metadata = TessagonMetadata(name='Stanley Park',
num_color_patterns=2,
classification='non_convex',
sides=[12],
uv_ratio=sqrt(3.0))
# Non-convex pattern. Might work better for 2D than 3D
# Also, the ASCII art below is a bit hard to visualize,
# so check out preview images linked from README.md
class StanleyParkTile(Tile):
# VERTS:
# ....a...b.... a = ['top', 'left']
# ^ .../.....\... b = ['top', 'right']
# | ..c.......d.. c = ['mid1', 'left']
# | ..|.......|.. d = ['mid1', 'right']
# | ..e...f...g . e = ['mid2', 'left']
# | ./.\./.\./.\. f = ['mid2', 'center']
# h...i...j...k g = ['mid2', 'right']
# V ....|...|.... h = ['mid3', 'left', 'outer']
# ....l...m.... i = ['mid3', 'left', 'inner']
# j = ['mid3', 'right', 'inner']
# U ---> k = ['mid3', 'right', 'outer']
# l = ['bottom', 'left']
# m = ['bottom', 'right']
# FACES:
# ....o...o....
# ^ .A./.....\.C. A = ['top', 'left']
# | ..o...B...o.. B = ['top', 'middle']
# | ..|.......|.. C = ['top', 'right']
# | ..o...o...o . D = ['bottom', 'left']
# | ./.\./.\./.\. E = ['bottom', 'middle']
# o...o...o...o F = ['bottom', 'right']
# V ..D.|.E.|.F..
# ....o...o....
#
# U --->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = False
def init_verts(self):
return {'top': {'left': None,
'right': None},
'mid1': {'left': None,
'right': None},
'mid2': {'left': None,
'center': None,
'right': None},
'mid3': {'left': {'outer': None,
'inner': None},
'right': {'inner': None,
'outer': None}},
'bottom': {'left': None,
'right': None}}
def init_faces(self):
return {'top': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None}}
def calculate_verts(self):
vert = self.add_vert(['top', 'left'], 2.0/6.0, 1.0)
self.set_equivalent_vert(*top_tile(['bottom', 'left']), vert)
# Reflection doesn't handle 'set_equivalent_vert' so ...
vert = self.add_vert(['top', 'right'], 4.0/6.0, 1.0)
self.set_equivalent_vert(*top_tile(['bottom', 'right']), vert)
self.add_vert(['mid1', 'left'], 1.0/6.0, 5.0/6.0)
self.add_vert(['mid2', 'left'], 1.0/6.0, 3.0/6.0)
self.add_vert(['mid2', 'center'], 3.0/6.0, 3.0/6.0)
self.add_vert(['mid3', 'left', 'outer'], 0.0, 2.0/6.0,
u_boundary=True)
self.add_vert(['mid3', 'left', 'inner'], 2.0/6.0, 2.0/6.0)
vert = self.add_vert(['bottom', 'left'], 2.0/6.0, 0.0)
self.set_equivalent_vert(*bottom_tile(['top', 'left']), vert)
vert = self.add_vert(['bottom', 'right'], 4.0/6.0, 0.0)
self.set_equivalent_vert(*bottom_tile(['top', 'right']), vert)
def calculate_faces(self):
face = self.add_face(['top', 'left'],
[['mid3', 'left', 'outer'],
['mid2', 'left'],
['mid1', 'left'],
['top', 'left'],
top_tile(['mid3', 'left', 'inner']),
top_tile(['mid2', 'left']),
top_tile(['mid3', 'left', 'outer']),
top_left_tile(['mid2', 'right']),
top_left_tile(['mid3', 'right', 'inner']),
left_tile(['top', 'right']),
left_tile(['mid1', 'right']),
left_tile(['mid2', 'right'])],
u_boundary=True)
self.set_equivalent_face(*top_tile(['bottom', 'left']), face)
self.set_equivalent_face(*top_left_tile(['bottom', 'right']), face)
self.set_equivalent_face(*left_tile(['top', 'right']), face)
face = self.add_face(['top', 'center'],
[['top', 'left'],
['mid1', 'left'],
['mid2', 'left'],
['mid3', 'left', 'inner'],
['mid2', 'center'],
['mid3', 'right', 'inner'],
['mid2', 'right'],
['mid1', 'right'],
['top', 'right'],
top_tile(['mid3', 'right', 'inner']),
top_tile(['mid2', 'center']),
top_tile(['mid3', 'left', 'inner'])])
self.set_equivalent_face(*top_tile(['bottom', 'center']), face)
def color_pattern1(self):
self.color_face(['top', 'center'], 1)
self.color_face(['bottom', 'center'], 1)
def color_pattern2(self):
if self.fingerprint[1] % 2 == 0:
self.color_face(['top', 'left'], 1)
self.color_face(['top', 'center'], 1)
self.color_face(['top', 'right'], 1)
class StanleyParkTessagon(Tessagon):
tile_class = StanleyParkTile
metadata = metadata

View File

@ -0,0 +1,152 @@
from math import sqrt
from tessagon.core.tessagon import Tessagon
from tessagon.core.tile import Tile
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import top_tile
metadata = TessagonMetadata(name='Regular Triangles',
num_color_patterns=3,
classification='regular',
shapes=['triangles'],
sides=[3],
uv_ratio=sqrt(3.0))
class TriTile(Tile):
# ^ 0.|.1 This is the topology of the tile.
# | |\|/| (Not a Dead Kennedy's logo ...).
# | |.2.|
# | |/|\|
# V 3.|.4
#
# U ----->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
def init_verts(self):
return {'left': {'top': None, 'bottom': None},
'middle': None,
'right': {'top': None, 'bottom': None}}
def init_faces(self):
return {'left': {'top': None, 'middle': None, 'bottom': None},
'right': {'top': None, 'middle': None, 'bottom': None}}
def calculate_verts(self):
# Four corners, via symmetry
self.add_vert(['left', 'top'], 0, 1, corner=True)
# The middle vert
self.add_vert('middle', 0.5, 0.5)
def calculate_faces(self):
# Four corners, via symmetry
self.add_face(['left', 'top'],
[['left', 'top'],
['middle'],
# Vert on neighboring tile
top_tile(['middle'])], v_boundary=True)
# Two interior faces, via symmetry
self.add_face(['left', 'middle'],
[['left', 'top'],
['left', 'bottom'],
['middle']])
def color_pattern1(self):
# two colors for triangles pointing in different directions
self.color_face(['left', 'top'], 0)
self.color_face(['right', 'top'], 1)
self.color_face(['left', 'middle'], 1)
self.color_face(['right', 'middle'], 0)
self.color_face(['left', 'bottom'], 0)
self.color_face(['right', 'bottom'], 1)
def color_pattern2(self):
# Two colors, this one is awesome, but complicated
if not self.fingerprint:
return
if self.fingerprint[1] % 3 == 0:
if self.fingerprint[0] % 3 == 0:
self.color_0_0()
elif self.fingerprint[0] % 3 == 1:
self.color_0_1()
elif self.fingerprint[1] % 3 == 1:
if self.fingerprint[0] % 3 == 0:
self.color_1_0()
elif self.fingerprint[0] % 3 == 1:
self.color_1_1()
else:
self.color_1_2()
else:
if self.fingerprint[0] % 3 == 0:
self.color_2_0()
elif self.fingerprint[0] % 3 == 1:
self.color_2_1()
else:
self.color_2_2()
def color_pattern3(self):
if not self.fingerprint:
return
if self.fingerprint[1] % 3 == 2:
self.color_paths([['left', 'middle'],
['right', 'bottom']], 1, 0)
elif self.fingerprint[1] % 3 == 1:
self.color_paths([['right', 'top'],
['right', 'bottom']], 1, 0)
else:
self.color_paths([['left', 'middle'],
['right', 'top']], 1, 0)
def color_0_0(self):
self.color_paths([], 0, 1)
def color_0_1(self):
paths = [['left', 'top'],
['left', 'bottom'],
['right', 'middle']]
self.color_paths(paths, 1, 0)
def color_1_0(self):
paths = [['left', 'top'],
['left', 'bottom'],
['right', 'bottom']]
self.color_paths(paths, 1, 0)
def color_1_1(self):
paths = [['left', 'bottom'],
['right', 'top'],
['right', 'middle']]
self.color_paths(paths, 1, 0)
def color_1_2(self):
paths = [['left', 'top'],
['left', 'middle'],
['right', 'middle']]
self.color_paths(paths, 1, 0)
def color_2_0(self):
paths = [['left', 'top'],
['left', 'bottom'],
['right', 'top']]
self.color_paths(paths, 1, 0)
def color_2_1(self):
paths = [['left', 'top'],
['right', 'middle'],
['right', 'bottom']]
self.color_paths(paths, 1, 0)
def color_2_2(self):
paths = [['left', 'middle'],
['left', 'bottom'],
['right', 'middle']]
self.color_paths(paths, 1, 0)
class TriTessagon(Tessagon):
tile_class = TriTile
metadata = metadata

View File

@ -0,0 +1,133 @@
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, right_tile, \
top_tile, top_left_tile, top_right_tile, \
bottom_tile, bottom_left_tile, bottom_right_tile
metadata = TessagonMetadata(name='Valemount',
num_color_patterns=1,
classification='non_edge',
shapes=['rectangles', 'squares'],
sides=[4],
uv_ratio=1.0)
class ValemountTile(Tile):
# o--o--o--o 1,2,3,4
# |..|.....|
# o..o--o--o 5,6,7,8
# ^ |..|..|..|
# | o--o--o..o 9,10,11,12
# | |.....|..|
# | o--o--o--o 13,14,15,16
# V
# U --->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = False
self.v_symmetric = False
def init_verts(self):
# Naming stuff is hard ...
return {1: None,
2: None,
3: None,
4: None,
5: None,
6: None,
7: None,
8: None,
9: None,
10: None,
11: None,
12: None,
13: None,
14: None,
15: None,
16: None}
def init_faces(self):
return {'top_left': None,
'top_right': None,
'bottom_left': None,
'bottom_right': None,
'center': None}
def calculate_verts(self):
# Top row
vert = self.add_vert([1], 0, 1)
self.set_equivalent_vert(*left_tile(4), vert)
self.set_equivalent_vert(*top_tile(13), vert)
self.set_equivalent_vert(*top_left_tile(16), vert)
vert = self.add_vert([2], 1/3.0, 1)
self.set_equivalent_vert(*top_tile(14), vert)
vert = self.add_vert([3], 2/3.0, 1)
self.set_equivalent_vert(*top_tile(15), vert)
vert = self.add_vert([4], 1, 1)
self.set_equivalent_vert(*right_tile(1), vert)
self.set_equivalent_vert(*top_tile(16), vert)
self.set_equivalent_vert(*top_right_tile(13), vert)
# Next row
vert = self.add_vert([5], 0, 2/3.0)
self.set_equivalent_vert(*left_tile(8), vert)
self.add_vert([6], 1/3.0, 2/3.0)
self.add_vert([7], 2/3.0, 2/3.0)
vert = self.add_vert([8], 1, 2/3.0)
self.set_equivalent_vert(*right_tile(5), vert)
# Next row
vert = self.add_vert([9], 0, 1/3.0)
self.set_equivalent_vert(*left_tile(12), vert)
self.add_vert([10], 1/3.0, 1/3.0)
self.add_vert([11], 2/3.0, 1/3.0)
vert = self.add_vert([12], 1, 1/3.0)
self.set_equivalent_vert(*right_tile(9), vert)
# Bottom row
vert = self.add_vert([13], 0, 0)
self.set_equivalent_vert(*left_tile(16), vert)
self.set_equivalent_vert(*bottom_tile(1), vert)
self.set_equivalent_vert(*bottom_left_tile(4), vert)
vert = self.add_vert([14], 1/3.0, 0)
self.set_equivalent_vert(*bottom_tile(2), vert)
vert = self.add_vert([15], 2/3.0, 0)
self.set_equivalent_vert(*bottom_tile(3), vert)
vert = self.add_vert([16], 1, 0)
self.set_equivalent_vert(*right_tile(13), vert)
self.set_equivalent_vert(*bottom_tile(4), vert)
self.set_equivalent_vert(*bottom_right_tile(1), vert)
def calculate_faces(self):
self.add_face('top_left',
[1, 2, 6, 10, 9, 5])
self.add_face('top_right',
[2, 3, 4, 8, 7, 6])
self.add_face('bottom_left',
[9, 10, 11, 15, 14, 13])
self.add_face('bottom_right',
[7, 8, 12, 16, 15, 11])
self.add_face('center',
[6, 7, 11, 10])
def color_pattern1(self):
self.color_paths([
['center']
], 1, 0)
class ValemountTessagon(Tessagon):
tile_class = ValemountTile
metadata = metadata

View File

@ -0,0 +1,162 @@
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, top_tile, left_top_tile
metadata = TessagonMetadata(name='Weave',
num_color_patterns=1,
classification='non_edge',
shapes=['quads', 'rectangles'],
sides=[4],
uv_ratio=1.0,
extra_parameters={
'square_ratio': {
'type': 'float',
'min': 0.0,
'max': 1.0,
'default': 0.5,
'description':
'Control the size of the squares'
}
})
class WeaveTile(Tile):
# 16 verts, 13 faces (5 internal, 8 on boundary)
#
# ....|..|.... .8..|.8|.8..
# -o--o..o--o- -o--o..o--o-
# ^ .|..|..|..|. .|4.|..|4.|.
# | .o--o--o--o. .o--o--o--o.
# | .|........|. 8|...8....|8
# | .o--o--o--o. .o--o--o--o.
# .|..|..|..|. .|4.|..|4.|.
# V -o--o..o--o. -o--o..o--o.
# ....|..|.... 8...|8.|...8
#
# U ----->
def __init__(self, tessagon, **kwargs):
super().__init__(tessagon, **kwargs)
self.u_symmetric = True
self.v_symmetric = True
self.square_ratio = kwargs.get('square_ratio', 0.5)
def init_verts(self):
# u_square means on the square that is on the U-boundary
return {'top': {'left': {'u_inner': {'v_inner': None,
'v_outer': None},
'u_outer': {'v_inner': None,
'v_outer': None}},
'right': {'u_inner': {'v_inner': None,
'v_outer': None},
'u_outer': {'v_inner': None,
'v_outer': None}}},
'bottom': {'left': {'u_inner': {'v_inner': None,
'v_outer': None},
'u_outer': {'v_inner': None,
'v_outer': None}},
'right': {'u_inner': {'v_inner': None,
'v_outer': None},
'u_outer': {'v_inner': None,
'v_outer': None}}}}
def init_faces(self):
return {'square': {'top': {'left': None,
'right': None},
'bottom': {'left': None,
'right': None}},
'oct': {'top': {'left': None,
'center': None,
'right': None},
'middle': {'left': None,
'center': None,
'right': None},
'bottom': {'left': None,
'center': None,
'right': None}}}
def calculate_verts(self):
half_square_size = 0.25 * self.square_ratio
u0 = 0.25 - half_square_size
u1 = 0.25 + half_square_size
v0 = 0.75 - half_square_size
v1 = 0.75 + half_square_size
# Define top left square, other verts defined through symmetry
self.add_vert(['top', 'left', 'u_inner', 'v_inner'], u1, v0)
self.add_vert(['top', 'left', 'u_inner', 'v_outer'], u1, v1)
self.add_vert(['top', 'left', 'u_outer', 'v_inner'], u0, v0)
self.add_vert(['top', 'left', 'u_outer', 'v_outer'], u0, v1)
def calculate_faces(self):
# 4 internal squares (via symmetry)
self.add_face(['square', 'top', 'left'],
[['top', 'left', 'u_outer', 'v_outer'],
['top', 'left', 'u_inner', 'v_outer'],
['top', 'left', 'u_inner', 'v_inner'],
['top', 'left', 'u_outer', 'v_inner']],
face_type='square')
# 1 interior strip
self.add_face(['oct', 'middle', 'center'],
[['top', 'left', 'u_outer', 'v_inner'],
['top', 'left', 'u_inner', 'v_inner'],
['top', 'right', 'u_inner', 'v_inner'],
['top', 'right', 'u_outer', 'v_inner'],
['bottom', 'right', 'u_outer', 'v_inner'],
['bottom', 'right', 'u_inner', 'v_inner'],
['bottom', 'left', 'u_inner', 'v_inner'],
['bottom', 'left', 'u_outer', 'v_inner']],
face_type='oct')
# 4 corner strips
self.add_face(['oct', 'top', 'left'],
[['top', 'left', 'u_inner', 'v_outer'],
['top', 'left', 'u_outer', 'v_outer'],
left_tile(['top', 'right', 'u_outer', 'v_outer']),
left_tile(['top', 'right', 'u_inner', 'v_outer']),
left_top_tile(['bottom', 'right',
'u_inner', 'v_outer']),
left_top_tile(['bottom', 'right',
'u_outer', 'v_outer']),
top_tile(['bottom', 'left', 'u_outer', 'v_outer']),
top_tile(['bottom', 'left', 'u_inner', 'v_outer'])],
face_type='oct', corner=True)
# 2 side strips
self.add_face(['oct', 'middle', 'left'],
[['top', 'left', 'u_outer', 'v_outer'],
['top', 'left', 'u_outer', 'v_inner'],
['bottom', 'left', 'u_outer', 'v_inner'],
['bottom', 'left', 'u_outer', 'v_outer'],
left_tile(['bottom', 'right', 'u_outer', 'v_outer']),
left_tile(['bottom', 'right', 'u_outer', 'v_inner']),
left_tile(['top', 'right', 'u_outer', 'v_inner']),
left_tile(['top', 'right', 'u_outer', 'v_outer'])],
face_type='oct', u_boundary=True)
# 2 top/bottom strips
self.add_face(['oct', 'top', 'center'],
[['top', 'left', 'u_inner', 'v_outer'],
['top', 'left', 'u_inner', 'v_inner'],
['top', 'right', 'u_inner', 'v_inner'],
['top', 'right', 'u_inner', 'v_outer'],
top_tile(['bottom', 'right', 'u_inner', 'v_outer']),
top_tile(['bottom', 'right', 'u_inner', 'v_inner']),
top_tile(['bottom', 'left', 'u_inner', 'v_inner']),
top_tile(['bottom', 'left', 'u_inner', 'v_outer'])],
face_type='oct', v_boundary=True)
def color_pattern1(self):
self.color_face(['oct', 'top', 'center'], 1)
self.color_face(['oct', 'middle', 'left'], 1)
self.color_face(['oct', 'top', 'left'], 2)
self.color_face(['oct', 'middle', 'center'], 2)
class WeaveTessagon(Tessagon):
tile_class = WeaveTile
metadata = metadata

View File

@ -0,0 +1,171 @@
from tessagon.core.tile import Tile
from tessagon.core.tessagon import Tessagon
from tessagon.core.tessagon_metadata import TessagonMetadata
from tessagon.core.tile_utils import left_tile, right_tile, \
top_tile, bottom_tile, left_top_tile, left_bottom_tile, \
right_bottom_tile, right_top_tile
metadata = TessagonMetadata(name='Zig-Zag',
num_color_patterns=1,
classification='non_edge',
shapes=['rectangles'],
sides=[4],
uv_ratio=1.0)
class ZigZagTile(Tile):
# 25 verts in five rows, five columns
# 10 faces (2 sets shared with neighbors)
#
# o-o-o-o.o row5 o-o-o-o.o
# ^ |...|.|.| |.1.|.|3|
# | o-o-o.o-o row4 o-o-o2o-o
# | ..|.|.|.. 4.|.|.|.6
# | o-o.o-o-o row3 o-o5o-o-o
# |.|.|...| |.|.|.8.|
# V o.o-o-o-o row2 o7o-o-o-o
# |.|.|.|.| |.|.9.|10
# o-o-o-o.o row5 o-o-o-o.o
#
# 1 2 3 4 5 <-cols
#
# U ------>
def init_verts(self):
# [col, row], these read like columns
return {1: {1: None, 2: None, 3: None, 4: None, 5: None},
2: {1: None, 2: None, 3: None, 4: None, 5: None},
3: {1: None, 2: None, 3: None, 4: None, 5: None},
4: {1: None, 2: None, 3: None, 4: None, 5: None},
5: {1: None, 2: None, 3: None, 4: None, 5: None}}
def init_faces(self):
return {1: None, 2: None, 3: None, 4: None, 5: None, 6: None,
7: None, 8: None, 9: None, 10: None}
def calculate_verts(self):
c = {1: 0.0,
2: 1/4.0,
3: 2/4.0,
4: 3/4.0,
5: 1.0}
for col in self.verts.keys():
for row in self.verts[col].keys():
# Some verts only get created if neighbors exist
if col == 5:
if not self.get_neighbor_tile(['right']):
if not self.get_neighbor_tile(['top']):
if row == 5:
continue
if row == 4:
continue
if not self.get_neighbor_tile(['bottom']):
if row == 1:
continue
vert = self.add_vert([col, row], c[col], c[row])
if col == 1:
self.set_equivalent_vert(*left_tile([5, row]), vert)
if row == 5:
self.set_equivalent_vert(*left_top_tile([5, 1]),
vert)
elif row == 1:
self.set_equivalent_vert(*left_bottom_tile([5, 5]),
vert)
elif col == 5:
self.set_equivalent_vert(*right_tile([1, row]), vert)
if row == 5:
self.set_equivalent_vert(*right_top_tile([1, 1]),
vert)
elif row == 1:
self.set_equivalent_vert(*right_bottom_tile([1, 5]),
vert)
if row == 5:
self.set_equivalent_vert(*top_tile([col, 1]), vert)
elif row == 1:
self.set_equivalent_vert(*bottom_tile([col, 5]), vert)
def calculate_faces(self):
self.add_face(1, [[1, 5],
[1, 4],
[2, 4],
[3, 4],
[3, 5],
[2, 5]])
self.add_face(2, [[3, 5],
[3, 4],
[3, 3],
[4, 3],
[4, 4],
[4, 5]])
face = self.add_face(3, [[4, 5],
[4, 4],
[5, 4],
[5, 5],
top_tile([5, 2]),
top_tile([4, 2])])
self.set_equivalent_face(*top_tile(10), face)
face = self.add_face(4, [[1, 3],
[2, 3],
[2, 4],
[1, 4],
left_tile([4, 4]),
left_tile([4, 3])])
self.set_equivalent_face(*left_tile(6), face)
self.add_face(5, [[3, 2],
[3, 3],
[3, 4],
[2, 4],
[2, 3],
[2, 2]])
face = self.add_face(6, [[5, 4],
[4, 4],
[4, 3],
[5, 3],
right_tile([2, 3]),
right_tile([2, 4])])
self.set_equivalent_face(*right_tile(4), face)
self.add_face(7, [[2, 1],
[2, 2],
[2, 3],
[1, 3],
[1, 2],
[1, 1]])
self.add_face(8, [[5, 2],
[5, 3],
[4, 3],
[3, 3],
[3, 2],
[4, 2]])
self.add_face(9, [[4, 1],
[4, 2],
[3, 2],
[2, 2],
[2, 1],
[3, 1]])
face = self.add_face(10, [[5, 1],
[5, 2],
[4, 2],
[4, 1],
bottom_tile([4, 4]),
bottom_tile([5, 4])])
self.set_equivalent_face(*bottom_tile(3), face)
def color_pattern1(self):
self.color_face(1, 1)
self.color_face(2, 1)
self.color_face(7, 1)
self.color_face(8, 1)
class ZigZagTessagon(Tessagon):
tile_class = ZigZagTile
metadata = metadata

View File

@ -0,0 +1 @@
__version__ = '0.8'