pylitematic 0.0.3__py3-none-any.whl → 0.0.5__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.
- pylitematic/__init__.py +3 -4
- pylitematic/block_palette.py +129 -0
- pylitematic/block_property.py +13 -13
- pylitematic/block_state.py +34 -12
- pylitematic/geometry.py +67 -103
- pylitematic/property_cache.py +52 -0
- pylitematic/region.py +517 -218
- pylitematic/resource_location.py +6 -10
- pylitematic/schematic.py +24 -13
- pylitematic/test.py +62 -54
- {pylitematic-0.0.3.dist-info → pylitematic-0.0.5.dist-info}/METADATA +4 -1
- pylitematic-0.0.5.dist-info/RECORD +14 -0
- pylitematic-0.0.3.dist-info/RECORD +0 -12
- {pylitematic-0.0.3.dist-info → pylitematic-0.0.5.dist-info}/WHEEL +0 -0
- {pylitematic-0.0.3.dist-info → pylitematic-0.0.5.dist-info}/top_level.txt +0 -0
pylitematic/__init__.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
__version__ = "0.0.
|
1
|
+
__version__ = "0.0.5"
|
2
2
|
|
3
|
-
from .block_state import BlockState
|
4
|
-
from .geometry import BlockPosition, Size3D
|
3
|
+
from .block_state import AIR, BlockId, BlockState
|
4
|
+
from .geometry import BlockPosition, Direction, Size3D
|
5
5
|
from .region import Region
|
6
|
-
from .resource_location import BlockId
|
7
6
|
from .schematic import Schematic
|
@@ -0,0 +1,129 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import copy
|
4
|
+
import nbtlib
|
5
|
+
import numpy as np
|
6
|
+
from typing import Iterator
|
7
|
+
|
8
|
+
from .block_state import AIR, BlockId, BlockState
|
9
|
+
|
10
|
+
|
11
|
+
class BlockPalette:
|
12
|
+
def __init__(self):
|
13
|
+
self._states: list[BlockState] = []
|
14
|
+
self._map: dict[BlockState, int] = {}
|
15
|
+
self.add_state(AIR)
|
16
|
+
|
17
|
+
def __len__(self):
|
18
|
+
return len(self._states)
|
19
|
+
|
20
|
+
def __str__(self) -> str:
|
21
|
+
return str({str(s): i for s, i in self._map.items()})
|
22
|
+
|
23
|
+
def __repr__(self) -> str:
|
24
|
+
return f"{type(self).__name__}({self._map})"
|
25
|
+
|
26
|
+
def __contains__(self, key) -> bool:
|
27
|
+
# TODO: add BlockId comparison?
|
28
|
+
if not isinstance(key, BlockState):
|
29
|
+
return NotImplemented
|
30
|
+
return key in self._map
|
31
|
+
|
32
|
+
def __eq__(self, other) -> bool | np.ndarray[bool]:
|
33
|
+
if not isinstance(other, (BlockState, BlockId)):
|
34
|
+
return NotImplemented
|
35
|
+
return np.array(self) == other
|
36
|
+
|
37
|
+
def __iter__(self) -> Iterator[tuple[BlockState, int]]:
|
38
|
+
return self.items()
|
39
|
+
|
40
|
+
def __array__(self, dtype: type | None = None, copy: bool = True):
|
41
|
+
arr = np.array(self._states, dtype=object)
|
42
|
+
return arr.copy() if copy else arr
|
43
|
+
|
44
|
+
def states(self) -> Iterator[BlockState]:
|
45
|
+
for state in self._map.keys():
|
46
|
+
yield state
|
47
|
+
|
48
|
+
def indices(self) -> Iterator[int]:
|
49
|
+
for index in self._map.values():
|
50
|
+
yield index
|
51
|
+
|
52
|
+
def items(self) -> Iterator[tuple[BlockState, int]]:
|
53
|
+
for state, index in self._map.items():
|
54
|
+
yield state, index
|
55
|
+
|
56
|
+
@property
|
57
|
+
def bits_per_state(self) -> int:
|
58
|
+
return max(2, (len(self) - 1).bit_length())
|
59
|
+
|
60
|
+
def copy(self) -> BlockPalette:
|
61
|
+
pal = BlockPalette()
|
62
|
+
pal._states = copy.deepcopy(self._states)
|
63
|
+
pal._map = copy.deepcopy(self._map)
|
64
|
+
return pal
|
65
|
+
|
66
|
+
def clear(self) -> None:
|
67
|
+
self._states.clear()
|
68
|
+
self._map.clear()
|
69
|
+
self.add_state(AIR)
|
70
|
+
|
71
|
+
def add_state(self, state: BlockState) -> int:
|
72
|
+
if state in self:
|
73
|
+
return self._map[state]
|
74
|
+
else:
|
75
|
+
index = len(self)
|
76
|
+
self._states.append(state)
|
77
|
+
self._map[state] = index
|
78
|
+
return index
|
79
|
+
|
80
|
+
def get_state(
|
81
|
+
self,
|
82
|
+
index: int | np.ndarray[int],
|
83
|
+
) -> BlockState | np.ndarray[BlockState]:
|
84
|
+
return np.array(self._states, dtype=object)[np.array(index, dtype=int)]
|
85
|
+
|
86
|
+
def get_index(
|
87
|
+
self,
|
88
|
+
state: BlockState | np.ndarray[BlockState],
|
89
|
+
add_missing: bool = False,
|
90
|
+
) -> int | np.ndarray[int]:
|
91
|
+
state = np.asarray(state, dtype=object)
|
92
|
+
unique_states, xdi = np.unique(state, return_inverse=True)
|
93
|
+
idx = []
|
94
|
+
for block in unique_states:
|
95
|
+
if block not in self and not add_missing:
|
96
|
+
raise KeyError(f"BlockState '{block!s}' not found in palette.")
|
97
|
+
idx.append(self.add_state(block))
|
98
|
+
|
99
|
+
index = np.array(idx, dtype=int)[xdi].reshape(state.shape)
|
100
|
+
return index.item() if np.isscalar(index) else index
|
101
|
+
|
102
|
+
def reduce(self, indices: np.ndarray[int]) -> None:
|
103
|
+
if not (isinstance(indices, np.ndarray) and indices.dtype == int):
|
104
|
+
raise TypeError("'indices' has to be a numpy array of integers")
|
105
|
+
|
106
|
+
unique_idx = np.unique(indices)
|
107
|
+
if 0 not in unique_idx:
|
108
|
+
# always include minecraft:air as the first entry in the palette
|
109
|
+
unique_idx = np.insert(unique_idx, 0, 0)
|
110
|
+
self._states = np.array(self._states, dtype=object)[unique_idx].tolist()
|
111
|
+
self._map = {state: i for i, state in enumerate(self._states)}
|
112
|
+
|
113
|
+
old_new_map = {old: new for new, old in enumerate(unique_idx)}
|
114
|
+
lookup = np.full(max(old_new_map) + 1, -1, dtype=int)
|
115
|
+
for old, new in old_new_map.items():
|
116
|
+
lookup[old] = new
|
117
|
+
return lookup[indices]
|
118
|
+
|
119
|
+
def to_nbt(self) -> nbtlib.List[nbtlib.Compound]:
|
120
|
+
pal = [state.to_nbt() for state in self._states]
|
121
|
+
return nbtlib.List[nbtlib.Compound](pal)
|
122
|
+
|
123
|
+
@classmethod
|
124
|
+
def from_nbt(cls, nbt: nbtlib.List[nbtlib.Compound]) -> BlockPalette:
|
125
|
+
states = [BlockState.from_nbt(block) for block in nbt]
|
126
|
+
pal = cls()
|
127
|
+
pal._states = states
|
128
|
+
pal._map = {state: i for i, state in enumerate(states)}
|
129
|
+
return pal
|
pylitematic/block_property.py
CHANGED
@@ -94,10 +94,10 @@ class Properties(dict):
|
|
94
94
|
def to_string(self) -> str:
|
95
95
|
return str(self)
|
96
96
|
|
97
|
-
@
|
98
|
-
def from_string(string: str) -> Properties:
|
97
|
+
@classmethod
|
98
|
+
def from_string(cls, string: str) -> Properties:
|
99
99
|
if string in ("", "[]"):
|
100
|
-
return
|
100
|
+
return cls()
|
101
101
|
|
102
102
|
if not (string.startswith("[") and string.endswith("]")):
|
103
103
|
raise ValueError(f"Invalid properties string {string!r}")
|
@@ -113,18 +113,18 @@ class Properties(dict):
|
|
113
113
|
ValueError(f"Duplicate property name {name!r}")
|
114
114
|
props[name] = PropertyValue.from_string(string=val_str).get()
|
115
115
|
|
116
|
-
return
|
116
|
+
return cls(props)
|
117
117
|
|
118
118
|
def to_nbt(self) -> nbtlib.Compound:
|
119
119
|
return nbtlib.Compound(
|
120
120
|
{name: value.to_nbt() for name, value in sorted(super().items())})
|
121
121
|
|
122
|
-
@
|
123
|
-
def from_nbt(nbt: nbtlib.Compound) -> Properties:
|
122
|
+
@classmethod
|
123
|
+
def from_nbt(cls, nbt: nbtlib.Compound) -> Properties:
|
124
124
|
props = {}
|
125
125
|
for name, value in nbt.items():
|
126
126
|
props[name] = PropertyValue.from_nbt(nbt=value).get()
|
127
|
-
return
|
127
|
+
return cls(props)
|
128
128
|
|
129
129
|
|
130
130
|
class PropertyValue(ABC):
|
@@ -204,20 +204,20 @@ class PropertyValue(ABC):
|
|
204
204
|
def to_string(self) -> str:
|
205
205
|
return str(self)
|
206
206
|
|
207
|
-
@
|
208
|
-
def from_string(string: str) -> PropertyValue:
|
207
|
+
@classmethod
|
208
|
+
def from_string(cls, string: str) -> PropertyValue:
|
209
209
|
try:
|
210
210
|
value = json.loads(string)
|
211
211
|
except json.JSONDecodeError:
|
212
212
|
value = string
|
213
|
-
return
|
213
|
+
return cls.value_factory(value)
|
214
214
|
|
215
215
|
def to_nbt(self) -> nbtlib.String:
|
216
216
|
return nbtlib.String(self)
|
217
217
|
|
218
|
-
@
|
219
|
-
def from_nbt(nbt: nbtlib.String) -> PropertyValue:
|
220
|
-
return
|
218
|
+
@classmethod
|
219
|
+
def from_nbt(cls, nbt: nbtlib.String) -> PropertyValue:
|
220
|
+
return cls.from_string(str(nbt))
|
221
221
|
|
222
222
|
|
223
223
|
class BooleanValue(PropertyValue):
|
pylitematic/block_state.py
CHANGED
@@ -1,11 +1,22 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from copy import deepcopy
|
4
|
+
from dataclasses import dataclass
|
4
5
|
from nbtlib import Compound
|
5
6
|
from typing import Any, Iterator
|
6
7
|
|
7
|
-
from .resource_location import BlockId
|
8
8
|
from .block_property import Properties
|
9
|
+
from .resource_location import ResourceLocation
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass(frozen=True, order=True)
|
13
|
+
class BlockId(ResourceLocation):
|
14
|
+
|
15
|
+
def __eq__(self, other: Any) -> bool:
|
16
|
+
if isinstance(other, BlockState):
|
17
|
+
return self == other.id
|
18
|
+
else:
|
19
|
+
return super().__eq__(other)
|
9
20
|
|
10
21
|
|
11
22
|
class BlockState:
|
@@ -42,10 +53,18 @@ class BlockState:
|
|
42
53
|
|
43
54
|
def __eq__(self, other: Any) -> bool:
|
44
55
|
if isinstance(other, str):
|
45
|
-
|
46
|
-
|
56
|
+
try:
|
57
|
+
other = BlockState.from_string(other)
|
58
|
+
return self == other
|
59
|
+
except ValueError:
|
60
|
+
return False
|
61
|
+
|
62
|
+
if isinstance(other, BlockId):
|
63
|
+
return self.id == other
|
64
|
+
elif isinstance(other, BlockState):
|
65
|
+
return (self.id, self._props) == (other.id, other._props)
|
66
|
+
else:
|
47
67
|
return NotImplemented
|
48
|
-
return (self.id, self._props) == (other.id, other._props)
|
49
68
|
|
50
69
|
def __lt__(self, other: Any) -> bool:
|
51
70
|
if not isinstance(other, BlockState):
|
@@ -74,15 +93,15 @@ class BlockState:
|
|
74
93
|
def to_string(self) -> str:
|
75
94
|
return str(self)
|
76
95
|
|
77
|
-
@
|
78
|
-
def from_string(string: str) -> BlockState:
|
96
|
+
@classmethod
|
97
|
+
def from_string(cls, string: str) -> BlockState:
|
79
98
|
idx = string.find("[") # basic parsing to separate block:id[name=value]
|
80
99
|
if idx == -1:
|
81
100
|
id, props = string, ""
|
82
101
|
else:
|
83
102
|
id, props = string[:idx], string[idx:]
|
84
103
|
|
85
|
-
state =
|
104
|
+
state = cls(id)
|
86
105
|
state._props = Properties.from_string(props)
|
87
106
|
return state
|
88
107
|
|
@@ -93,19 +112,19 @@ class BlockState:
|
|
93
112
|
nbt["Properties"] = self._props.to_nbt()
|
94
113
|
return nbt
|
95
114
|
|
96
|
-
@
|
97
|
-
def from_nbt(nbt: Compound) -> BlockState:
|
98
|
-
state =
|
115
|
+
@classmethod
|
116
|
+
def from_nbt(cls, nbt: Compound) -> BlockState:
|
117
|
+
state = cls(str(nbt["Name"]))
|
99
118
|
state._props = Properties.from_nbt(nbt.get("Properties", Compound()))
|
100
119
|
return state
|
101
120
|
|
102
121
|
def with_id(self, id: str) -> BlockState:
|
103
|
-
state =
|
122
|
+
state = type(self)(id)
|
104
123
|
state._props = deepcopy(self._props)
|
105
124
|
return state
|
106
125
|
|
107
126
|
def with_props(self, **props: Any) -> BlockState:
|
108
|
-
state =
|
127
|
+
state = type(self)(self.id)
|
109
128
|
new_props = deepcopy(self._props)
|
110
129
|
for name, value in props.items():
|
111
130
|
if value is None:
|
@@ -117,3 +136,6 @@ class BlockState:
|
|
117
136
|
|
118
137
|
def without_props(self) -> BlockState:
|
119
138
|
return BlockState(self.id)
|
139
|
+
|
140
|
+
|
141
|
+
AIR = BlockState("air")
|
pylitematic/geometry.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from dataclasses import dataclass
|
4
|
+
import enum
|
4
5
|
import nbtlib
|
5
6
|
import numpy as np
|
7
|
+
from typing import Iterator
|
6
8
|
|
7
9
|
|
8
10
|
@dataclass(frozen=True)
|
@@ -12,134 +14,89 @@ class Vec3i:
|
|
12
14
|
_c: int
|
13
15
|
|
14
16
|
def __post_init__(self) -> None:
|
15
|
-
object.__setattr__(self, "_a",
|
16
|
-
object.__setattr__(self, "_b",
|
17
|
-
object.__setattr__(self, "_c",
|
17
|
+
object.__setattr__(self, "_a", int(self._a))
|
18
|
+
object.__setattr__(self, "_b", int(self._b))
|
19
|
+
object.__setattr__(self, "_c", int(self._c))
|
20
|
+
|
21
|
+
def __getitem__(self, index: int) -> int:
|
22
|
+
return tuple(self)[index]
|
18
23
|
|
19
24
|
def __str__(self) -> str:
|
20
25
|
return str(list(self))
|
21
26
|
|
27
|
+
def __repr__(self) -> str:
|
28
|
+
return (
|
29
|
+
f"{type(self).__name__}(a={self._a}, b={self._b}, c={self._c})")
|
30
|
+
|
22
31
|
def __len__(self) -> int:
|
23
32
|
return 3
|
24
33
|
|
34
|
+
def __iter__(self) -> Iterator[int]:
|
35
|
+
return iter((self._a, self._b, self._c))
|
36
|
+
|
37
|
+
def __neg__(self) -> Vec3i:
|
38
|
+
return type(self)(*(-i for i in self))
|
39
|
+
|
40
|
+
def __abs__(self) -> Vec3i:
|
41
|
+
return type(self)(*(abs(i) for i in self))
|
42
|
+
|
43
|
+
def __array__(self, dtype: type | None = None, copy: bool = True):
|
44
|
+
arr = np.array(tuple(self), dtype=dtype)
|
45
|
+
return arr.copy() if copy else arr
|
46
|
+
|
25
47
|
def __add__(self, other) -> Vec3i:
|
26
|
-
|
27
|
-
other = self._to_array(other)
|
28
|
-
try:
|
29
|
-
result = arr + other
|
30
|
-
except Exception:
|
31
|
-
return NotImplemented
|
32
|
-
return type(self)(*result.astype(int))
|
48
|
+
return type(self)(*(np.array(self) + other))
|
33
49
|
|
34
50
|
def __radd__(self, other) -> Vec3i:
|
35
51
|
return self.__add__(other)
|
36
52
|
|
37
53
|
def __sub__(self, other) -> Vec3i:
|
38
|
-
|
39
|
-
other = self._to_array(other)
|
40
|
-
try:
|
41
|
-
result = arr - other
|
42
|
-
except Exception:
|
43
|
-
return NotImplemented
|
44
|
-
return type(self)(*result.astype(int))
|
54
|
+
return type(self)(*(np.array(self) - other))
|
45
55
|
|
46
56
|
def __rsub__(self, other) -> Vec3i:
|
47
|
-
|
48
|
-
try:
|
49
|
-
result = other - arr
|
50
|
-
except Exception:
|
51
|
-
return NotImplemented
|
52
|
-
return type(self)(*result.astype(int))
|
57
|
+
return -self.__sub__(other)
|
53
58
|
|
54
59
|
def __mul__(self, other) -> Vec3i:
|
55
|
-
|
56
|
-
other = self._to_array(other)
|
57
|
-
try:
|
58
|
-
result = arr * other
|
59
|
-
except Exception:
|
60
|
-
return NotImplemented
|
61
|
-
return type(self)(*result.astype(int))
|
60
|
+
return type(self)(*(np.array(self) * other))
|
62
61
|
|
63
62
|
def __rmul__(self, other) -> Vec3i:
|
64
63
|
return self.__mul__(other)
|
65
64
|
|
66
65
|
def __floordiv__(self, other) -> Vec3i:
|
67
|
-
|
68
|
-
other = self._to_array(other)
|
69
|
-
try:
|
70
|
-
result = arr // other
|
71
|
-
except Exception:
|
72
|
-
return NotImplemented
|
73
|
-
return type(self)(*result.astype(int))
|
66
|
+
return type(self)(*(np.array(self) // other))
|
74
67
|
|
75
68
|
def __rfloordiv__(self, other) -> Vec3i:
|
76
|
-
|
77
|
-
try:
|
78
|
-
result = other // arr
|
79
|
-
except Exception:
|
80
|
-
return NotImplemented
|
81
|
-
return type(self)(*result.astype(int))
|
69
|
+
return type(self)(*(other // np.array(self)))
|
82
70
|
|
83
|
-
def
|
84
|
-
return
|
71
|
+
def __truediv__(self, other) -> Vec3i:
|
72
|
+
return self.__floordiv__(other)
|
85
73
|
|
86
|
-
def
|
87
|
-
return self.
|
74
|
+
def __rtruediv__(self, other) -> Vec3i:
|
75
|
+
return self.__rfloordiv__(other)
|
88
76
|
|
89
|
-
def
|
90
|
-
return
|
77
|
+
def __mod__(self, other) -> Vec3i:
|
78
|
+
return type(self)(*(np.array(self) % other))
|
91
79
|
|
92
|
-
def
|
93
|
-
return type(self)(*np.
|
80
|
+
def __rmod__(self, other) -> Vec3i:
|
81
|
+
return type(self)(*(other % np.array(self)))
|
82
|
+
|
83
|
+
def __eq__(self, other):
|
84
|
+
return np.array(self) == other
|
85
|
+
|
86
|
+
def __ne__(self, other):
|
87
|
+
return np.invert(self.__eq__(other))
|
94
88
|
|
95
89
|
def __lt__(self, other):
|
96
|
-
return np.array(self) <
|
90
|
+
return np.array(self) < other
|
97
91
|
|
98
92
|
def __le__(self, other):
|
99
|
-
return np.array(self) <=
|
93
|
+
return np.array(self) <= other
|
100
94
|
|
101
95
|
def __gt__(self, other):
|
102
|
-
return np.array(self) >
|
96
|
+
return np.array(self) > other
|
103
97
|
|
104
98
|
def __ge__(self, other):
|
105
|
-
return np.array(self) >=
|
106
|
-
|
107
|
-
def __eq__(self, other):
|
108
|
-
return np.array(self) == self._to_array(other)
|
109
|
-
|
110
|
-
def __ne__(self, other):
|
111
|
-
return np.array(self) != self._to_array(other)
|
112
|
-
|
113
|
-
def __array__(self, dtype: type | None = None, copy: bool = True):
|
114
|
-
arr = np.array([self._a, self._b, self._c], dtype=dtype)
|
115
|
-
if copy:
|
116
|
-
return arr.copy()
|
117
|
-
else:
|
118
|
-
return arr
|
119
|
-
|
120
|
-
def _to_array(self, other):
|
121
|
-
if isinstance(other, Vec3i):
|
122
|
-
return np.array(other)
|
123
|
-
else:
|
124
|
-
return other
|
125
|
-
|
126
|
-
@staticmethod
|
127
|
-
def _to_int(value) -> int:
|
128
|
-
if isinstance(value, (int, np.integer)):
|
129
|
-
return int(value)
|
130
|
-
elif isinstance(value, float):
|
131
|
-
if value.is_integer():
|
132
|
-
return int(value)
|
133
|
-
raise TypeError(
|
134
|
-
f"{type(value).__name__} value {value!r} is not"
|
135
|
-
" int, numpy integer, or whole float")
|
136
|
-
|
137
|
-
def to_tuple(self) -> tuple[int, int, int]:
|
138
|
-
return (self._a, self._b, self._c)
|
139
|
-
|
140
|
-
@classmethod
|
141
|
-
def from_tuple(cls, t: tuple[int, int, int]) -> Vec3i:
|
142
|
-
return cls(*t)
|
99
|
+
return np.array(self) >= other
|
143
100
|
|
144
101
|
def to_nbt(self) -> nbtlib.Compound:
|
145
102
|
return nbtlib.Compound({
|
@@ -172,6 +129,15 @@ class BlockPosition(Vec3i):
|
|
172
129
|
return f"{type(self).__name__}(x={self.x}, y={self.y}, z={self.z})"
|
173
130
|
|
174
131
|
|
132
|
+
class Direction(BlockPosition, enum.Enum):
|
133
|
+
NORTH = 0, 0, -1
|
134
|
+
SOUTH = 0, 0, 1
|
135
|
+
WEST = -1, 0, 0
|
136
|
+
EAST = 1, 0, 0
|
137
|
+
UP = 0, 1, 0
|
138
|
+
DOWN = 0, -1, 0
|
139
|
+
|
140
|
+
|
175
141
|
@dataclass(frozen=True)
|
176
142
|
class Size3D(Vec3i):
|
177
143
|
|
@@ -192,16 +158,14 @@ class Size3D(Vec3i):
|
|
192
158
|
f"{type(self).__name__}("
|
193
159
|
f"width={self.width}, height={self.height}, length={self.length})")
|
194
160
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
if axis is None:
|
199
|
-
return BlockPosition(*limit)
|
161
|
+
@property
|
162
|
+
def volume(self) -> int:
|
163
|
+
return abs(self.width * self.height * self.length)
|
200
164
|
|
201
|
-
|
202
|
-
|
165
|
+
@property
|
166
|
+
def limit(self) -> BlockPosition:
|
167
|
+
return BlockPosition(*(self - self.sign))
|
203
168
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
return BlockPosition(*ret)
|
169
|
+
@property
|
170
|
+
def sign(self) -> BlockPosition:
|
171
|
+
return BlockPosition(*np.sign(self))
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import functools
|
2
|
+
|
3
|
+
|
4
|
+
class cached_property:
|
5
|
+
def __init__(self, func):
|
6
|
+
self.func = func
|
7
|
+
self.name = func.__name__
|
8
|
+
functools.update_wrapper(self, func)
|
9
|
+
|
10
|
+
def __get__(self, instance, owner=None):
|
11
|
+
if instance is None:
|
12
|
+
return self
|
13
|
+
|
14
|
+
if not hasattr(instance, "_cache"):
|
15
|
+
raise AttributeError("Instance must have a '_cache' attribute")
|
16
|
+
|
17
|
+
cache = instance._cache
|
18
|
+
if self.name not in cache:
|
19
|
+
cache[self.name] = self.func(instance)
|
20
|
+
return cache[self.name]
|
21
|
+
|
22
|
+
|
23
|
+
def clears_cache(func):
|
24
|
+
@functools.wraps(func)
|
25
|
+
def wrapper(self, *args, **kwargs):
|
26
|
+
if hasattr(self, "_cache"):
|
27
|
+
self._cache.clear()
|
28
|
+
return func(self, *args, **kwargs)
|
29
|
+
return wrapper
|
30
|
+
|
31
|
+
|
32
|
+
class PropertyCache(dict):
|
33
|
+
def __getattr__(self, name):
|
34
|
+
try:
|
35
|
+
return self[name]
|
36
|
+
except KeyError:
|
37
|
+
raise AttributeError(f"No cached value for {name!r}")
|
38
|
+
|
39
|
+
def __setattr__(self, name, value):
|
40
|
+
self[name] = value
|
41
|
+
|
42
|
+
def __delattr__(self, name):
|
43
|
+
try:
|
44
|
+
del self[name]
|
45
|
+
except KeyError:
|
46
|
+
raise AttributeError(f"No cached value named {name!r}")
|
47
|
+
|
48
|
+
|
49
|
+
class PropertyCacheMixin:
|
50
|
+
def __init__(self, *args, **kwargs) -> None:
|
51
|
+
super().__init__(*args, **kwargs)
|
52
|
+
self._cache = PropertyCache()
|