pylitematic 0.0.2__py3-none-any.whl → 0.0.4__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 -3
- pylitematic/block_property.py +13 -79
- pylitematic/block_state.py +20 -13
- pylitematic/geometry.py +70 -92
- pylitematic/region.py +339 -182
- pylitematic/resource_location.py +10 -6
- pylitematic/schematic.py +18 -13
- pylitematic/test.py +69 -36
- {pylitematic-0.0.2.dist-info → pylitematic-0.0.4.dist-info}/METADATA +7 -1
- pylitematic-0.0.4.dist-info/RECORD +12 -0
- pylitematic-0.0.2.dist-info/RECORD +0 -12
- {pylitematic-0.0.2.dist-info → pylitematic-0.0.4.dist-info}/WHEEL +0 -0
- {pylitematic-0.0.2.dist-info → pylitematic-0.0.4.dist-info}/top_level.txt +0 -0
pylitematic/region.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from abc import ABC, abstractmethod
|
3
4
|
from bitpacking import bitpack, bitunpack
|
4
5
|
from functools import cached_property
|
6
|
+
from itertools import product
|
5
7
|
import nbtlib
|
6
8
|
import numpy as np
|
7
9
|
import twos
|
@@ -9,7 +11,7 @@ from typing import Iterator
|
|
9
11
|
|
10
12
|
from .block_state import BlockState
|
11
13
|
from .geometry import BlockPosition, Size3D
|
12
|
-
from .resource_location import
|
14
|
+
from .resource_location import BlockId
|
13
15
|
|
14
16
|
|
15
17
|
AIR = BlockState("air")
|
@@ -21,115 +23,63 @@ class Region:
|
|
21
23
|
size: tuple[int, int, int] | Size3D,
|
22
24
|
origin: tuple[int, int, int] | BlockPosition = (0, 0, 0),
|
23
25
|
):
|
24
|
-
|
25
|
-
|
26
|
+
if not isinstance(size, Size3D):
|
27
|
+
size = Size3D(*size)
|
28
|
+
self._size: Size3D = size
|
26
29
|
|
27
|
-
|
28
|
-
|
30
|
+
if not isinstance(origin, BlockPosition):
|
31
|
+
origin = BlockPosition(*origin)
|
32
|
+
self._origin: BlockPosition = origin
|
33
|
+
|
34
|
+
self._palette: list[BlockState] = [AIR] # TODO: add clear method
|
35
|
+
self._palette_map: dict[BlockState, int] = {AIR: 0} # TODO: bind tighter to _palette
|
29
36
|
self._blocks = np.zeros(abs(self._size), dtype=int)
|
30
37
|
|
31
|
-
|
32
|
-
self.
|
33
|
-
self.
|
34
|
-
self.
|
38
|
+
# TODO: Add support for (tile) entities and ticks
|
39
|
+
self._entities = nbtlib.List[nbtlib.Compound]()
|
40
|
+
self._tile_entities = nbtlib.List[nbtlib.Compound]()
|
41
|
+
self._block_ticks = nbtlib.List[nbtlib.Compound]()
|
42
|
+
self._fluid_ticks = nbtlib.List[nbtlib.Compound]()
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
elif isinstance(item, BlockState):
|
40
|
-
index = self._palette_map.get(item)
|
41
|
-
if index is None:
|
42
|
-
return False
|
43
|
-
return np.any(self._blocks == index)
|
44
|
-
elif isinstance(item, ResourceLocation):
|
45
|
-
return any(
|
46
|
-
(bs.id == item and np.any(self._blocks == idx))
|
47
|
-
for bs, idx in self._palette_map.items())
|
48
|
-
else:
|
49
|
-
return False
|
44
|
+
self._local = LocalRegionView(self)
|
45
|
+
self._world = WorldRegionView(self)
|
46
|
+
self._numpy = NumpyRegionView(self)
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
ndim = self._blocks.ndim
|
55
|
-
result = []
|
56
|
-
for item in index:
|
57
|
-
if item is Ellipsis:
|
58
|
-
result.extend([slice(None)] * (ndim - len(index) + 1))
|
59
|
-
else:
|
60
|
-
result.append(item)
|
61
|
-
while len(result) < ndim:
|
62
|
-
result.append(slice(None))
|
63
|
-
return tuple(result)
|
64
|
-
|
65
|
-
def _to_internal(self, pos):
|
66
|
-
index = []
|
67
|
-
for i, item in enumerate(pos):
|
68
|
-
offset = self.lower[i]
|
69
|
-
if isinstance(item, int):
|
70
|
-
index.append(item - offset)
|
71
|
-
elif isinstance(item, slice):
|
72
|
-
start = item.start - offset if item.start is not None else None
|
73
|
-
stop = item.stop - offset if item.stop is not None else None
|
74
|
-
index.append(slice(start, stop, item.step))
|
75
|
-
else:
|
76
|
-
index.append(item)
|
77
|
-
return tuple(index)
|
78
|
-
|
79
|
-
def _from_internal(self, index: tuple[int, int, int]) -> BlockPosition:
|
80
|
-
return self.lower + index
|
48
|
+
@property
|
49
|
+
def local(self) -> LocalRegionView:
|
50
|
+
return self._local
|
81
51
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
else:
|
86
|
-
index = self._expand_index(key)
|
87
|
-
return self._to_internal(index)
|
52
|
+
@property
|
53
|
+
def world(self) -> WorldRegionView:
|
54
|
+
return self._world
|
88
55
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
if np.isscalar(indices):
|
93
|
-
return self._palette[indices]
|
56
|
+
@property
|
57
|
+
def numpy(self) -> NumpyRegionView:
|
58
|
+
return self._numpy
|
94
59
|
|
95
|
-
|
60
|
+
def __contains__(self, item) -> bool:
|
61
|
+
return item in self.local
|
96
62
|
|
97
|
-
def
|
98
|
-
|
63
|
+
def __eq__(self, other) -> bool:
|
64
|
+
return self.local == other
|
99
65
|
|
100
|
-
|
101
|
-
|
66
|
+
def __ne__(self, other) -> bool:
|
67
|
+
return self.local != other
|
102
68
|
|
103
|
-
|
104
|
-
|
105
|
-
if value not in self._palette_map:
|
106
|
-
self._palette_map[value] = len(self._palette)
|
107
|
-
self._palette.append(value)
|
108
|
-
self._blocks[index] = self._palette_map[value]
|
69
|
+
def __getitem__(self, key):
|
70
|
+
return self.local[key]
|
109
71
|
|
110
|
-
|
111
|
-
|
112
|
-
raise ValueError(
|
113
|
-
"Shape mismatch between assigned array and target slice")
|
72
|
+
def __setitem__(self, key, value) -> None:
|
73
|
+
self.local[key] = value
|
114
74
|
|
115
|
-
|
116
|
-
|
117
|
-
idx = []
|
118
|
-
for state in unique_states:
|
119
|
-
if state not in self._palette_map:
|
120
|
-
self._palette_map[state] = len(self._palette)
|
121
|
-
self._palette.append(state)
|
122
|
-
idx.append(self._palette_map[state])
|
123
|
-
index_array = np.array(idx, dtype=int)[xdi].reshape(value.shape)
|
124
|
-
self._blocks[index] = index_array
|
125
|
-
else:
|
126
|
-
raise TypeError(
|
127
|
-
"Value must be a BlockState or a list of BlockStates")
|
75
|
+
def __iter__(self) -> tuple[BlockPosition, BlockState]:
|
76
|
+
return iter(self.local)
|
128
77
|
|
129
78
|
def compact_palette(self) -> None:
|
79
|
+
# TODO: determine all appropriate places to call this method
|
130
80
|
idx = np.unique(self._blocks)
|
131
|
-
# always include minecraft:air in a palette
|
132
81
|
if 0 not in idx:
|
82
|
+
# always include minecraft:air as the first entry in the palette
|
133
83
|
idx = np.insert(idx, 0, 0)
|
134
84
|
index_map = {old: new for new, old in enumerate(idx)}
|
135
85
|
|
@@ -145,6 +95,7 @@ class Region:
|
|
145
95
|
self._palette = palette
|
146
96
|
self._palette_map = palette_map
|
147
97
|
|
98
|
+
# block state en- / decoding (NBT)
|
148
99
|
def _bits_per_state(self) -> int:
|
149
100
|
return max(2, (len(self._palette) - 1).bit_length())
|
150
101
|
|
@@ -171,126 +122,76 @@ class Region:
|
|
171
122
|
)
|
172
123
|
return nbtlib.LongArray([twos.to_signed(x, 64) for x in chunks])
|
173
124
|
|
174
|
-
@staticmethod
|
175
|
-
def inclusive_end(
|
176
|
-
size: Size3D,
|
177
|
-
pos: BlockPosition = BlockPosition(0, 0, 0),
|
178
|
-
) -> BlockPosition:
|
179
|
-
return pos + (size - np.sign(size))
|
180
|
-
# return pos + np.where(size > 0, size - 1, size + 1)
|
181
|
-
|
182
125
|
@property
|
183
126
|
def size(self) -> Size3D:
|
184
127
|
return self._size
|
185
128
|
|
186
|
-
@cached_property
|
187
|
-
def sign(self) -> Size3D:
|
188
|
-
return Size3D(*np.sign(self._size))
|
189
|
-
|
190
|
-
@property
|
191
|
-
def origin(self) -> BlockPosition:
|
192
|
-
return self._origin
|
193
|
-
|
194
|
-
@cached_property
|
195
|
-
def limit(self) -> BlockPosition:
|
196
|
-
return self.inclusive_end(pos=self._origin, size=self._size)
|
197
|
-
|
198
|
-
@cached_property
|
199
|
-
def start(self) -> BlockPosition:
|
200
|
-
return BlockPosition(0, 0, 0)
|
201
|
-
|
202
|
-
@cached_property
|
203
|
-
def end(self) -> BlockPosition:
|
204
|
-
return self.inclusive_end(pos=self.start, size=self._size)
|
205
|
-
|
206
129
|
@property
|
207
130
|
def width(self) -> int:
|
208
|
-
return self.
|
131
|
+
return self.size.width
|
209
132
|
|
210
133
|
@property
|
211
134
|
def height(self) -> int:
|
212
|
-
return self.
|
135
|
+
return self.size.height
|
213
136
|
|
214
137
|
@property
|
215
138
|
def length(self) -> int:
|
216
|
-
return self.
|
139
|
+
return self.size.length
|
217
140
|
|
218
141
|
@property
|
219
142
|
def volume(self) -> int:
|
220
|
-
return
|
143
|
+
return self.size.volume
|
221
144
|
|
222
145
|
@property
|
223
|
-
def
|
224
|
-
|
225
|
-
return np.count_nonzero(self._blocks)
|
146
|
+
def origin(self) -> BlockPosition:
|
147
|
+
return self._origin
|
226
148
|
|
227
149
|
@property
|
228
|
-
def
|
229
|
-
|
150
|
+
def block_count(self) -> int:
|
151
|
+
# TODO: Add filter BlockStates / BlockIds and rename to count()
|
152
|
+
return np.sum(self != AIR).item()
|
230
153
|
|
231
|
-
@
|
154
|
+
@property
|
232
155
|
def lower(self) -> BlockPosition:
|
233
|
-
return
|
156
|
+
return self.local.lower
|
234
157
|
|
235
|
-
@
|
158
|
+
@property
|
236
159
|
def upper(self) -> BlockPosition:
|
237
|
-
return
|
160
|
+
return self.local.upper
|
238
161
|
|
239
|
-
@
|
162
|
+
@property
|
240
163
|
def bounds(self) -> tuple[BlockPosition, BlockPosition]:
|
241
|
-
return self.
|
164
|
+
return self.local.bounds
|
242
165
|
|
243
|
-
|
244
|
-
|
245
|
-
return BlockPosition(*np.min((self.origin, self.limit), axis=0))
|
166
|
+
def items(self) -> Iterator[tuple[BlockPosition, BlockState]]:
|
167
|
+
return self.local.items()
|
246
168
|
|
247
|
-
|
248
|
-
|
249
|
-
return BlockPosition(*np.max((self.origin, self.limit), axis=0))
|
169
|
+
def positions(self) -> Iterator[BlockPosition]:
|
170
|
+
return self.local.positions()
|
250
171
|
|
251
|
-
|
252
|
-
|
253
|
-
return self.global_lower, self.global_upper
|
172
|
+
def blocks(self) -> Iterator[BlockState]:
|
173
|
+
return self.local.blocks()
|
254
174
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
exclude: BlockState | list[BlockState] | None = None,
|
259
|
-
ignore_props: bool = False,
|
260
|
-
) -> Iterator[tuple[BlockPosition, BlockState]]:
|
261
|
-
if isinstance(include, BlockState):
|
262
|
-
include = [include]
|
263
|
-
if isinstance(exclude, BlockState):
|
264
|
-
exclude = [exclude]
|
265
|
-
|
266
|
-
for z, y, x in np.ndindex(self.shape[::-1]):
|
267
|
-
pos = BlockPosition(x, y, z) * self.sign
|
268
|
-
state = self[pos]
|
269
|
-
|
270
|
-
if exclude:
|
271
|
-
if not ignore_props:
|
272
|
-
if state in exclude:
|
273
|
-
continue
|
274
|
-
else:
|
275
|
-
if any(state.id == ex.id for ex in exclude):
|
276
|
-
continue
|
175
|
+
# block position transformations
|
176
|
+
def world_to_local(self, world: BlockPosition) -> BlockPosition:
|
177
|
+
return world - self._origin
|
277
178
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
if not any(state.id == s.id for s in include):
|
284
|
-
continue
|
179
|
+
def local_to_world(self, local: BlockPosition) -> BlockPosition:
|
180
|
+
return self._origin + local
|
181
|
+
|
182
|
+
def local_to_numpy(self, local: BlockPosition) -> BlockPosition:
|
183
|
+
return BlockPosition(*self.local.position_to_index(local))
|
285
184
|
|
286
|
-
|
185
|
+
def numpy_to_local(self, index: BlockPosition) -> BlockPosition:
|
186
|
+
return self.local.index_to_position(tuple(index))
|
287
187
|
|
288
|
-
def
|
289
|
-
return self.
|
188
|
+
def world_to_numpy(self, world: BlockPosition) -> BlockPosition:
|
189
|
+
return BlockPosition(*self.world.position_to_index(world))
|
290
190
|
|
291
|
-
def
|
292
|
-
return
|
191
|
+
def numpy_to_world(self, index: BlockPosition) -> BlockPosition:
|
192
|
+
return self.world.index_to_position(tuple(index))
|
293
193
|
|
194
|
+
# NBT conversion
|
294
195
|
def to_nbt(self) -> nbtlib.Compound:
|
295
196
|
nbt = nbtlib.Compound()
|
296
197
|
|
@@ -308,12 +209,12 @@ class Region:
|
|
308
209
|
|
309
210
|
return nbt
|
310
211
|
|
311
|
-
@
|
312
|
-
def from_nbt(nbt: nbtlib.Compound) -> Region:
|
212
|
+
@classmethod
|
213
|
+
def from_nbt(cls, nbt: nbtlib.Compound) -> Region:
|
313
214
|
pos = BlockPosition.from_nbt(nbt["Position"])
|
314
215
|
size = Size3D.from_nbt(nbt["Size"])
|
315
216
|
|
316
|
-
region =
|
217
|
+
region = cls(origin=pos, size=size)
|
317
218
|
|
318
219
|
region._palette = [
|
319
220
|
BlockState.from_nbt(block) for block in nbt["BlockStatePalette"]]
|
@@ -326,3 +227,259 @@ class Region:
|
|
326
227
|
region._fluid_ticks = nbt["PendingFluidTicks"]
|
327
228
|
|
328
229
|
return region
|
230
|
+
|
231
|
+
|
232
|
+
class _RegionView(ABC):
|
233
|
+
|
234
|
+
def __init__(self, region: Region) -> None:
|
235
|
+
self.region = region
|
236
|
+
# TODO: add support for (tile) entities and ticks
|
237
|
+
|
238
|
+
@property
|
239
|
+
def _blocks(self) -> np.ndarray[int]:
|
240
|
+
return self.region._blocks
|
241
|
+
|
242
|
+
@property
|
243
|
+
def _palette(self) -> list[BlockState]:
|
244
|
+
return self.region._palette
|
245
|
+
|
246
|
+
@property
|
247
|
+
def _palette_map(self) -> dict[BlockState, int]:
|
248
|
+
return self.region._palette_map
|
249
|
+
|
250
|
+
@abstractmethod
|
251
|
+
def position_to_index(self, pos: BlockPosition) -> tuple[int, int, int]:
|
252
|
+
...
|
253
|
+
|
254
|
+
@abstractmethod
|
255
|
+
def index_to_position(self, index: tuple[int, int, int]) -> BlockPosition:
|
256
|
+
...
|
257
|
+
|
258
|
+
@abstractmethod
|
259
|
+
def _align_array(self, arr: np.ndarray) -> np.ndarray:
|
260
|
+
...
|
261
|
+
|
262
|
+
@abstractmethod
|
263
|
+
def _transform_index(self, index):
|
264
|
+
...
|
265
|
+
|
266
|
+
def __getitem__(self, key):
|
267
|
+
if isinstance(key, BlockPosition):
|
268
|
+
# return self.at(key) # TODO
|
269
|
+
key = tuple(key)
|
270
|
+
index = self._transform_index(key)
|
271
|
+
|
272
|
+
indices = self._blocks[index]
|
273
|
+
if np.isscalar(indices):
|
274
|
+
return self._palette[indices]
|
275
|
+
else:
|
276
|
+
return np.array(self._palette, dtype=object)[indices]
|
277
|
+
|
278
|
+
def __setitem__(self, key, value):
|
279
|
+
if isinstance(key, BlockPosition):
|
280
|
+
# return self.set_at(key, value) # TODO
|
281
|
+
key = tuple(key)
|
282
|
+
index = self._transform_index(key)
|
283
|
+
|
284
|
+
if isinstance(value, list):
|
285
|
+
value = np.array(value, dtype=object)
|
286
|
+
|
287
|
+
if isinstance(value, BlockState):
|
288
|
+
# assign single BlockState to slice
|
289
|
+
if value not in self._palette_map:
|
290
|
+
self._palette_map[value] = len(self._palette)
|
291
|
+
self._palette.append(value)
|
292
|
+
self._blocks[index] = self._palette_map[value]
|
293
|
+
|
294
|
+
elif isinstance(value, np.ndarray):
|
295
|
+
if value.shape != self._blocks[index].shape:
|
296
|
+
# TODO: allow casting
|
297
|
+
raise ValueError(
|
298
|
+
"Shape mismatch between assigned array and target slice")
|
299
|
+
|
300
|
+
# look up (or add) indices for all BlockStates
|
301
|
+
unique_states, xdi = np.unique(value, return_inverse=True)
|
302
|
+
idx = []
|
303
|
+
for state in unique_states:
|
304
|
+
if state not in self._palette_map:
|
305
|
+
self._palette_map[state] = len(self._palette)
|
306
|
+
self._palette.append(state)
|
307
|
+
idx.append(self._palette_map[state])
|
308
|
+
index_array = np.array(idx, dtype=int)[xdi].reshape(value.shape)
|
309
|
+
self._blocks[index] = index_array
|
310
|
+
else:
|
311
|
+
raise TypeError(
|
312
|
+
"Value must be a BlockState or a list of BlockStates")
|
313
|
+
|
314
|
+
def __contains__(self, item) -> bool:
|
315
|
+
if isinstance(item, BlockPosition):
|
316
|
+
return all(self.lower <= item) and all(item <= self.upper)
|
317
|
+
|
318
|
+
elif isinstance(item, BlockState):
|
319
|
+
index = self._palette_map.get(item)
|
320
|
+
if index is None:
|
321
|
+
return False
|
322
|
+
return index in self._blocks
|
323
|
+
# return np.any(self._blocks == index).item()
|
324
|
+
|
325
|
+
elif isinstance(item, BlockId):
|
326
|
+
return any(
|
327
|
+
# bs.id == item and np.any(self._blocks == idx)
|
328
|
+
bs.id == item and idx in self._blocks
|
329
|
+
for bs, idx in self._palette_map.items())
|
330
|
+
|
331
|
+
else:
|
332
|
+
return False
|
333
|
+
|
334
|
+
def __iter__(self) -> Iterator[tuple[BlockPosition, BlockState]]:
|
335
|
+
return self.items()
|
336
|
+
|
337
|
+
def items(self) -> Iterator[tuple[BlockPosition, BlockState]]:
|
338
|
+
for pos, block in zip(self.positions(), self.blocks()):
|
339
|
+
yield pos, block
|
340
|
+
|
341
|
+
def positions(self) -> Iterator[BlockPosition]:
|
342
|
+
ranges = [
|
343
|
+
range(start, stop, step)
|
344
|
+
for start, stop, step
|
345
|
+
in zip(self.origin, self.origin + self.size, self.size.sign)
|
346
|
+
]
|
347
|
+
for z, y, x in product(*reversed(ranges)):
|
348
|
+
yield BlockPosition(x, y, z)
|
349
|
+
|
350
|
+
def blocks(self) -> Iterator[BlockState]:
|
351
|
+
indices = self._align_array(self._blocks).transpose(2, 1, 0).ravel()
|
352
|
+
palette = np.array(self._palette, dtype=object)
|
353
|
+
for block in palette[indices]:
|
354
|
+
yield block
|
355
|
+
|
356
|
+
def __eq__(self, other) -> np.ndarray[bool]:
|
357
|
+
palette = np.array(self._palette, dtype=object)
|
358
|
+
|
359
|
+
if isinstance(other, BlockState):
|
360
|
+
matches = np.array([state == other for state in palette])
|
361
|
+
mask = matches[self._blocks]
|
362
|
+
|
363
|
+
elif isinstance(other, BlockId):
|
364
|
+
matches = np.array([state.id == other for state in palette])
|
365
|
+
mask = matches[self._blocks]
|
366
|
+
|
367
|
+
else:
|
368
|
+
return NotImplemented
|
369
|
+
|
370
|
+
return self._align_array(mask)
|
371
|
+
|
372
|
+
def __ne__(self, other) -> np.ndarray[bool]:
|
373
|
+
return np.invert(self.__eq__(other))
|
374
|
+
|
375
|
+
property
|
376
|
+
@abstractmethod
|
377
|
+
def origin(self) -> BlockPosition:
|
378
|
+
...
|
379
|
+
|
380
|
+
@property
|
381
|
+
@abstractmethod
|
382
|
+
def size(self) -> Size3D:
|
383
|
+
...
|
384
|
+
|
385
|
+
@cached_property
|
386
|
+
def limit(self) -> BlockPosition:
|
387
|
+
return self.origin + self.size.limit
|
388
|
+
|
389
|
+
@cached_property
|
390
|
+
def lower(self) -> BlockPosition:
|
391
|
+
return BlockPosition(*np.min((self.origin, self.limit), axis=0))
|
392
|
+
|
393
|
+
@cached_property
|
394
|
+
def upper(self) -> BlockPosition:
|
395
|
+
return BlockPosition(*np.max((self.origin, self.limit), axis=0))
|
396
|
+
|
397
|
+
@property
|
398
|
+
def bounds(self) -> tuple[BlockPosition, BlockPosition]:
|
399
|
+
return self.lower, self.upper
|
400
|
+
|
401
|
+
|
402
|
+
class NumpyRegionView(_RegionView):
|
403
|
+
|
404
|
+
@property
|
405
|
+
def origin(self) -> BlockPosition:
|
406
|
+
return BlockPosition(0, 0, 0)
|
407
|
+
|
408
|
+
@property
|
409
|
+
def size(self) -> Size3D:
|
410
|
+
return abs(self.region._size)
|
411
|
+
|
412
|
+
def position_to_index(self, pos: BlockPosition) -> tuple[int, int, int]:
|
413
|
+
return tuple(pos)
|
414
|
+
|
415
|
+
def index_to_position(self, index: tuple[int, int, int]) -> BlockPosition:
|
416
|
+
return BlockPosition(*index)
|
417
|
+
|
418
|
+
def _align_array(self, arr: np.ndarray) -> np.ndarray:
|
419
|
+
return arr
|
420
|
+
|
421
|
+
def _transform_index(self, index):
|
422
|
+
return index
|
423
|
+
|
424
|
+
|
425
|
+
class _OrientedView(_RegionView):
|
426
|
+
|
427
|
+
@property
|
428
|
+
def size(self) -> Size3D:
|
429
|
+
return self.region._size
|
430
|
+
|
431
|
+
@cached_property
|
432
|
+
def negative_axes(self) -> tuple[int,...]:
|
433
|
+
return tuple(np.argwhere(self.size < 0).flatten().tolist())
|
434
|
+
|
435
|
+
def position_to_index(self, pos: BlockPosition) -> tuple[int, int, int]:
|
436
|
+
return pos - self.lower
|
437
|
+
|
438
|
+
def index_to_position(self, index: tuple[int, int, int]) -> BlockPosition:
|
439
|
+
return self.lower + index
|
440
|
+
|
441
|
+
def _align_array(self, arr: np.ndarray) -> np.ndarray:
|
442
|
+
return np.flip(arr, axis=self.negative_axes)
|
443
|
+
|
444
|
+
def _transform_index(self, key):
|
445
|
+
if isinstance(key, (int, np.integer, slice, type(Ellipsis))):
|
446
|
+
key = (key,)
|
447
|
+
|
448
|
+
if isinstance(key, tuple):
|
449
|
+
key = list(key)
|
450
|
+
for i, k in enumerate(key):
|
451
|
+
offset = self.lower[i]
|
452
|
+
if isinstance(k, (int, np.integer)):
|
453
|
+
key[i] = k - offset
|
454
|
+
elif isinstance(k, slice):
|
455
|
+
start = k.start - offset if k.start is not None else None
|
456
|
+
stop = k.stop - offset if k.stop is not None else None
|
457
|
+
key[i] = slice(start, stop, k.step)
|
458
|
+
else:
|
459
|
+
# Ellipsis
|
460
|
+
key[i] = k
|
461
|
+
return tuple(key)
|
462
|
+
|
463
|
+
elif isinstance(key, np.ndarray) and key.dtype == bool:
|
464
|
+
# boolean indexing
|
465
|
+
key = self._align_array(key)
|
466
|
+
if key.shape != self._blocks.shape:
|
467
|
+
raise IndexError("Boolean index must match region shape.")
|
468
|
+
return key
|
469
|
+
|
470
|
+
else:
|
471
|
+
return key
|
472
|
+
|
473
|
+
|
474
|
+
class LocalRegionView(_OrientedView):
|
475
|
+
|
476
|
+
@property
|
477
|
+
def origin(self) -> BlockPosition:
|
478
|
+
return BlockPosition(0, 0, 0)
|
479
|
+
|
480
|
+
|
481
|
+
class WorldRegionView(_OrientedView):
|
482
|
+
|
483
|
+
@property
|
484
|
+
def origin(self) -> BlockPosition:
|
485
|
+
return self.region._origin
|
pylitematic/resource_location.py
CHANGED
@@ -50,8 +50,8 @@ class ResourceLocation:
|
|
50
50
|
def to_string(self) -> str:
|
51
51
|
return str(self)
|
52
52
|
|
53
|
-
@
|
54
|
-
def from_string(string: str) -> ResourceLocation:
|
53
|
+
@classmethod
|
54
|
+
def from_string(cls, string: str) -> ResourceLocation:
|
55
55
|
match = LOCATION_PATTERN.fullmatch(string)
|
56
56
|
if not match:
|
57
57
|
raise ValueError(f"Invalid resource location string {string!r}")
|
@@ -59,11 +59,15 @@ class ResourceLocation:
|
|
59
59
|
namespace = match.group("namespace")
|
60
60
|
path = match.group("path")
|
61
61
|
|
62
|
-
return
|
62
|
+
return cls(path=path, namespace=namespace)
|
63
63
|
|
64
64
|
def to_nbt(self) -> nbtlib.String:
|
65
65
|
return nbtlib.String(self)
|
66
66
|
|
67
|
-
@
|
68
|
-
def from_nbt(nbt: nbtlib.String) -> ResourceLocation:
|
69
|
-
return
|
67
|
+
@classmethod
|
68
|
+
def from_nbt(cls, nbt: nbtlib.String) -> ResourceLocation:
|
69
|
+
return cls.from_string(str(nbt))
|
70
|
+
|
71
|
+
|
72
|
+
class BlockId(ResourceLocation):
|
73
|
+
...
|