Source code for tqec.sketchup.geometry

from __future__ import annotations

import typing as ty
from dataclasses import dataclass
from enum import Enum

import numpy as np
import numpy.typing as npt

from tqec.position import Direction3D
from tqec.sketchup.block_graph import BlockType, CubeType, PipeType


[docs] class FaceType(Enum): X = "X" Z = "Z" H = "H" @staticmethod def from_string(face_type: str) -> FaceType: return FaceType(face_type.upper()) def opposite(self) -> FaceType: if self == FaceType.X: return FaceType.Z if self == FaceType.Z: return FaceType.X return FaceType.H
[docs] @dataclass(frozen=True) class Face: """A rectangle face in the 3d space. (axis_width, axis_height, axis_normal) is by the right-hand rule, which only has 3 possible values: (X, Y, Z) | (Y, Z, X) | (Z, X, Y). Attributes: face_type: The type of the face. width: The width of the face. height: The height of the face. normal_direction: The normal direction of the face, which is the direction of the axis that the face is perpendicular to. positive_facing: Whether the normal direction is towards the positive direction of the axis. translation: The position translation of the face from the origin. """ face_type: FaceType width: float height: float normal_direction: Direction3D positive_facing: bool = True translation: ty.Tuple[float, float, float] = (0, 0, 0) @staticmethod def get_triangle_indices() -> npt.NDArray[np.int_]: return np.array([0, 0, 2, 2, 1, 1, 2, 2, 0, 0, 3, 3], dtype=np.int_) def get_vertices(self) -> npt.NDArray[np.float_]: ax3_index = self.normal_direction.axis_index ax1_index = (ax3_index + 1) % 3 ax2_index = (ax3_index + 2) % 3 # rectangle vertices vertices_position = np.array( [ [0, 0, 0], [self.width, 0, 0], [self.width, self.height, 0], [0, self.height, 0], ], dtype=np.float_, ) # permute the axes permutation = [0, 1, 2] permutation[ax3_index] = 2 permutation[ax1_index] = 0 permutation[ax2_index] = 1 vertices_position = vertices_position[:, permutation] # translate the rectangle vertices_position += np.asarray(self.translation, dtype=np.float_) return vertices_position.flatten() def get_normal_vectors(self) -> npt.NDArray[np.float_]: normal = np.zeros(3, dtype=np.float_) ax3_index = self.normal_direction.axis_index normal[ax3_index] = 1 if self.positive_facing else -1 return np.tile(normal, 4) def translated_by(self, dx: float, dy: float, dz: float) -> Face: return Face( self.face_type, self.width, self.height, self.normal_direction, self.positive_facing, ( self.translation[0] + dx, self.translation[1] + dy, self.translation[2] + dz, ), ) def with_opposite_facing(self) -> Face: return Face( self.face_type, self.width, self.height, self.normal_direction, not self.positive_facing, self.translation, )
[docs] def parse_block_type_from_str(block_type: str) -> BlockType: """Parse a block type from a string.""" if "o" in block_type: return PipeType(block_type.lower()) else: return CubeType(block_type.lower())
Geometry = dict[BlockType, list[Face]] def _create_cube_geometries() -> Geometry: """Create zxx, xzx, xxz, xzz, zxz, zzx cube geometries.""" cube_geometries = {} width, height = 1, 1 for name in ["zxx", "xzx", "xxz", "xzz", "zxz", "zzx"]: faces = [] for i, face_type in enumerate(name): normal_direction = Direction3D.from_axis_index(i) face = Face( FaceType.from_string(face_type), width, height, normal_direction, False ) faces.append(face) translation = [0, 0, 0] translation[i] = 1 faces.append(face.translated_by(*translation).with_opposite_facing()) cube_geometries[parse_block_type_from_str(name)] = faces return cube_geometries def _create_no_h_pipe_geometries() -> Geometry: """Create ozx, oxz, xoz, zox, xzo, zxo pipe geometries.""" pipe_geometries = {} for name in ["ozx", "oxz", "xoz", "zox", "xzo", "zxo"]: faces = [] pipe_direction_index = name.index("o") for i, face_type in enumerate(name): if face_type == "o": continue if i == (pipe_direction_index - 1) % 3: width, height = 2, 1 else: width, height = 1, 2 normal_direction = Direction3D.from_axis_index(i) face = Face( FaceType.from_string(face_type), width, height, normal_direction, False ) faces.append(face) translation = [0, 0, 0] translation[i] = 1 faces.append(face.translated_by(*translation).with_opposite_facing()) pipe_geometries[parse_block_type_from_str(name)] = faces return pipe_geometries def _get_3d_translation( pipe_direction_index: int, value: float ) -> tuple[float, float, float]: assert 0 <= pipe_direction_index <= 2 tmp: list[float] = [0, 0, 0] tmp[pipe_direction_index] = value return (tmp[0], tmp[1], tmp[2]) def _create_h_pipe_geometries() -> Geometry: """Create ozxh, oxzh, zoxh, xozh, zxoh, xzoh pipe geometries.""" pipe_geometries = {} for name in ["ozxh", "oxzh", "zoxh", "xozh", "zxoh", "xzoh"]: faces: list[Face] = [] pipe_direction_index = name.index("o") for i, face_type in enumerate(name[:-1]): if face_type == "o": continue if i == (pipe_direction_index - 1) % 3: w1, h1 = 0.9, 1.0 w2, h2 = 0.2, 1.0 w3, h3 = 0.9, 1.0 else: w1, h1 = 1.0, 0.9 w2, h2 = 1.0, 0.2 w3, h3 = 1.0, 0.9 normal_direction = Direction3D.from_axis_index(i) face1 = Face( FaceType.from_string(face_type), w1, h1, normal_direction, False ) face2_translation = _get_3d_translation(pipe_direction_index, 0.9) face2 = Face(FaceType.H, w2, h2, normal_direction, False, face2_translation) face3_translation = _get_3d_translation(pipe_direction_index, 1.1) face3 = Face( FaceType.from_string(face_type).opposite(), w3, h3, normal_direction, False, face3_translation, ) faces.extend([face1, face2, face3]) translation = [0, 0, 0] translation[i] = 1 faces.extend( face.translated_by(*translation).with_opposite_facing() for face in [face1, face2, face3] ) pipe_geometries[parse_block_type_from_str(name)] = faces return pipe_geometries
[docs] def load_library_block_geometries() -> Geometry: """Load the geometries of all the library blocks.""" geometry = {} # 6 cube blocks geometry.update(_create_cube_geometries()) # 6 pipe blocks without H geometry.update(_create_no_h_pipe_geometries()) # 6 pipe blocks with H geometry.update(_create_h_pipe_geometries()) return geometry