pylitematic 0.0.1__py3-none-any.whl → 0.0.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.
pylitematic/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.0.1"
1
+ __version__ = "0.0.2"
2
2
 
3
3
  from .block_state import BlockState
4
4
  from .geometry import BlockPosition, Size3D
pylitematic/geometry.py CHANGED
@@ -19,11 +19,14 @@ class Vec3i:
19
19
  def __str__(self) -> str:
20
20
  return str(list(self))
21
21
 
22
+ def __len__(self) -> int:
23
+ return 3
24
+
22
25
  def __add__(self, other) -> Vec3i:
23
26
  arr = np.array(self)
24
- other_arr = self._to_array(other)
27
+ other = self._to_array(other)
25
28
  try:
26
- result = arr + other_arr
29
+ result = arr + other
27
30
  except Exception:
28
31
  return NotImplemented
29
32
  return type(self)(*result.astype(int))
@@ -33,9 +36,9 @@ class Vec3i:
33
36
 
34
37
  def __sub__(self, other) -> Vec3i:
35
38
  arr = np.array(self)
36
- other_arr = self._to_array(other)
39
+ other = self._to_array(other)
37
40
  try:
38
- result = arr - other_arr
41
+ result = arr - other
39
42
  except Exception:
40
43
  return NotImplemented
41
44
  return type(self)(*result.astype(int))
@@ -48,13 +51,34 @@ class Vec3i:
48
51
  return NotImplemented
49
52
  return type(self)(*result.astype(int))
50
53
 
51
- def __mul__(self, scalar: int) -> Vec3i:
52
- return type(self)(
53
- self._a * scalar, self._b * scalar, self._c * scalar)
54
+ def __mul__(self, other) -> Vec3i:
55
+ arr = np.array(self)
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))
62
+
63
+ def __rmul__(self, other) -> Vec3i:
64
+ return self.__mul__(other)
65
+
66
+ def __floordiv__(self, other) -> Vec3i:
67
+ arr = np.array(self)
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))
54
74
 
55
- def __floordiv__(self, scalar: int) -> Vec3i:
56
- return type(self)(
57
- self._a // scalar, self._b // scalar, self._c // scalar)
75
+ def __rfloordiv__(self, other) -> Vec3i:
76
+ arr = np.array(self)
77
+ try:
78
+ result = other // arr
79
+ except Exception:
80
+ return NotImplemented
81
+ return type(self)(*result.astype(int))
58
82
 
59
83
  def __neg__(self) -> Vec3i:
60
84
  return type(self)(-self._a, -self._b, -self._c)
pylitematic/region.py CHANGED
@@ -1,12 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from bitpacking import bitpack, bitunpack
4
+ from functools import cached_property
4
5
  import nbtlib
5
6
  import numpy as np
6
7
  import twos
8
+ from typing import Iterator
7
9
 
8
10
  from .block_state import BlockState
9
11
  from .geometry import BlockPosition, Size3D
12
+ from .resource_location import ResourceLocation
10
13
 
11
14
 
12
15
  AIR = BlockState("air")
@@ -22,27 +25,77 @@ class Region:
22
25
  self._size: Size3D = Size3D(*size)
23
26
 
24
27
  self._palette: list[BlockState] = [AIR]
25
- self._palette_map: dict[BlockState, int] = {AIR: 0} # FIXME
26
- self._blocks = np.zeros(abs(self._size))
28
+ self._palette_map: dict[BlockState, int] = {AIR: 0}
29
+ self._blocks = np.zeros(abs(self._size), dtype=int)
30
+
27
31
  self._entities: list[nbtlib.Compound] = []
28
32
  self._tile_entities: list[nbtlib.Compound] = []
29
33
  self._block_ticks: list[nbtlib.Compound] = []
30
34
  self._fluid_ticks: list[nbtlib.Compound] = []
31
35
 
32
- def __getitem__(self, key):
36
+ def __contains__(self, item) -> bool:
37
+ if isinstance(item, BlockPosition):
38
+ return all(self.lower <= item) and all(item <= self.upper)
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
50
+
51
+ def _expand_index(self, index):
52
+ if not isinstance(index, tuple):
53
+ index = (index,)
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
81
+
82
+ def _key_to_index(self, key):
33
83
  if isinstance(key, BlockPosition):
34
- index = self._blocks[key.x, key.y, key.z]
35
- return self._palette[index]
84
+ index = tuple(key)
85
+ else:
86
+ index = self._expand_index(key)
87
+ return self._to_internal(index)
36
88
 
37
- indices = self._blocks[key]
89
+ def __getitem__(self, key):
90
+ index = self._key_to_index(key)
91
+ indices = self._blocks[index]
38
92
  if np.isscalar(indices):
39
93
  return self._palette[indices]
40
94
 
41
95
  return np.array(self._palette, dtype=object)[indices]
42
96
 
43
97
  def __setitem__(self, key, value) -> None:
44
- if isinstance(key, BlockPosition):
45
- key = key.to_tuple()
98
+ index = self._key_to_index(key)
46
99
 
47
100
  if isinstance(value, list):
48
101
  value = np.array(value, dtype=object)
@@ -52,11 +105,10 @@ class Region:
52
105
  if value not in self._palette_map:
53
106
  self._palette_map[value] = len(self._palette)
54
107
  self._palette.append(value)
55
- index = self._palette_map[value]
56
- self._blocks[key] = index
108
+ self._blocks[index] = self._palette_map[value]
57
109
 
58
110
  elif isinstance(value, np.ndarray):
59
- if value.shape != self._blocks[key].shape:
111
+ if value.shape != self._blocks[index].shape:
60
112
  raise ValueError(
61
113
  "Shape mismatch between assigned array and target slice")
62
114
 
@@ -69,7 +121,7 @@ class Region:
69
121
  self._palette.append(state)
70
122
  idx.append(self._palette_map[state])
71
123
  index_array = np.array(idx, dtype=int)[xdi].reshape(value.shape)
72
- self._blocks[key] = index_array
124
+ self._blocks[index] = index_array
73
125
  else:
74
126
  raise TypeError(
75
127
  "Value must be a BlockState or a list of BlockStates")
@@ -119,18 +171,37 @@ class Region:
119
171
  )
