1# Purpose: menger sponge addon for ezdxf 2# Created: 06.12.2016 3# Copyright (c) 2016-2020 Manfred Moitzi 4# License: MIT License 5from typing import TYPE_CHECKING, Iterable, List, Tuple 6from ezdxf.math import Vec3 7from ezdxf.render.mesh import MeshVertexMerger, MeshTransformer 8 9if TYPE_CHECKING: 10 from ezdxf.eztypes import Vertex, GenericLayoutType, Matrix44, UCS 11 12all_cubes_size_3_template = [ 13 (0, 0, 0), (1, 0, 0), (2, 0, 0), (0, 1, 0), (1, 1, 0), (2, 1, 0), (0, 2, 0), (1, 2, 0), (2, 2, 0), 14 (0, 0, 1), (1, 0, 1), (2, 0, 1), (0, 1, 1), (1, 1, 1), (2, 1, 1), (0, 2, 1), (1, 2, 1), (2, 2, 1), 15 (0, 0, 2), (1, 0, 2), (2, 0, 2), (0, 1, 2), (1, 1, 2), (2, 1, 2), (0, 2, 2), (1, 2, 2), (2, 2, 2), 16] 17 18original_menger_cubes = [ 19 (0, 0, 0), (1, 0, 0), (2, 0, 0), (0, 1, 0), (2, 1, 0), (0, 2, 0), (1, 2, 0), (2, 2, 0), 20 (0, 0, 1), (2, 0, 1), (0, 2, 1), (2, 2, 1), 21 (0, 0, 2), (1, 0, 2), (2, 0, 2), (0, 1, 2), (2, 1, 2), (0, 2, 2), (1, 2, 2), (2, 2, 2), 22] 23 24menger_v1 = [ 25 (0, 0, 0), (2, 0, 0), (1, 1, 0), (0, 2, 0), (2, 2, 0), 26 (1, 0, 1), (0, 1, 1), (2, 1, 1), (1, 2, 1), 27 (0, 0, 2), (2, 0, 2), (1, 1, 2), (0, 2, 2), (2, 2, 2), 28] 29 30menger_v2 = [ 31 (1, 0, 0), (0, 1, 0), (2, 1, 0), (1, 2, 0), 32 (0, 0, 1), (2, 0, 1), (1, 1, 1), (0, 2, 1), (2, 2, 1), 33 (1, 0, 2), (0, 1, 2), (2, 1, 2), (1, 2, 2), 34] 35 36jerusalem_cube = [ 37 (0, 0, 0), (1, 0, 0), (2, 0, 0), (3, 0, 0), (4, 0, 0), (0, 1, 0), (1, 1, 0), (3, 1, 0), (4, 1, 0), (0, 2, 0), 38 (4, 2, 0), (0, 3, 0), (1, 3, 0), (3, 3, 0), (4, 3, 0), (0, 4, 0), (1, 4, 0), (2, 4, 0), (3, 4, 0), (4, 4, 0), 39 (0, 0, 1), (1, 0, 1), (3, 0, 1), (4, 0, 1), (0, 1, 1), (1, 1, 1), (3, 1, 1), (4, 1, 1), (0, 3, 1), (1, 3, 1), 40 (3, 3, 1), (4, 3, 1), (0, 4, 1), (1, 4, 1), (3, 4, 1), (4, 4, 1), (0, 0, 2), (4, 0, 2), (0, 4, 2), (4, 4, 2), 41 (0, 0, 3), (1, 0, 3), (3, 0, 3), (4, 0, 3), (0, 1, 3), (1, 1, 3), (3, 1, 3), (4, 1, 3), (0, 3, 3), (1, 3, 3), 42 (3, 3, 3), (4, 3, 3), (0, 4, 3), (1, 4, 3), (3, 4, 3), (4, 4, 3), (0, 0, 4), (1, 0, 4), (2, 0, 4), (3, 0, 4), 43 (4, 0, 4), (0, 1, 4), (1, 1, 4), (3, 1, 4), (4, 1, 4), (0, 2, 4), (4, 2, 4), (0, 3, 4), (1, 3, 4), (3, 3, 4), 44 (4, 3, 4), (0, 4, 4), (1, 4, 4), (2, 4, 4), (3, 4, 4), (4, 4, 4), 45] 46 47building_schemas = [ 48 original_menger_cubes, 49 menger_v1, 50 menger_v2, 51 jerusalem_cube, 52] 53 54# subdivide level in order of building_schemas 55cube_sizes = [3., 3., 3., 5.] 56 57# 8 corner vertices 58_cube_vertices = [ 59 (0, 0, 0), 60 (1, 0, 0), 61 (1, 1, 0), 62 (0, 1, 0), 63 (0, 0, 1), 64 (1, 0, 1), 65 (1, 1, 1), 66 (0, 1, 1), 67] 68 69# 6 cube faces 70cube_faces = [ 71 [0, 3, 2, 1], 72 [4, 5, 6, 7], 73 [0, 1, 5, 4], 74 [1, 2, 6, 5], 75 [3, 7, 6, 2], 76 [0, 4, 7, 3], 77] 78 79 80class MengerSponge: 81 """ 82 83 Args: 84 location: location of lower left corner as (x, y, z) tuple 85 length: side length 86 level: subdivide level 87 kind: type of menger sponge 88 89 === =========================== 90 0 Original Menger Sponge 91 1 Variant XOX 92 2 Variant OXO 93 3 Jerusalem Cube 94 === =========================== 95 96 """ 97 98 def __init__(self, location: 'Vertex' = (0., 0., 0.), length: float = 1., level: int = 1, kind: int = 0): 99 self.cube_definitions = _menger_sponge(location=location, length=length, level=level, kind=kind) 100 101 def vertices(self) -> Iterable['Vertex']: 102 """ 103 Yields the cube vertices as list of (x, y, z) tuples. 104 105 """ 106 for location, length in self.cube_definitions: 107 x, y, z = location 108 yield [Vec3(x + xf * length, y + yf * length, z + zf * length) for xf, yf, zf in _cube_vertices] 109 110 __iter__ = vertices 111 112 @staticmethod 113 def faces() -> List[List[int]]: 114 """ 115 Returns list of cube faces. All cube vertices have the same order, so one faces list fits them all. 116 117 """ 118 return cube_faces 119 120 def render(self, layout: 'GenericLayoutType', merge: bool = False, dxfattribs: dict = None, 121 matrix: 'Matrix44' = None, ucs: 'UCS' = None) -> None: 122 """ 123 Renders the menger sponge into layout, set `merge` to ``True`` for rendering the whole menger sponge into 124 one MESH entity, set `merge` to ``False`` for rendering the individual cubes of the menger sponge as 125 MESH entities. 126 127 Args: 128 layout: DXF target layout 129 merge: ``True`` for one MESH entity, ``False`` for individual MESH entities per cube 130 dxfattribs: DXF attributes for the MESH entities 131 matrix: apply transformation matrix at rendering 132 ucs: apply UCS transformation at rendering 133 134 """ 135 if merge: 136 mesh = self.mesh() 137 mesh.render_mesh(layout, dxfattribs=dxfattribs, matrix=matrix, ucs=ucs) 138 else: 139 for cube in self.cubes(): 140 cube.render_mesh(layout, dxfattribs, matrix=matrix, ucs=ucs) 141 142 def cubes(self) -> Iterable[MeshTransformer]: 143 """ Yields all cubes of the menger sponge as individual :class:`MeshTransformer` objects. 144 """ 145 faces = self.faces() 146 for vertices in self: 147 mesh = MeshVertexMerger() 148 mesh.add_mesh(vertices=vertices, faces=faces) 149 yield MeshTransformer.from_builder(mesh) 150 151 def mesh(self) -> MeshTransformer: 152 """ Returns geometry as one :class:`MeshTransformer` object. 153 """ 154 faces = self.faces() 155 mesh = MeshVertexMerger() 156 for vertices in self: 157 mesh.add_mesh(vertices=vertices, faces=faces) 158 return MeshTransformer.from_builder(mesh) 159 160 161def _subdivide(location: 'Vertex' = (0., 0., 0.), length: float = 1., kind: int = 0) -> List[Tuple['Vertex', float]]: 162 """ 163 Divides a cube in sub-cubes and keeps only cubes determined by the building schema. 164 165 All sides are parallel to x-, y- and z-axis, location is a (x, y, z) tuple and represents the coordinates of the 166 lower left corner (nearest to the axis origin) of the cube, length is the side-length of the cube 167 168 Args: 169 location: (x, y, z) tuple, coordinates of the lower left corner of the cube 170 length: side length of the cube 171 kind: int for 0: original menger sponge; 1: Variant XOX; 2: Variant OXO; 3: Jerusalem Cube; 172 173 Returns: list of sub-cubes (location, length) 174 175 """ 176 177 init_x, init_y, init_z = location 178 step_size = float(length) / cube_sizes[kind] 179 remaining_cubes = building_schemas[kind] 180 181 def sub_location(indices) -> Vec3: 182 x, y, z = indices 183 return Vec3( 184 init_x + x * step_size, 185 init_y + y * step_size, 186 init_z + z * step_size, 187 ) 188 189 return [(sub_location(indices), step_size) for indices in remaining_cubes] 190 191 192def _menger_sponge(location: 'Vertex' = (0., 0., 0.), length: float = 1., level: int = 1, kind: int = 0) -> List[ 193 Tuple[Vec3, float]]: 194 """ 195 Builds a menger sponge for given level. 196 197 Args: 198 location: (x, y, z) tuple, coordinates of the lower left corner of the cube 199 length: side length of the cube 200 level: level of menger sponge, has to be 1 or bigger 201 kind: int for 0: original menger sponge; 1: Variant XOX; 2: Variant OXO; 3: Jerusalem Cube; 202 203 Returns: list of sub-cubes (location, length) 204 205 """ 206 kind = int(kind) 207 if kind not in (0, 1, 2, 3): 208 raise ValueError('kind has to be 0, 1, 2 or 3.') 209 level = int(level) 210 if level < 1: 211 raise ValueError("level has to be 1 or bigger.") 212 cubes = _subdivide(location, length, kind=kind) 213 for _ in range(level - 1): 214 next_level_cubes = [] 215 for location, length in cubes: 216 next_level_cubes.extend(_subdivide(location, length, kind=kind)) 217 cubes = next_level_cubes 218 return cubes 219