diff --git a/src/classy_blocks/optimize/cell.py b/src/classy_blocks/optimize/cell.py index 7985505..45e6330 100644 --- a/src/classy_blocks/optimize/cell.py +++ b/src/classy_blocks/optimize/cell.py @@ -1,8 +1,9 @@ import abc -from typing import ClassVar, Dict, List, Optional, Set +from typing import ClassVar, Dict, List, Optional, Set, Tuple import numpy as np +from classy_blocks.optimize.connection import CellConnection from classy_blocks.types import IndexType, NPPointListType, NPPointType, OrientType from classy_blocks.util import functions as f from classy_blocks.util.constants import EDGE_PAIRS, FACE_MAP, VSMALL @@ -15,6 +16,7 @@ class NoCommonSidesError(Exception): class CellBase(abc.ABC): side_names: ClassVar[List[str]] side_indexes: ClassVar[List[IndexType]] + edge_pairs: ClassVar[List[Tuple[int, int]]] def __init__(self, grid_points: NPPointListType, indexes: IndexType): self.grid_points = grid_points @@ -28,6 +30,7 @@ def __init__(self, grid_points: NPPointListType, indexes: IndexType): "front": None, "back": None, } + self.connections = [CellConnection(set(pair), {indexes[pair[0]], indexes[pair[1]]}) for pair in self.edge_pairs] def get_common_indexes(self, candidate: "CellBase") -> Set[int]: """Returns indexes of common vertices between this and provided cell""" @@ -83,13 +86,14 @@ def center(self) -> NPPointType: return np.average(self.points, axis=0) @property - def face_points(self) -> NPPointListType: + def side_points(self) -> NPPointListType: + """In 2D, a 'side' is a line segment but in 3D it is a quadrangle""" return np.take(self.points, self.side_indexes, axis=0) @property - def face_centers(self) -> NPPointListType: + def side_centers(self) -> NPPointListType: """Center point of each face""" - return np.average(self.face_points, axis=1) + return np.average(self.side_points, axis=1) @property def quality(self) -> float: @@ -112,8 +116,8 @@ def q_scale(base, exponent, factor, value): ### non-orthogonality # angles between faces and self.center-neighbour.center or, if there's no neighbour # on this face, between face and self.center-face_center - face_points = self.face_points[i] - face_center = self.face_centers[i] + face_points = self.side_points[i] + face_center = self.side_centers[i] side_1 = face_points - face_center side_2 = np.roll(face_points, -1, axis=0) - face_center @@ -165,11 +169,10 @@ def min_length(self) -> float: class QuadCell(CellBase): - # FACE_MAP, ordered and modified so that all faces point towards cell center; - # provided their points are visited in an anti-clockwise manner - # names and indexes must correspond (both must be in the same order) + # Like constants.FACE_MAP but for quadrangle sides as line segments side_names: ClassVar = ["front", "right", "back", "left"] side_indexes: ClassVar = [[0, 1], [1, 2], [2, 3], [3, 0]] + edge_pairs: ClassVar = [(0, 1), (1, 2), (2, 3), (3, 0)] @property def min_length(self) -> float: @@ -188,6 +191,7 @@ class HexCell(CellBase): # names and indexes must correspond (both must be in the same order) side_names: ClassVar = ["bottom", "top", "left", "right", "front", "back"] side_indexes: ClassVar = [[0, 1, 2, 3], [7, 6, 5, 4], [4, 0, 3, 7], [6, 2, 1, 5], [0, 4, 5, 1], [7, 3, 2, 6]] + edge_pairs: ClassVar = EDGE_PAIRS @property def min_length(self) -> float: diff --git a/src/classy_blocks/optimize/connection.py b/src/classy_blocks/optimize/connection.py new file mode 100644 index 0000000..cafb324 --- /dev/null +++ b/src/classy_blocks/optimize/connection.py @@ -0,0 +1,11 @@ +import dataclasses +from typing import Set + + +@dataclasses.dataclass +class CellConnection: + """A connection between two points; + they are refered by indexes rather than positions""" + + corners: Set[int] # cell-local indexes + indexes: Set[int] diff --git a/src/classy_blocks/optimize/grid.py b/src/classy_blocks/optimize/grid.py index 919368d..a2bdd31 100644 --- a/src/classy_blocks/optimize/grid.py +++ b/src/classy_blocks/optimize/grid.py @@ -27,23 +27,30 @@ def __init__(self, points: NPPointListType, addressing: List[IndexType]): # new numpy arrays for every calculation self.points = points - self.cells = [self.cell_class(self.points, indexes) for indexes in addressing] self.junctions = [Junction(self.points, index) for index in range(len(self.points))] + self.cells = [self.cell_class(self.points, indexes) for indexes in addressing] - self._bind_junctions() - self._bind_neighbours() + self._bind_cell_neighbours() + self._bind_junction_cells() + self._bind_junction_connections() - def _bind_junctions(self) -> None: + def _bind_cell_neighbours(self) -> None: + """Adds neighbours to cells""" + for cell_1 in self.cells: + for cell_2 in self.cells: + cell_1.add_neighbour(cell_2) + + def _bind_junction_cells(self) -> None: """Adds cells to junctions""" for cell in self.cells: for junction in self.junctions: junction.add_cell(cell) - def _bind_neighbours(self) -> None: - """Adds neighbours to cells""" - for cell_1 in self.cells: - for cell_2 in self.cells: - cell_1.add_neighbour(cell_2) + def _bind_junction_connections(self) -> None: + """Adds connections to junctions""" + for junction_1 in self.junctions: + for junction_2 in self.junctions: + junction_1.add_connection(junction_2) def get_junction_from_clamp(self, clamp: ClampBase) -> Junction: for junction in self.junctions: diff --git a/src/classy_blocks/optimize/junction.py b/src/classy_blocks/optimize/junction.py index aa910fa..43d569d 100644 --- a/src/classy_blocks/optimize/junction.py +++ b/src/classy_blocks/optimize/junction.py @@ -13,6 +13,7 @@ class ClampExistsError(Exception): @dataclasses.dataclass class IndexedLink: + # TODO: refactor / deuglify link: LinkBase follower_index: int @@ -24,8 +25,11 @@ class Junction: def __init__(self, points: NPPointListType, index: int): self.points = points self.index = index + self.cells: Set[CellBase] = set() + self.connections: List[Junction] = [] + self.clamp: Optional[ClampBase] = None self.links: List[IndexedLink] = [] @@ -41,6 +45,25 @@ def add_cell(self, cell: CellBase) -> None: self.cells.add(cell) return + def add_connection(self, to: "Junction") -> bool: + """Returns True if this Junction is connected to passed one""" + if to == self: + return False + + # if any of connections within a cell is equal to + # the connection between these two junctions, + # they are connected + junction_indexes = {self.index, to.index} + + for cell in self.cells: + for connection in cell.connections: + if connection.indexes == junction_indexes: + if to not in self.connections: + self.connections.append(to) + return True + + return False + def add_clamp(self, clamp: ClampBase) -> None: if self.clamp is not None: raise ClampExistsError(f"Clamp already defined for junction {self.index}") diff --git a/tests/test_modify/test_grid.py b/tests/test_modify/test_grid.py index 799b717..4ff8624 100644 --- a/tests/test_modify/test_grid.py +++ b/tests/test_modify/test_grid.py @@ -27,3 +27,7 @@ def test_junction_cells(self, index, count): @parameterized.expand(((0, "right", 1), (1, "left", 0), (1, "back", 2), (2, "front", 1))) def test_cell_neighbours(self, parent, orient, neighbour): self.assertEqual(self.grid.cells[parent].neighbours[orient], self.grid.cells[neighbour]) + + @parameterized.expand(((0, 3), (1, 4), (2, 5), (3, 3))) + def test_connections(self, junction, count): + self.assertEqual(len(self.grid.junctions[junction].connections), count)