120
172
  return nbtlib.LongArray([twos.to_signed(x, 64) for x in chunks])
121
173
 
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
+
122
182
  @property
123
183
  def size(self) -> Size3D:
124
184
  return self._size
125
185
 
186
+ @cached_property
187
+ def sign(self) -> Size3D:
188
+ return Size3D(*np.sign(self._size))
189
+
126
190
  @property
127
191
  def origin(self) -> BlockPosition:
128
192
  return self._origin
129
193
 
130
- @property
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
131
203
  def end(self) -> BlockPosition:
132
- return self._origin + np.where(
133
- self._size > 0, self._size - 1, self._size + 1)
204
+ return self.inclusive_end(pos=self.start, size=self._size)
134
205
 
135
206
  @property
136
207
  def width(self) -> int:
@@ -149,33 +220,76 @@ class Region:
149
220
  return np.prod(self.shape).item()
150
221
 
151
222
  @property
152
- def blocks(self) -> int: # TODO: rename
223
+ def block_count(self) -> int:
224
+ # TODO: Add filter BlockState and rename to count()
153
225
  return np.count_nonzero(self._blocks)
154
226
 
155
227
  @property
156
228
  def shape(self) -> tuple[int, int, int]:
157
229
  return self._blocks.shape
158
230
 
159
- @property
231
+ @cached_property
160
232
  def lower(self) -> BlockPosition:
161
- return BlockPosition(*np.min((self.origin, self.end), axis=0))
233
+ return BlockPosition(*np.min((self.start, self.end), axis=0))
162
234
 
163
- @property
235
+ @cached_property
164
236
  def upper(self) -> BlockPosition:
165
- return BlockPosition(*np.max((self.origin, self.end), axis=0))
237
+ return BlockPosition(*np.max((self.start, self.end), axis=0))
166
238
 
167
- @property
239
+ @cached_property
168
240
  def bounds(self) -> tuple[BlockPosition, BlockPosition]:
169
241
  return self.lower, self.upper
170
242
 
171
- # def count(self, block: BlockState, ignore_props: bool = False) -> int:
172
- # ...
243
+ @cached_property
244
+ def global_lower(self) -> BlockPosition:
245
+ return BlockPosition(*np.min((self.origin, self.limit), axis=0))
173
246
 
174
- def global_position(self, pos: BlockPosition) -> BlockPosition:
175
- return self._origin + pos
247
+ @cached_property
248
+ def global_upper(self) -> BlockPosition:
249
+ return BlockPosition(*np.max((self.origin, self.limit), axis=0))
176
250
 
