polycubetools 1.1.0__py3-none-any.whl → 1.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polycubetools/__init__.py +2 -1
- polycubetools/errors.py +18 -0
- polycubetools/hull.py +56 -1
- polycubetools/utils.py +29 -13
- {polycubetools-1.1.0.dist-info → polycubetools-1.1.2.dist-info}/METADATA +1 -1
- polycubetools-1.1.2.dist-info/RECORD +16 -0
- {polycubetools-1.1.0.dist-info → polycubetools-1.1.2.dist-info}/WHEEL +1 -1
- polycubetools-1.1.0.dist-info/RECORD +0 -15
- {polycubetools-1.1.0.dist-info → polycubetools-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {polycubetools-1.1.0.dist-info → polycubetools-1.1.2.dist-info}/top_level.txt +0 -0
polycubetools/__init__.py
CHANGED
|
@@ -10,9 +10,10 @@ Basic framework for working with polycubes in a 3-dimensional grid.
|
|
|
10
10
|
__author__ = "Team Polycube"
|
|
11
11
|
__title__ = "polycubetools"
|
|
12
12
|
__license__ = "MIT"
|
|
13
|
-
__version__ = "1.1.
|
|
13
|
+
__version__ = "1.1.2"
|
|
14
14
|
|
|
15
15
|
from . import utils as utils
|
|
16
|
+
from .errors import *
|
|
16
17
|
from .grid import *
|
|
17
18
|
from .hull import *
|
|
18
19
|
from .models import *
|
polycubetools/errors.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
__all__ = (
|
|
4
|
+
"InvalidVolumeException",
|
|
5
|
+
"PolycubeException",
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PolycubeException(Exception):
|
|
10
|
+
"""Base exception for all errors raised by this library."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InvalidVolumeException(PolycubeException):
|
|
16
|
+
"""Exception raised when failing to compute a volume."""
|
|
17
|
+
|
|
18
|
+
pass
|
polycubetools/hull.py
CHANGED
|
@@ -35,6 +35,11 @@ class AbstractHull(ABC):
|
|
|
35
35
|
"""Checks if the given coordinate is inside the hull."""
|
|
36
36
|
pass
|
|
37
37
|
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def inside_hull(self, pos: Coordinate) -> bool:
|
|
40
|
+
"""Checks if the given coordinate is inside the hull or in its inner part."""
|
|
41
|
+
pass
|
|
42
|
+
|
|
38
43
|
@abstractmethod
|
|
39
44
|
def in_inner_part(self, pos: Coordinate) -> bool:
|
|
40
45
|
"""Checks if the given coordinate is inside the inner part of the hull."""
|
|
@@ -67,6 +72,9 @@ class CoordinateHull(AbstractHull):
|
|
|
67
72
|
def in_hull(self, pos: Coordinate) -> bool:
|
|
68
73
|
return pos in self.coords
|
|
69
74
|
|
|
75
|
+
def inside_hull(self, pos: Coordinate) -> bool:
|
|
76
|
+
return pos in self.coords or self.in_inner_part(pos)
|
|
77
|
+
|
|
70
78
|
def in_inner_part(self, pos: Coordinate) -> bool:
|
|
71
79
|
raise NotImplementedError(
|
|
72
80
|
"Inner part is not defined for coordinate hulls (you can implement it in a subclass if needed)"
|
|
@@ -113,9 +121,56 @@ class CuboidHull(AbstractHull):
|
|
|
113
121
|
def in_hull(self, pos: Coordinate) -> bool:
|
|
114
122
|
minc, maxc = self.min_coord, self.max_coord
|
|
115
123
|
px, py, pz = pos.x, pos.y, pos.z
|
|
116
|
-
return
|
|
124
|
+
return (
|
|
125
|
+
px == minc.x or px == maxc.x or py == minc.y or py == maxc.y or pz == minc.z or pz == maxc.z
|
|
126
|
+
) and self.inside_hull(pos)
|
|
127
|
+
|
|
128
|
+
def inside_hull(self, pos: Coordinate) -> bool:
|
|
129
|
+
minc, maxc = self.min_coord, self.max_coord
|
|
130
|
+
px, py, pz = pos.x, pos.y, pos.z
|
|
131
|
+
return minc.x <= px <= maxc.x and minc.y <= py <= maxc.y and minc.z <= pz <= maxc.z
|
|
117
132
|
|
|
118
133
|
def in_inner_part(self, pos: Coordinate) -> bool:
|
|
119
134
|
minc, maxc = self.min_coord, self.max_coord
|
|
120
135
|
px, py, pz = pos.x, pos.y, pos.z
|
|
121
136
|
return minc.x < px < maxc.x and minc.y < py < maxc.y and minc.z < pz < maxc.z
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class CoordinateSetHull(AbstractHull):
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
coords: frozenset[Coordinate],
|
|
143
|
+
inner_coords: frozenset[Coordinate],
|
|
144
|
+
frontier: set[Coordinate] | None = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
super().__init__()
|
|
147
|
+
self.coords = coords
|
|
148
|
+
self.inner_coords = inner_coords
|
|
149
|
+
if frontier is None:
|
|
150
|
+
self.frontier = set(coords)
|
|
151
|
+
else:
|
|
152
|
+
self.frontier = frontier
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def from_snapshot(cls, snapshot: SNAPSHOT) -> Self:
|
|
156
|
+
coords = frozenset(Coordinate.from_tuple(t) for t in snapshot["coords"])
|
|
157
|
+
frontier = {Coordinate.from_tuple(t) for t in snapshot["frontier"]}
|
|
158
|
+
inner_coords = frozenset(Coordinate.from_tuple(t) for t in snapshot["inner_coords"])
|
|
159
|
+
|
|
160
|
+
return cls(coords, inner_coords, frontier)
|
|
161
|
+
|
|
162
|
+
def to_snapshot(self) -> SNAPSHOT:
|
|
163
|
+
return {
|
|
164
|
+
"coords": tuple(c.to_tuple() for c in self.coords),
|
|
165
|
+
"frontier": tuple(c.to_tuple() for c in self.frontier),
|
|
166
|
+
"inner_coords": tuple(c.to_tuple() for c in self.inner_coords),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
def in_hull(self, pos: Coordinate) -> bool:
|
|
170
|
+
return pos in self.coords
|
|
171
|
+
|
|
172
|
+
def inside_hull(self, pos: Coordinate) -> bool:
|
|
173
|
+
return pos in self.coords or self.in_inner_part(pos)
|
|
174
|
+
|
|
175
|
+
def in_inner_part(self, pos: Coordinate) -> bool:
|
|
176
|
+
return pos in self.inner_coords
|
polycubetools/utils.py
CHANGED
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
import logging
|
|
6
6
|
from typing import Any, TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from .errors import InvalidVolumeException
|
|
8
9
|
from .models import TWENTY_SIX_NEIGHBORHOOD_DIRECTIONS, Polycube, RotatedPolycube, Coordinate
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
@@ -12,12 +13,10 @@ if TYPE_CHECKING:
|
|
|
12
13
|
|
|
13
14
|
_logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
15
|
-
NO_VOLUME = -1
|
|
16
|
-
MULTIPLE_VOLUME_COMPONENTS = -2
|
|
17
|
-
ERROR_IN_ALGORITHM = -3
|
|
18
|
-
|
|
19
16
|
__all__ = (
|
|
17
|
+
"collect_volume",
|
|
20
18
|
"compute_volume",
|
|
19
|
+
"find_connected_component",
|
|
21
20
|
"get_extreme_points",
|
|
22
21
|
"is_valid_fence",
|
|
23
22
|
"load_polycubes",
|
|
@@ -49,7 +48,7 @@ def _is_border_coordinate(coord: Coordinate, max_extrem_point: Coordinate, min_e
|
|
|
49
48
|
)
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
def
|
|
51
|
+
def find_connected_component(visible_coords: set[Coordinate]) -> set[Coordinate]:
|
|
53
52
|
"""Finds all coordinates connected to the first coordinate in the set using 26-neighborhood."""
|
|
54
53
|
start = visible_coords.pop()
|
|
55
54
|
stack = [start]
|
|
@@ -86,7 +85,7 @@ def get_extreme_points(coords: set[Coordinate]) -> tuple[Coordinate, Coordinate]
|
|
|
86
85
|
return Coordinate(x_max, y_max, z_max), Coordinate(x_min, y_min, z_min)
|
|
87
86
|
|
|
88
87
|
|
|
89
|
-
def
|
|
88
|
+
def collect_volume(coords: set[Coordinate]) -> set[Coordinate]:
|
|
90
89
|
"""Compute the volume from the hull formed by the coordinates"""
|
|
91
90
|
max_extreme_point, min_extreme_point = get_extreme_points(coords)
|
|
92
91
|
max_extreme_point = Coordinate(max_extreme_point.x + 1, max_extreme_point.y + 1, max_extreme_point.z + 1)
|
|
@@ -101,24 +100,41 @@ def compute_volume(coords: set[Coordinate]) -> int:
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
# the here popped coordinate is always outside the hull; this is because we added this outside new layer!
|
|
104
|
-
|
|
103
|
+
first_component = find_connected_component(visible_coords)
|
|
105
104
|
|
|
106
|
-
# we return false because there is no second component, meaning the shape has no volume
|
|
107
105
|
if not visible_coords:
|
|
108
106
|
_logger.warning("No volume found! Only one component detected.")
|
|
109
|
-
return
|
|
107
|
+
return set()
|
|
108
|
+
|
|
109
|
+
second_component = find_connected_component(visible_coords)
|
|
110
|
+
|
|
111
|
+
if min_extreme_point in second_component and max_extreme_point in second_component:
|
|
112
|
+
volume_cubes = first_component
|
|
113
|
+
elif min_extreme_point in first_component and max_extreme_point in first_component:
|
|
114
|
+
volume_cubes = second_component
|
|
115
|
+
else:
|
|
116
|
+
_logger.warning("Error. Couldn't find one outside component")
|
|
117
|
+
raise InvalidVolumeException("Couldn't find one outside component")
|
|
110
118
|
|
|
111
|
-
volume_cubes = _find_connected_component(visible_coords)
|
|
112
119
|
if visible_coords:
|
|
113
120
|
_logger.warning(
|
|
114
|
-
f"Found {len(volume_cubes)} as volume, but still {len(visible_coords)} unvisited cubes left.
|
|
121
|
+
f"Found {len(volume_cubes)} as volume, but still {len(visible_coords)} unvisited cubes left. Another enclosed area exists!"
|
|
115
122
|
)
|
|
116
|
-
|
|
123
|
+
raise InvalidVolumeException("Volume has multiple components")
|
|
117
124
|
|
|
118
125
|
if any(_is_border_coordinate(c, max_extreme_point, min_extreme_point) for c in volume_cubes):
|
|
119
126
|
_logger.warning("Volume has been found on the border! Bug in the validation.")
|
|
120
|
-
|
|
127
|
+
raise InvalidVolumeException("Volume has been found on the border")
|
|
121
128
|
|
|
129
|
+
return volume_cubes
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def compute_volume(coords: set[Coordinate]) -> int:
|
|
133
|
+
"""
|
|
134
|
+
Ruft die Funktion collect_volume auf, um alle Punkte der Volumen-Komponente zu erhalten.
|
|
135
|
+
Die Anzahl dieser Punkte ist das Volumen.
|
|
136
|
+
"""
|
|
137
|
+
volume_cubes = collect_volume(coords)
|
|
122
138
|
return len(volume_cubes)
|
|
123
139
|
|
|
124
140
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
polycubetools/__init__.py,sha256=r957_jZL81olgIrGgMPRZhC2_a8NMOmlAJOyKF7p39Y,394
|
|
2
|
+
polycubetools/errors.py,sha256=dIb032TJFwQJkZL3HOjPbDL8Qz2l9oAsunQzCz8TOe4,339
|
|
3
|
+
polycubetools/grid.py,sha256=UbUXxQ5opEAPKlPmbv7KIorUnRw41cuK7mkVoLDhqLE,7540
|
|
4
|
+
polycubetools/hull.py,sha256=FK9E9LV3SXcNI2EYAT4-MWeDNsyUue3VtSpehibl4EM,6212
|
|
5
|
+
polycubetools/models.py,sha256=5qi9WWOxYg8666x1LOkfyQoDSLf4Qzs3Z94-8N_zRLU,5128
|
|
6
|
+
polycubetools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
polycubetools/solver.py,sha256=X_6dXQ9WBm4CRHXILvs2whXbxYjW-ZeChBczosKJ7vM,5918
|
|
8
|
+
polycubetools/utils.py,sha256=P2pVBDfMQhbYcn4zWRzJmSNPp4opkOTBC9fm3RRtAlk,7647
|
|
9
|
+
polycubetools/data/heptacubes.json,sha256=vQ9QA3J5vOtjqw9wFYYDLLhmw7oMmOYzNdmTRiMga20,15393824
|
|
10
|
+
polycubetools/data/hexacubes.json,sha256=klfA_tcan_J0CFsOaQdmZgerbVIWDEN1yjW2tq31kd8,1986336
|
|
11
|
+
polycubetools/data/pentacubes.json,sha256=U3lpdByY9jPlDQRcuOUxS3xQBGpPwh9MNpEeSYTq7KE,259662
|
|
12
|
+
polycubetools-1.1.2.dist-info/licenses/LICENSE,sha256=dhlyH2n5C_8e4NuZqQk-CKpYSg8BaCHKep2mPLBZHiQ,1064
|
|
13
|
+
polycubetools-1.1.2.dist-info/METADATA,sha256=krGSfDBo88VmCFn-nArtxDm6k_c_e2i08pCsiIZFb1Q,1476
|
|
14
|
+
polycubetools-1.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
+
polycubetools-1.1.2.dist-info/top_level.txt,sha256=3pdiwnGYfuq3VSO8Y5L2wJYyU2U87PrfH5BBcBZjeKI,14
|
|
16
|
+
polycubetools-1.1.2.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
polycubetools/__init__.py,sha256=nvdLE5imasBZJQvtr79OrJ7eCPAVSirOWbGIkin96qM,372
|
|
2
|
-
polycubetools/grid.py,sha256=UbUXxQ5opEAPKlPmbv7KIorUnRw41cuK7mkVoLDhqLE,7540
|
|
3
|
-
polycubetools/hull.py,sha256=tM6pipw05fAWJ3N0KXgzCpXnZbQsRljPUXAinNXOd2w,4257
|
|
4
|
-
polycubetools/models.py,sha256=5qi9WWOxYg8666x1LOkfyQoDSLf4Qzs3Z94-8N_zRLU,5128
|
|
5
|
-
polycubetools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
polycubetools/solver.py,sha256=X_6dXQ9WBm4CRHXILvs2whXbxYjW-ZeChBczosKJ7vM,5918
|
|
7
|
-
polycubetools/utils.py,sha256=80gA7bVXO5hxLuHNbsfC_s9Wz7shBubMWyKVm52Taw8,6928
|
|
8
|
-
polycubetools/data/heptacubes.json,sha256=vQ9QA3J5vOtjqw9wFYYDLLhmw7oMmOYzNdmTRiMga20,15393824
|
|
9
|
-
polycubetools/data/hexacubes.json,sha256=klfA_tcan_J0CFsOaQdmZgerbVIWDEN1yjW2tq31kd8,1986336
|
|
10
|
-
polycubetools/data/pentacubes.json,sha256=U3lpdByY9jPlDQRcuOUxS3xQBGpPwh9MNpEeSYTq7KE,259662
|
|
11
|
-
polycubetools-1.1.0.dist-info/licenses/LICENSE,sha256=dhlyH2n5C_8e4NuZqQk-CKpYSg8BaCHKep2mPLBZHiQ,1064
|
|
12
|
-
polycubetools-1.1.0.dist-info/METADATA,sha256=Pwsw00NgRNvTq-YNjAA0KC-HhkmWMG3vQ7NBhb14qtQ,1476
|
|
13
|
-
polycubetools-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
polycubetools-1.1.0.dist-info/top_level.txt,sha256=3pdiwnGYfuq3VSO8Y5L2wJYyU2U87PrfH5BBcBZjeKI,14
|
|
15
|
-
polycubetools-1.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|