From e2f85cb98f43b3cf6f4dd7a88cfb5c7d499d8c96 Mon Sep 17 00:00:00 2001 From: Chris Want Date: Thu, 29 Jun 2023 20:41:34 -0600 Subject: [PATCH] Tiling patterns for 'XYZ function', using Tessagon 0.8. --- add_mesh_extra_objects/__init__.py | 2 +- .../add_mesh_3d_function_surface.py | 123 +++++-- add_mesh_extra_objects/tessagon/__init__.py | 2 + .../tessagon/adaptors/__init__.py | 0 .../tessagon/adaptors/blender_adaptor.py | 27 ++ .../tessagon/adaptors/list_adaptor.py | 33 ++ .../tessagon/adaptors/vtk_adaptor.py | 48 +++ .../tessagon/core/__init__.py | 22 ++ .../tessagon/core/abstract_tile.py | 236 ++++++++++++ .../tessagon/core/alternating_tile.py | 15 + .../tessagon/core/grid_tile_generator.py | 83 +++++ .../core/parallelogram_tile_generator.py | 198 ++++++++++ .../tessagon/core/tessagon.py | 161 +++++++++ .../tessagon/core/tessagon_discovery.py | 116 ++++++ .../tessagon/core/tessagon_metadata.py | 61 ++++ add_mesh_extra_objects/tessagon/core/tile.py | 338 ++++++++++++++++++ .../tessagon/core/tile_generator.py | 69 ++++ .../tessagon/core/tile_utils.py | 46 +++ .../tessagon/core/value_blend.py | 35 ++ .../tessagon/misc/__init__.py | 0 .../tessagon/misc/shapes.py | 157 ++++++++ .../tessagon/types/__init__.py | 0 .../tessagon/types/big_hex_tri_tessagon.py | 175 +++++++++ .../tessagon/types/brick_tessagon.py | 76 ++++ .../tessagon/types/cloverdale_tessagon.py | 143 ++++++++ .../types/dissected_hex_quad_tessagon.py | 167 +++++++++ .../types/dissected_hex_tri_tessagon.py | 135 +++++++ .../types/dissected_square_tessagon.py | 112 ++++++ .../types/dissected_triangle_tessagon.py | 130 +++++++ .../tessagon/types/dodeca_tessagon.py | 208 +++++++++++ .../tessagon/types/dodeca_tri_tessagon.py | 145 ++++++++ .../tessagon/types/floret_tessagon.py | 192 ++++++++++ .../tessagon/types/hex_big_tri_tessagon.py | 132 +++++++ .../tessagon/types/hex_square_tri_tessagon.py | 169 +++++++++ .../tessagon/types/hex_tessagon.py | 129 +++++++ .../tessagon/types/hex_tri_tessagon.py | 101 ++++++ .../types/islamic_hex_stars_tessagon.py | 127 +++++++ .../types/islamic_stars_crosses_tessagon.py | 121 +++++++ .../tessagon/types/octo_tessagon.py | 84 +++++ .../tessagon/types/penta2_tessagon.py | 97 +++++ .../tessagon/types/penta_tessagon.py | 137 +++++++ .../tessagon/types/pythagorean_tessagon.py | 194 ++++++++++ .../tessagon/types/rhombus_tessagon.py | 93 +++++ .../tessagon/types/square_tessagon.py | 104 ++++++ .../tessagon/types/square_tri2_tessagon.py | 98 +++++ .../tessagon/types/square_tri_tessagon.py | 145 ++++++++ .../tessagon/types/stanley_park_tessagon.py | 142 ++++++++ .../tessagon/types/tri_tessagon.py | 152 ++++++++ .../tessagon/types/valemount_tessagon.py | 133 +++++++ .../tessagon/types/weave_tessagon.py | 162 +++++++++ .../tessagon/types/zig_zag_tessagon.py | 171 +++++++++ add_mesh_extra_objects/tessagon/version.py | 1 + 52 files changed, 5715 insertions(+), 32 deletions(-) create mode 100644 add_mesh_extra_objects/tessagon/__init__.py create mode 100644 add_mesh_extra_objects/tessagon/adaptors/__init__.py create mode 100644 add_mesh_extra_objects/tessagon/adaptors/blender_adaptor.py create mode 100644 add_mesh_extra_objects/tessagon/adaptors/list_adaptor.py create mode 100644 add_mesh_extra_objects/tessagon/adaptors/vtk_adaptor.py create mode 100644 add_mesh_extra_objects/tessagon/core/__init__.py create mode 100644 add_mesh_extra_objects/tessagon/core/abstract_tile.py create mode 100644 add_mesh_extra_objects/tessagon/core/alternating_tile.py create mode 100644 add_mesh_extra_objects/tessagon/core/grid_tile_generator.py create mode 100644 add_mesh_extra_objects/tessagon/core/parallelogram_tile_generator.py create mode 100644 add_mesh_extra_objects/tessagon/core/tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/core/tessagon_discovery.py create mode 100644 add_mesh_extra_objects/tessagon/core/tessagon_metadata.py create mode 100644 add_mesh_extra_objects/tessagon/core/tile.py create mode 100644 add_mesh_extra_objects/tessagon/core/tile_generator.py create mode 100644 add_mesh_extra_objects/tessagon/core/tile_utils.py create mode 100644 add_mesh_extra_objects/tessagon/core/value_blend.py create mode 100644 add_mesh_extra_objects/tessagon/misc/__init__.py create mode 100644 add_mesh_extra_objects/tessagon/misc/shapes.py create mode 100644 add_mesh_extra_objects/tessagon/types/__init__.py create mode 100644 add_mesh_extra_objects/tessagon/types/big_hex_tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/brick_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/cloverdale_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/dissected_hex_quad_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/dissected_hex_tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/dissected_square_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/dissected_triangle_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/dodeca_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/dodeca_tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/floret_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/hex_big_tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/hex_square_tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/hex_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/hex_tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/islamic_hex_stars_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/islamic_stars_crosses_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/octo_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/penta2_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/penta_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/pythagorean_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/rhombus_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/square_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/square_tri2_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/square_tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/stanley_park_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/tri_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/valemount_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/weave_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/types/zig_zag_tessagon.py create mode 100644 add_mesh_extra_objects/tessagon/version.py diff --git a/add_mesh_extra_objects/__init__.py b/add_mesh_extra_objects/__init__.py index 98e412aac..cf4be9d62 100644 --- a/add_mesh_extra_objects/__init__.py +++ b/add_mesh_extra_objects/__init__.py @@ -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 = { diff --git a/add_mesh_extra_objects/add_mesh_3d_function_surface.py b/add_mesh_extra_objects/add_mesh_3d_function_surface.py index 6ba565ace..854e51b32 100644 --- a/add_mesh_extra_objects/add_mesh_3d_function_surface.py +++ b/add_mesh_extra_objects/add_mesh_3d_function_surface.py @@ -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 object’s 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 diff --git a/add_mesh_extra_objects/tessagon/__init__.py b/add_mesh_extra_objects/tessagon/__init__.py new file mode 100644 index 000000000..3027628c8 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/__init__.py @@ -0,0 +1,2 @@ +from .version import __version__ +from .core.tessagon_discovery import TessagonDiscovery # noqa: F401 diff --git a/add_mesh_extra_objects/tessagon/adaptors/__init__.py b/add_mesh_extra_objects/tessagon/adaptors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/add_mesh_extra_objects/tessagon/adaptors/blender_adaptor.py b/add_mesh_extra_objects/tessagon/adaptors/blender_adaptor.py new file mode 100644 index 000000000..6333ad406 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/adaptors/blender_adaptor.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/adaptors/list_adaptor.py b/add_mesh_extra_objects/tessagon/adaptors/list_adaptor.py new file mode 100644 index 000000000..dd2dd17c5 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/adaptors/list_adaptor.py @@ -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} diff --git a/add_mesh_extra_objects/tessagon/adaptors/vtk_adaptor.py b/add_mesh_extra_objects/tessagon/adaptors/vtk_adaptor.py new file mode 100644 index 000000000..51866f688 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/adaptors/vtk_adaptor.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/core/__init__.py b/add_mesh_extra_objects/tessagon/core/__init__.py new file mode 100644 index 000000000..751c86b3c --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/__init__.py @@ -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'(?= 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)) diff --git a/add_mesh_extra_objects/tessagon/core/tessagon.py b/add_mesh_extra_objects/tessagon/core/tessagon.py new file mode 100644 index 000000000..73b90a60d --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/core/tessagon_discovery.py b/add_mesh_extra_objects/tessagon/core/tessagon_discovery.py new file mode 100644 index 000000000..a8bbadf70 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/tessagon_discovery.py @@ -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') diff --git a/add_mesh_extra_objects/tessagon/core/tessagon_metadata.py b/add_mesh_extra_objects/tessagon/core/tessagon_metadata.py new file mode 100644 index 000000000..8c0afd4e6 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/tessagon_metadata.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/core/tile.py b/add_mesh_extra_objects/tessagon/core/tile.py new file mode 100644 index 000000000..d638fefd5 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/tile.py @@ -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) diff --git a/add_mesh_extra_objects/tessagon/core/tile_generator.py b/add_mesh_extra_objects/tessagon/core/tile_generator.py new file mode 100644 index 000000000..e1600f305 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/tile_generator.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/core/tile_utils.py b/add_mesh_extra_objects/tessagon/core/tile_utils.py new file mode 100644 index 000000000..57c85387f --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/tile_utils.py @@ -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] diff --git a/add_mesh_extra_objects/tessagon/core/value_blend.py b/add_mesh_extra_objects/tessagon/core/value_blend.py new file mode 100644 index 000000000..74be24ff4 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/core/value_blend.py @@ -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) diff --git a/add_mesh_extra_objects/tessagon/misc/__init__.py b/add_mesh_extra_objects/tessagon/misc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/add_mesh_extra_objects/tessagon/misc/shapes.py b/add_mesh_extra_objects/tessagon/misc/shapes.py new file mode 100644 index 000000000..52cdb24c8 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/misc/shapes.py @@ -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) diff --git a/add_mesh_extra_objects/tessagon/types/__init__.py b/add_mesh_extra_objects/tessagon/types/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/add_mesh_extra_objects/tessagon/types/big_hex_tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/big_hex_tri_tessagon.py new file mode 100644 index 000000000..62f0a156c --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/big_hex_tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/brick_tessagon.py b/add_mesh_extra_objects/tessagon/types/brick_tessagon.py new file mode 100644 index 000000000..511dd96ae --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/brick_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/cloverdale_tessagon.py b/add_mesh_extra_objects/tessagon/types/cloverdale_tessagon.py new file mode 100644 index 000000000..ffdc3ddf7 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/cloverdale_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/dissected_hex_quad_tessagon.py b/add_mesh_extra_objects/tessagon/types/dissected_hex_quad_tessagon.py new file mode 100644 index 000000000..447f96110 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/dissected_hex_quad_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/dissected_hex_tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/dissected_hex_tri_tessagon.py new file mode 100644 index 000000000..1b994c05e --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/dissected_hex_tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/dissected_square_tessagon.py b/add_mesh_extra_objects/tessagon/types/dissected_square_tessagon.py new file mode 100644 index 000000000..952c8d605 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/dissected_square_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/dissected_triangle_tessagon.py b/add_mesh_extra_objects/tessagon/types/dissected_triangle_tessagon.py new file mode 100644 index 000000000..34e197efb --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/dissected_triangle_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/dodeca_tessagon.py b/add_mesh_extra_objects/tessagon/types/dodeca_tessagon.py new file mode 100644 index 000000000..2067d87c6 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/dodeca_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/dodeca_tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/dodeca_tri_tessagon.py new file mode 100644 index 000000000..ec8f29ee4 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/dodeca_tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/floret_tessagon.py b/add_mesh_extra_objects/tessagon/types/floret_tessagon.py new file mode 100644 index 000000000..ef3a2caab --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/floret_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/hex_big_tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/hex_big_tri_tessagon.py new file mode 100644 index 000000000..9894469ca --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/hex_big_tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/hex_square_tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/hex_square_tri_tessagon.py new file mode 100644 index 000000000..1438d4184 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/hex_square_tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/hex_tessagon.py b/add_mesh_extra_objects/tessagon/types/hex_tessagon.py new file mode 100644 index 000000000..b5538d932 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/hex_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/hex_tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/hex_tri_tessagon.py new file mode 100644 index 000000000..4c93ebabd --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/hex_tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/islamic_hex_stars_tessagon.py b/add_mesh_extra_objects/tessagon/types/islamic_hex_stars_tessagon.py new file mode 100644 index 000000000..b073ec929 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/islamic_hex_stars_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/islamic_stars_crosses_tessagon.py b/add_mesh_extra_objects/tessagon/types/islamic_stars_crosses_tessagon.py new file mode 100644 index 000000000..c7261d39a --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/islamic_stars_crosses_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/octo_tessagon.py b/add_mesh_extra_objects/tessagon/types/octo_tessagon.py new file mode 100644 index 000000000..0c3f9881e --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/octo_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/penta2_tessagon.py b/add_mesh_extra_objects/tessagon/types/penta2_tessagon.py new file mode 100644 index 000000000..70d40cda2 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/penta2_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/penta_tessagon.py b/add_mesh_extra_objects/tessagon/types/penta_tessagon.py new file mode 100644 index 000000000..23a5b4f11 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/penta_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/pythagorean_tessagon.py b/add_mesh_extra_objects/tessagon/types/pythagorean_tessagon.py new file mode 100644 index 000000000..b2bc537ea --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/pythagorean_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/rhombus_tessagon.py b/add_mesh_extra_objects/tessagon/types/rhombus_tessagon.py new file mode 100644 index 000000000..fae05f724 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/rhombus_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/square_tessagon.py b/add_mesh_extra_objects/tessagon/types/square_tessagon.py new file mode 100644 index 000000000..300f94aa6 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/square_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/square_tri2_tessagon.py b/add_mesh_extra_objects/tessagon/types/square_tri2_tessagon.py new file mode 100644 index 000000000..0453d9eeb --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/square_tri2_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/square_tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/square_tri_tessagon.py new file mode 100644 index 000000000..f79b3780c --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/square_tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/stanley_park_tessagon.py b/add_mesh_extra_objects/tessagon/types/stanley_park_tessagon.py new file mode 100644 index 000000000..2da6eaad6 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/stanley_park_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/tri_tessagon.py b/add_mesh_extra_objects/tessagon/types/tri_tessagon.py new file mode 100644 index 000000000..086a1f7d0 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/tri_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/valemount_tessagon.py b/add_mesh_extra_objects/tessagon/types/valemount_tessagon.py new file mode 100644 index 000000000..40e74d036 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/valemount_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/weave_tessagon.py b/add_mesh_extra_objects/tessagon/types/weave_tessagon.py new file mode 100644 index 000000000..5e45ca43b --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/weave_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/types/zig_zag_tessagon.py b/add_mesh_extra_objects/tessagon/types/zig_zag_tessagon.py new file mode 100644 index 000000000..999e1e589 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/types/zig_zag_tessagon.py @@ -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 diff --git a/add_mesh_extra_objects/tessagon/version.py b/add_mesh_extra_objects/tessagon/version.py new file mode 100644 index 000000000..376df7ca5 --- /dev/null +++ b/add_mesh_extra_objects/tessagon/version.py @@ -0,0 +1 @@ +__version__ = '0.8' -- 2.30.2