177
- def local_position(self, pos: BlockPosition) -> BlockPosition:
178
- return pos - self._origin
251
+ @cached_property
252
+ def global_bounds(self) -> tuple[BlockPosition, BlockPosition]:
253
+ return self.global_lower, self.global_upper
254
+
255
+ def blocks(
256
+ self,
257
+ include: BlockState | list[BlockState] | None = None,
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
277
+
278
+ if include:
279
+ if not ignore_props:
280
+ if state not in include:
281
+ continue
282
+ else:
283
+ if not any(state.id == s.id for s in include):
284
+ continue
285
+
286
+ yield pos, state
287
+
288
+ def to_global(self, local_pos: BlockPosition) -> BlockPosition:
289
+ return self._origin + local_pos
290
+
291
+ def to_local(self, global_pos: BlockPosition) -> BlockPosition:
292
+ return global_pos - self._origin
179
293
 
180
294
  def to_nbt(self) -> nbtlib.Compound:
181
295
  nbt = nbtlib.Compound()
pylitematic/schematic.py CHANGED
@@ -107,7 +107,7 @@ class Schematic:
107
107
  lowers = []
108
108
  uppers = []
109
109
  for reg in self._regions.values():
110
- lower, upper = reg.bounds
110
+ lower, upper = reg.global_bounds
111
111
  lowers.append(lower)
112
112
  uppers.append(upper)
113
113
  return (
@@ -121,7 +121,7 @@ class Schematic:
121
121
 
122
122
  @property
123
123
  def blocks(self) -> int:
124
- return sum(reg.blocks for reg in self._regions.values())
124
+ return sum(reg.block_count for reg in self._regions.values())
125
125
 
126
126
  @property
127
127
  def region_count(self) -> int:
pylitematic/test.py CHANGED
@@ -1,6 +1,6 @@
1
+ import numpy as np
1
2
  import pathlib
2
- from pylitematic import BlockPosition, BlockState, Schematic
3
-
3
+ from pylitematic import BlockPosition, BlockState, ResourceLocation, Schematic
4
4
 
5
5
  path = pathlib.Path("/mnt/d/minecraft/schematics/Litematica/test/subs.litematic")
6
6
  path = pathlib.Path("/mnt/d/minecraft/schematics/Litematica/test/creeper_test.litematic")
@@ -11,13 +11,29 @@ s = Schematic.load(path)
11
11
  print(f"{s.volume=} {s.size=} {s.bounds=}")
12
12
  for name, reg in s.regions():
13
13
  print(name)
14
- print(f"\t{reg.shape=} {reg.volume=} {reg.blocks=}")
15
- print(f"\t{reg.origin=!s} {reg.end=!s}")
16
- print(f"\t{reg.lower=} {reg.upper=!s} {reg.size=}")
14
+ print(f"\t{reg.shape=} {reg.volume=} {reg.block_count=}")
15
+ print(f"\t{reg.origin=!s} {reg.limit=!s}")
16
+ print(f"\t{reg.start=!s} {reg.end=!s}")
17
+ print(f"\t{reg.lower=!s} {reg.upper=!s} {reg.size=}")
17
18
  # print(f"\t{reg[..., 1, 0]}")
18
19
  # print(f"\t{reg[:][1][0]}")
19
20
  # print(f"\t{reg[BlockPosition(0, 1, 0)]}")
20
- reg[1,1,1] = BlockState.from_string("minecraft:stone")
21
+ # reg[1,1,1] = BlockState.from_string("minecraft:stone")
22
+ # print("lol: ", reg[reg.end])
23
+ reg[0,:,0] = BlockState("minecraft:obsidian")
21
24
  reg[0,:,0] = [dirt, stone, dirt]
22
- print(f"\t{reg[:]}")
23
- s.save("/mnt/d/minecraft/schematics/Litematica/test/pylitematic.litematic")
25
+ # print(reg[...,0])
26
+ # print(reg[np.array([BlockPosition(0, 0, 0), BlockPosition(1, 1, 1)])])
27
+ # print(f"\t{reg[:]}")
28
+ # for pos, state in reg.blocks(exclude_air=True):
29
+ # print(pos, state)
30
+ # for pos, state in reg.blocks((BlockState("oak_log", axis="x"), BlockState("spruce_log", axis="z")), ignore_props=True):
31
+ for pos, state in reg.blocks(exclude=BlockState("air")):
32
+ print(pos, reg._to_internal(pos), state)
33
+ for pos, state in reg.blocks(include=BlockState("air")):
34
+ reg[pos] = BlockState("minecraft:netherrack")
35
+ print(BlockState("oak_log", axis="x") in reg)
36
+ print(BlockPosition(1, 1, 0) in reg)
37
+ print(ResourceLocation("birch_log") in reg)
38
+ # print(reg[0,:,2])
39
+ s.save("/mnt/d/minecraft/schematics/Litematica/test/aaa.litematic")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pylitematic
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Load, modify, and save Litematica schematics
5
5
  Author-email: Boscawinks <bosca.winks@gmx.de>
6
6
  License: GPL-3.0-only
@@ -0,0 +1,12 @@
1
+ pylitematic/__init__.py,sha256=ycco-z7gJlUnBUJuJRo9qzu5pSzXSZLPkqOVLy8OsEI,211
2
+ pylitematic/block_property.py,sha256=eV9YbMcuttX-WPdbqN6dMmhb4jK_8_zRv33wNaVd8_o,9887
3
+ pylitematic/block_state.py,sha256=hC8ptOTR6XKokn-m2Nlxpi44AvL-vwlgive3KQ4VtUg,3328
4
+ pylitematic/geometry.py,sha256=n4Kk413FFhWHFgRRRQCYB2UdDBnbUMqwCEBP85YK1vI,5009
5
+ pylitematic/region.py,sha256=15elHo9Li_nw7_VeM9GP92mcK0cYuJtONKnDyj_SnxY,10918
6
+ pylitematic/resource_location.py,sha256=kVv9-4WVu_Ak24Um05bucKG-mcXymnylwHMha-ORLoo,2143
7
+ pylitematic/schematic.py,sha256=Zc85oT6jdHVdQtAgcZbj9qFXXXWow1GfKXc1TV10CqQ,7879
8
+ pylitematic/test.py,sha256=6IrD4t3f7puWIkgsZVfy-epDgKWFQIOGMizQFF7O7u0,1886
9
+ pylitematic-0.0.2.dist-info/METADATA,sha256=f0AzgTmhaCPTVVjpnoovf_q_2vG9x_KToOT4kh7bQDM,948
10
+ pylitematic-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ pylitematic-0.0.2.dist-info/top_level.txt,sha256=sYUxm6O7Dh5TzuP-kPFe2FHJWUuwHFO69vN2VBiEG4A,12
12
+ pylitematic-0.0.2.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- pylitematic/__init__.py,sha256=xVVYkyJ7nV4I2eZ-XAcDz7yP2wXQHA-DWcMYRy_UUyg,211
2
- pylitematic/block_property.py,sha256=eV9YbMcuttX-WPdbqN6dMmhb4jK_8_zRv33wNaVd8_o,9887
3
- pylitematic/block_state.py,sha256=hC8ptOTR6XKokn-m2Nlxpi44AvL-vwlgive3KQ4VtUg,3328
4
- pylitematic/geometry.py,sha256=J_Hu4umWUKxN6MFrem0f1hxmAEJPJugxJ2v2oXIygVk,4432
5
- pylitematic/region.py,sha256=86Nr3vPvzEYXLnrPaRHx62DsxJFWNCD9leUHJ6ThPNs,7009
6
- pylitematic/resource_location.py,sha256=kVv9-4WVu_Ak24Um05bucKG-mcXymnylwHMha-ORLoo,2143
7
- pylitematic/schematic.py,sha256=HuPxQBmnE_AswncG0LN-lxQYJHADKxEl6JPLYK2MRUY,7867
8
- pylitematic/test.py,sha256=vJU2d3ncw1EFEWALbUPXzsofk16-zWBDOZuJm6qNS7I,1019
9
- pylitematic-0.0.1.dist-info/METADATA,sha256=KI4k64vSJ2LaVsK8_P0p0ARDc3b_UAsYM4TKgVP3oVA,948
10
- pylitematic-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- pylitematic-0.0.1.dist-info/top_level.txt,sha256=sYUxm6O7Dh5TzuP-kPFe2FHJWUuwHFO69vN2VBiEG4A,12
12
- pylitematic-0.0.1.dist-info/RECORD,,