pylitematic 0.0.3__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 +2 -2
- pylitematic/block_property.py +13 -13
- pylitematic/block_state.py +8 -8
- pylitematic/geometry.py +67 -103
- pylitematic/region.py +334 -184
- pylitematic/resource_location.py +6 -6
- pylitematic/schematic.py +11 -8
- pylitematic/test.py +11 -10
- {pylitematic-0.0.3.dist-info → pylitematic-0.0.4.dist-info}/METADATA +4 -1
- pylitematic-0.0.4.dist-info/RECORD +12 -0
- pylitematic-0.0.3.dist-info/RECORD +0 -12
- {pylitematic-0.0.3.dist-info → pylitematic-0.0.4.dist-info}/WHEEL +0 -0
- {pylitematic-0.0.3.dist-info → pylitematic-0.0.4.dist-info}/top_level.txt +0 -0
pylitematic/__init__.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
__version__ = "0.0.
|
1
|
+
__version__ = "0.0.4"
|
2
2
|
|
3
3
|
from .block_state import BlockState
|
4
|
-
from .geometry import BlockPosition, Size3D
|
4
|
+
from .geometry import BlockPosition, Direction, Size3D
|
5
5
|
from .region import Region
|
6
6
|
from .resource_location import BlockId
|
7
7
|
from .schematic import Schematic
|
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
@@ -74,15 +74,15 @@ class BlockState:
|
|
74
74
|
def to_string(self) -> str:
|
75
75
|
return str(self)
|
76
76
|
|
77
|
-
@
|
78
|
-
def from_string(string: str) -> BlockState:
|
77
|
+
@classmethod
|
78
|
+
def from_string(cls, string: str) -> BlockState:
|
79
79
|
idx = string.find("[") # basic parsing to separate block:id[name=value]
|
80
80
|
if idx == -1:
|
81
81
|
id, props = string, ""
|
82
82
|
else:
|
83
83
|
id, props = string[:idx], string[idx:]
|
84
84
|
|
85
|
-
state =
|
85
|
+
state = cls(id)
|
86
86
|
state._props = Properties.from_string(props)
|
87
87
|
return state
|
88
88
|
|
@@ -93,19 +93,19 @@ class BlockState:
|
|
93
93
|
nbt["Properties"] = self._props.to_nbt()
|
94
94
|
return nbt
|
95
95
|
|
96
|
-
@
|
97
|
-
def from_nbt(nbt: Compound) -> BlockState:
|
98
|
-
state =
|
96
|
+
@classmethod
|
97
|
+
def from_nbt(cls, nbt: Compound) -> BlockState:
|
98
|
+
state = cls(str(nbt["Name"]))
|
99
99
|
state._props = Properties.from_nbt(nbt.get("Properties", Compound()))
|
100
100
|
return state
|
101
101
|
|
102
102
|
def with_id(self, id: str) -> BlockState:
|
103
|
-
state =
|
103
|
+
state = type(self)(id)
|
104
104
|
state._props = deepcopy(self._props)
|
105
105
|
return state
|
106
106
|
|
107
107
|
def with_props(self, **props: Any) -> BlockState:
|
108
|
-
state =
|
108
|
+
state = type(self)(self.id)
|
109
109
|
new_props = deepcopy(self._props)
|
110
110
|
for name, value in props.items():
|
111
111
|
if value is None:
|
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 self.__add__(-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))
|
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
|
@@ -21,130 +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
|
-
# TODO: Add support
|
38
|
+
# TODO: Add support for (tile) entities and ticks
|
32
39
|
self._entities = nbtlib.List[nbtlib.Compound]()
|
33
40
|
self._tile_entities = nbtlib.List[nbtlib.Compound]()
|
34
41
|
self._block_ticks = nbtlib.List[nbtlib.Compound]()
|
35
42
|
self._fluid_ticks = nbtlib.List[nbtlib.Compound]()
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
elif isinstance(item, BlockState):
|
41
|
-
index = self._palette_map.get(item)
|
42
|
-
if index is None:
|
43
|
-
return False
|
44
|
-
return np.any(self._blocks == index)
|
45
|
-
elif isinstance(item, BlockId):
|
46
|
-
return any(
|
47
|
-
(bs.id == item and np.any(self._blocks == idx))
|
48
|
-
for bs, idx in self._palette_map.items())
|
49
|
-
else:
|
50
|
-
return False
|
51
|
-
|
52
|
-
def __eq__(self, other) -> bool:
|
53
|
-
palette = np.array(self._palette, dtype=object)
|
44
|
+
self._local = LocalRegionView(self)
|
45
|
+
self._world = WorldRegionView(self)
|
46
|
+
self._numpy = NumpyRegionView(self)
|
54
47
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
elif isinstance(other, BlockId):
|
60
|
-
matches = np.array([state.id == other for state in palette])
|
61
|
-
return matches[self._blocks]
|
62
|
-
|
63
|
-
else:
|
64
|
-
return NotImplemented
|
65
|
-
|
66
|
-
def __getitem__(self, key):
|
67
|
-
index = self._key_to_index(key)
|
68
|
-
indices = self._blocks[index]
|
69
|
-
if np.isscalar(indices):
|
70
|
-
return self._palette[indices]
|
48
|
+
@property
|
49
|
+
def local(self) -> LocalRegionView:
|
50
|
+
return self._local
|
71
51
|
|
72
|
-
|
52
|
+
@property
|
53
|
+
def world(self) -> WorldRegionView:
|
54
|
+
return self._world
|
73
55
|
|
74
|
-
|
75
|
-
|
56
|
+
@property
|
57
|
+
def numpy(self) -> NumpyRegionView:
|
58
|
+
return self._numpy
|
76
59
|
|
77
|
-
|
78
|
-
|
60
|
+
def __contains__(self, item) -> bool:
|
61
|
+
return item in self.local
|
79
62
|
|
80
|
-
|
81
|
-
|
82
|
-
if value not in self._palette_map:
|
83
|
-
self._palette_map[value] = len(self._palette)
|
84
|
-
self._palette.append(value)
|
85
|
-
self._blocks[index] = self._palette_map[value]
|
63
|
+
def __eq__(self, other) -> bool:
|
64
|
+
return self.local == other
|
86
65
|
|
87
|
-
|
88
|
-
|
89
|
-
raise ValueError(
|
90
|
-
"Shape mismatch between assigned array and target slice")
|
66
|
+
def __ne__(self, other) -> bool:
|
67
|
+
return self.local != other
|
91
68
|
|
92
|
-
|
93
|
-
|
94
|
-
idx = []
|
95
|
-
for state in unique_states:
|
96
|
-
if state not in self._palette_map:
|
97
|
-
self._palette_map[state] = len(self._palette)
|
98
|
-
self._palette.append(state)
|
99
|
-
idx.append(self._palette_map[state])
|
100
|
-
index_array = np.array(idx, dtype=int)[xdi].reshape(value.shape)
|
101
|
-
self._blocks[index] = index_array
|
102
|
-
else:
|
103
|
-
raise TypeError(
|
104
|
-
"Value must be a BlockState or a list of BlockStates")
|
69
|
+
def __getitem__(self, key):
|
70
|
+
return self.local[key]
|
105
71
|
|
106
|
-
def
|
107
|
-
|
108
|
-
index = (index,)
|
109
|
-
ndim = self._blocks.ndim
|
110
|
-
result = []
|
111
|
-
for item in index:
|
112
|
-
if item is Ellipsis:
|
113
|
-
result.extend([slice(None)] * (ndim - len(index) + 1))
|
114
|
-
else:
|
115
|
-
result.append(item)
|
116
|
-
while len(result) < ndim:
|
117
|
-
result.append(slice(None))
|
118
|
-
return tuple(result)
|
119
|
-
|
120
|
-
def _to_internal(self, pos):
|
121
|
-
index = []
|
122
|
-
for i, item in enumerate(pos):
|
123
|
-
offset = self.lower[i]
|
124
|
-
if isinstance(item, int):
|
125
|
-
index.append(item - offset)
|
126
|
-
elif isinstance(item, slice):
|
127
|
-
start = item.start - offset if item.start is not None else None
|
128
|
-
stop = item.stop - offset if item.stop is not None else None
|
129
|
-
index.append(slice(start, stop, item.step))
|
130
|
-
else:
|
131
|
-
index.append(item)
|
132
|
-
return tuple(index)
|
133
|
-
|
134
|
-
def _from_internal(self, index: tuple[int, int, int]) -> BlockPosition:
|
135
|
-
return self.lower + index
|
72
|
+
def __setitem__(self, key, value) -> None:
|
73
|
+
self.local[key] = value
|
136
74
|
|
137
|
-
def
|
138
|
-
|
139
|
-
index = tuple(key)
|
140
|
-
else:
|
141
|
-
index = self._expand_index(key)
|
142
|
-
return self._to_internal(index)
|
75
|
+
def __iter__(self) -> tuple[BlockPosition, BlockState]:
|
76
|
+
return iter(self.local)
|
143
77
|
|
144
78
|
def compact_palette(self) -> None:
|
79
|
+
# TODO: determine all appropriate places to call this method
|
145
80
|
idx = np.unique(self._blocks)
|
146
|
-
# always include minecraft:air in a palette
|
147
81
|
if 0 not in idx:
|
82
|
+
# always include minecraft:air as the first entry in the palette
|
148
83
|
idx = np.insert(idx, 0, 0)
|
149
84
|
index_map = {old: new for new, old in enumerate(idx)}
|
150
85
|
|
@@ -160,6 +95,7 @@ class Region:
|
|
160
95
|
self._palette = palette
|
161
96
|
self._palette_map = palette_map
|
162
97
|
|
98
|
+
# block state en- / decoding (NBT)
|
163
99
|
def _bits_per_state(self) -> int:
|
164
100
|
return max(2, (len(self._palette) - 1).bit_length())
|
165
101
|
|
@@ -190,114 +126,72 @@ class Region:
|
|
190
126
|
def size(self) -> Size3D:
|
191
127
|
return self._size
|
192
128
|
|
193
|
-
@cached_property
|
194
|
-
def sign(self) -> Size3D:
|
195
|
-
return Size3D(*np.sign(self._size))
|
196
|
-
|
197
|
-
@property
|
198
|
-
def origin(self) -> BlockPosition:
|
199
|
-
return self._origin
|
200
|
-
|
201
|
-
@cached_property
|
202
|
-
def limit(self) -> BlockPosition:
|
203
|
-
return self._origin + self._size.end()
|
204
|
-
|
205
|
-
@cached_property
|
206
|
-
def start(self) -> BlockPosition:
|
207
|
-
return BlockPosition(0, 0, 0)
|
208
|
-
|
209
|
-
@cached_property
|
210
|
-
def end(self) -> BlockPosition:
|
211
|
-
return self._size.end()
|
212
|
-
|
213
129
|
@property
|
214
130
|
def width(self) -> int:
|
215
|
-
return self.
|
131
|
+
return self.size.width
|
216
132
|
|
217
133
|
@property
|
218
134
|
def height(self) -> int:
|
219
|
-
return self.
|
135
|
+
return self.size.height
|
220
136
|
|
221
137
|
@property
|
222
138
|
def length(self) -> int:
|
223
|
-
return self.
|
139
|
+
return self.size.length
|
224
140
|
|
225
141
|
@property
|
226
142
|
def volume(self) -> int:
|
227
|
-
return
|
143
|
+
return self.size.volume
|
228
144
|
|
229
145
|
@property
|
230
|
-
def
|
231
|
-
|
232
|
-
return np.count_nonzero(self._blocks)
|
146
|
+
def origin(self) -> BlockPosition:
|
147
|
+
return self._origin
|
233
148
|
|
234
149
|
@property
|
235
|
-
def
|
236
|
-
|
150
|
+
def block_count(self) -> int:
|
151
|
+
# TODO: Add filter BlockStates / BlockIds and rename to count()
|
152
|
+
return np.sum(self != AIR).item()
|
237
153
|
|
238
|
-
@
|
154
|
+
@property
|
239
155
|
def lower(self) -> BlockPosition:
|
240
|
-
return
|
156
|
+
return self.local.lower
|
241
157
|
|
242
|
-
@
|
158
|
+
@property
|
243
159
|
def upper(self) -> BlockPosition:
|
244
|
-
return
|
160
|
+
return self.local.upper
|
245
161
|
|
246
|
-
@
|
162
|
+
@property
|
247
163
|
def bounds(self) -> tuple[BlockPosition, BlockPosition]:
|
248
|
-
return self.
|
164
|
+
return self.local.bounds
|
249
165
|
|
250
|
-
|
251
|
-
|
252
|
-
return BlockPosition(*np.min((self.origin, self.limit), axis=0))
|
166
|
+
def items(self) -> Iterator[tuple[BlockPosition, BlockState]]:
|
167
|
+
return self.local.items()
|
253
168
|
|
254
|
-
|
255
|
-
|
256
|
-
return BlockPosition(*np.max((self.origin, self.limit), axis=0))
|
169
|
+
def positions(self) -> Iterator[BlockPosition]:
|
170
|
+
return self.local.positions()
|
257
171
|
|
258
|
-
|
259
|
-
|
260
|
-
return self.global_lower, self.global_upper
|
172
|
+
def blocks(self) -> Iterator[BlockState]:
|
173
|
+
return self.local.blocks()
|
261
174
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
exclude: BlockState | list[BlockState] | None = None,
|
266
|
-
ignore_props: bool = False,
|
267
|
-
) -> Iterator[tuple[BlockPosition, BlockState]]:
|
268
|
-
if isinstance(include, BlockState):
|
269
|
-
include = [include]
|
270
|
-
if isinstance(exclude, BlockState):
|
271
|
-
exclude = [exclude]
|
272
|
-
|
273
|
-
for z, y, x in np.ndindex(self.shape[::-1]):
|
274
|
-
pos = BlockPosition(x, y, z) * self.sign
|
275
|
-
state = self[pos]
|
276
|
-
|
277
|
-
if exclude:
|
278
|
-
if not ignore_props:
|
279
|
-
if state in exclude:
|
280
|
-
continue
|
281
|
-
else:
|
282
|
-
if any(state.id == ex.id for ex in exclude):
|
283
|
-
continue
|
175
|
+
# block position transformations
|
176
|
+
def world_to_local(self, world: BlockPosition) -> BlockPosition:
|
177
|
+
return world - self._origin
|
284
178
|
|
285
|
-
|
286
|
-
|
287
|
-
if state not in include:
|
288
|
-
continue
|
289
|
-
else:
|
290
|
-
if not any(state.id == s.id for s in include):
|
291
|
-
continue
|
179
|
+
def local_to_world(self, local: BlockPosition) -> BlockPosition:
|
180
|
+
return self._origin + local
|
292
181
|
|
293
|
-
|
182
|
+
def local_to_numpy(self, local: BlockPosition) -> BlockPosition:
|
183
|
+
return BlockPosition(*self.local.position_to_index(local))
|
294
184
|
|
295
|
-
def
|
296
|
-
return self.
|
185
|
+
def numpy_to_local(self, index: BlockPosition) -> BlockPosition:
|
186
|
+
return self.local.index_to_position(tuple(index))
|
297
187
|
|
298
|
-
def
|
299
|
-
return
|
188
|
+
def world_to_numpy(self, world: BlockPosition) -> BlockPosition:
|
189
|
+
return BlockPosition(*self.world.position_to_index(world))
|
300
190
|
|
191
|
+
def numpy_to_world(self, index: BlockPosition) -> BlockPosition:
|
192
|
+
return self.world.index_to_position(tuple(index))
|
193
|
+
|
194
|
+
# NBT conversion
|
301
195
|
def to_nbt(self) -> nbtlib.Compound:
|
302
196
|
nbt = nbtlib.Compound()
|
303
197
|
|
@@ -315,12 +209,12 @@ class Region:
|
|
315
209
|
|
316
210
|
return nbt
|
317
211
|
|
318
|
-
@
|
319
|
-
def from_nbt(nbt: nbtlib.Compound) -> Region:
|
212
|
+
@classmethod
|
213
|
+
def from_nbt(cls, nbt: nbtlib.Compound) -> Region:
|
320
214
|
pos = BlockPosition.from_nbt(nbt["Position"])
|
321
215
|
size = Size3D.from_nbt(nbt["Size"])
|
322
216
|
|
323
|
-
region =
|
217
|
+
region = cls(origin=pos, size=size)
|
324
218
|
|
325
219
|
region._palette = [
|
326
220
|
BlockState.from_nbt(block) for block in nbt["BlockStatePalette"]]
|
@@ -333,3 +227,259 @@ class Region:
|
|
333
227
|
region._fluid_ticks = nbt["PendingFluidTicks"]
|
334
228
|
|
335
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,14 +59,14 @@ 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
70
|
|
71
71
|
|
72
72
|
class BlockId(ResourceLocation):
|
pylitematic/schematic.py
CHANGED
@@ -70,6 +70,7 @@ class Schematic:
|
|
70
70
|
self._created_at = round(time.time() * 1000)
|
71
71
|
self._modified_at = self._created_at
|
72
72
|
|
73
|
+
# TODO: use packaging.version.Version
|
73
74
|
self.version_major = version_major
|
74
75
|
self.version_minor = version_minor
|
75
76
|
self.mc_version = mc_version
|
@@ -104,10 +105,11 @@ class Schematic:
|
|
104
105
|
|
105
106
|
@property
|
106
107
|
def bounds(self) -> tuple[BlockPosition, BlockPosition]:
|
108
|
+
# TODO: make cached and update on region add / remove
|
107
109
|
lowers = []
|
108
110
|
uppers = []
|
109
111
|
for reg in self._regions.values():
|
110
|
-
lower, upper = reg.
|
112
|
+
lower, upper = reg.world.bounds
|
111
113
|
lowers.append(lower)
|
112
114
|
uppers.append(upper)
|
113
115
|
return (
|
@@ -142,9 +144,11 @@ class Schematic:
|
|
142
144
|
|
143
145
|
def add_region(self, name: str, region: Region) -> None:
|
144
146
|
self._regions[name] = region
|
147
|
+
# TODO: re-calculate bounding box
|
145
148
|
|
146
149
|
def remove_region(self, name: str) -> Region:
|
147
150
|
return self._regions.pop(name)
|
151
|
+
# TODO: re-calculate bounding box
|
148
152
|
|
149
153
|
@property
|
150
154
|
def created_at(self) -> datetime:
|
@@ -158,15 +162,14 @@ class Schematic:
|
|
158
162
|
file = nbtlib.File(self.to_nbt())
|
159
163
|
file.save(path, gzipped=True, byteorder="big")
|
160
164
|
|
161
|
-
@
|
162
|
-
def load(path: pathlib.Path | str) -> Schematic:
|
165
|
+
@classmethod
|
166
|
+
def load(cls, path: pathlib.Path | str) -> Schematic:
|
163
167
|
if isinstance(path, str):
|
164
168
|
path = pathlib.Path(path)
|
165
169
|
nbt = nbtlib.File.load(path.expanduser(), True)
|
166
|
-
return
|
170
|
+
return cls.from_nbt(nbt)
|
167
171
|
|
168
172
|
def to_nbt(self) -> nbtlib.Compound:
|
169
|
-
# self._update()
|
170
173
|
nbt = nbtlib.Compound()
|
171
174
|
|
172
175
|
# meta data
|
@@ -202,8 +205,8 @@ class Schematic:
|
|
202
205
|
|
203
206
|
return nbt
|
204
207
|
|
205
|
-
@
|
206
|
-
def from_nbt(nbt: nbtlib.Compound) -> Schematic:
|
208
|
+
@classmethod
|
209
|
+
def from_nbt(cls, nbt: nbtlib.Compound) -> Schematic:
|
207
210
|
# meta data
|
208
211
|
try:
|
209
212
|
meta = nbt["Metadata"]
|
@@ -247,7 +250,7 @@ class Schematic:
|
|
247
250
|
|
248
251
|
mc_version = nbt.get("MinecraftDataVersion")
|
249
252
|
|
250
|
-
schem =
|
253
|
+
schem = cls(
|
251
254
|
name=name,
|
252
255
|
author=author,
|
253
256
|
description=desc,
|
pylitematic/test.py
CHANGED
@@ -45,27 +45,28 @@ air = BlockState("air")
|
|
45
45
|
stone = BlockState("stone")
|
46
46
|
dirt = BlockState("dirt")
|
47
47
|
grass = BlockState("grass_block")
|
48
|
-
cobble = BlockState("
|
48
|
+
cobble = BlockState("cobblestone")
|
49
|
+
mossy_cobble = BlockState("mossy_cobblestone")
|
49
50
|
snow = BlockState("snow_block")
|
50
51
|
pumpkin = BlockState("carved_pumpkin", facing="west")
|
51
52
|
|
52
53
|
ground = Region(size=Size3D(16, 9, 16), origin=BlockPosition(0, 0, 0))
|
53
|
-
ground[:,:5,:] = stone
|
54
|
-
ground[:,5:8,:] = dirt
|
55
|
-
ground[:,8:,:] = grass
|
54
|
+
ground.local[:,:5,:] = stone
|
55
|
+
ground.local[:,5:8,:] = dirt
|
56
|
+
ground.local[:,8:,:] = grass
|
56
57
|
|
57
58
|
boulder = Region(size=(4, 4, 4), origin=ground.origin+[6, ground.height, 6])
|
58
|
-
boulder[:] =
|
59
|
+
boulder[:] = mossy_cobble
|
60
|
+
boulder.numpy[:,-2:,:] = cobble
|
59
61
|
|
60
62
|
# snow_man = Region(size=(1, 3, 1), origin=boulder.origin+[1, boulder.height, 1])
|
61
|
-
snow_man = Region(size=(
|
62
|
-
snow_man[
|
63
|
+
snow_man = Region(size=(1, -3, 1), origin=boulder.origin+[1, boulder.upper.y+3, 1])
|
64
|
+
snow_man[...] = snow
|
65
|
+
# snow_man.numpy[:,:2,:] = [[[snow], [snow]]]
|
63
66
|
snow_man[0,snow_man.upper.y,0] = pumpkin
|
64
67
|
|
65
|
-
schem = Schematic(name="
|
68
|
+
schem = Schematic(name="a_scene", author="Boscawinks", description="A simple scene")
|
66
69
|
schem.add_region("ground", ground)
|
67
70
|
schem.add_region("boulder", boulder)
|
68
71
|
schem.add_region("snow_man", snow_man)
|
69
72
|
schem.save(f"/mnt/d/minecraft/schematics/Litematica/test/{schem.name}.litematic")
|
70
|
-
|
71
|
-
print(snow_man == snow)
|
@@ -1,13 +1,16 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pylitematic
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.4
|
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
|
7
7
|
Project-URL: Homepage, https://github.com/boscawinks/pylitematic
|
8
8
|
Project-URL: Repository, https://github.com/boscawinks/pylitematic
|
9
9
|
Project-URL: Issues, https://github.com/boscawinks/pylitematic/issues
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
10
11
|
Classifier: Programming Language :: Python :: 3
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Classifier: Topic :: Utilities
|
11
14
|
Requires-Python: >=3.10.12
|
12
15
|
Description-Content-Type: text/markdown
|
13
16
|
Requires-Dist: bitpacking>=0.1.0
|
@@ -0,0 +1,12 @@
|
|
1
|
+
pylitematic/__init__.py,sha256=Cu9tl3jcE6c1VPcMFkrNq5ddWhGZFd7OT_lhItHBdsI,213
|
2
|
+
pylitematic/block_property.py,sha256=HGxDSngEr-tcwuZPGn6ohHQQ65OL4MaTP9ytDAvuSCw,7714
|
3
|
+
pylitematic/block_state.py,sha256=PuwE3b7zf8Gw-wV3DOXYYwfvLrUJmkj0H_XC7yGMKdw,3540
|
4
|
+
pylitematic/geometry.py,sha256=x_rjJ0gO7uhUJqEg9es8nzD-p3rCiSM9vpdwF7JukAM,4194
|
5
|
+
pylitematic/region.py,sha256=iMX95qkd-OAWP8GUyi-_DBYgtphy8djgLb5lRR_Ssu8,15150
|
6
|
+
pylitematic/resource_location.py,sha256=bqw3Oh9Bx-oWZdm-Qj_FoqpNOh3gc6DPvKqCm6DIDSo,2168
|
7
|
+
pylitematic/schematic.py,sha256=nKH5EjU2ZvuJzjyO84kmKrcKI0jqmNfzq1UXNIroKYQ,8090
|
8
|
+
pylitematic/test.py,sha256=weexc1mKqC7f2Uayd3KuXLsXBOvMCmLGqZrekyeVEiI,3284
|
9
|
+
pylitematic-0.0.4.dist-info/METADATA,sha256=pKIVzFpc22EELK8IyPmlM2UB5999l_SGozXuZucRRDQ,1159
|
10
|
+
pylitematic-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
pylitematic-0.0.4.dist-info/top_level.txt,sha256=sYUxm6O7Dh5TzuP-kPFe2FHJWUuwHFO69vN2VBiEG4A,12
|
12
|
+
pylitematic-0.0.4.dist-info/RECORD,,
|
@@ -1,12 +0,0 @@
|
|
1
|
-
pylitematic/__init__.py,sha256=-yLTtTzD7kRpeKMbUTYNy67zkFkxUP8V1DnPXhS3ZFY,202
|
2
|
-
pylitematic/block_property.py,sha256=4wwsQrxyNGDcrktpd4tD0Vzv-a1OxWYMj0H05GDiT-I,7739
|
3
|
-
pylitematic/block_state.py,sha256=tMRoY6jPtt2M85DR9U9dnQh2Ukp6pP_65xXzMIFeUEo,3546
|
4
|
-
pylitematic/geometry.py,sha256=kt2buIJovAymAtaGQYmmWj7ypOt1xI8s--LHkbTPUlM,5399
|
5
|
-
pylitematic/region.py,sha256=0bUcK9oVyhNDzfxTjZmVLwMddrIdxvJX-cXkWL8BRSE,11097
|
6
|
-
pylitematic/resource_location.py,sha256=gHVE1RTRHtc8DWSlXa0WUom68tRlJy8LU5dOzuONV98,2186
|
7
|
-
pylitematic/schematic.py,sha256=SLz4NQNBJkLXV9Oj0fVOE-daP4MRsdYAnnl1vKzJv_Q,7928
|
8
|
-
pylitematic/test.py,sha256=9uch5L58o9QVJI50jUcbuulEswYzDgtYnRiIP991jFo,3162
|
9
|
-
pylitematic-0.0.3.dist-info/METADATA,sha256=ES-4UDWi6DQU6fCSNrgf5FLMDX_jh1g7k6kx9fmiX70,1033
|
10
|
-
pylitematic-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
-
pylitematic-0.0.3.dist-info/top_level.txt,sha256=sYUxm6O7Dh5TzuP-kPFe2FHJWUuwHFO69vN2VBiEG4A,12
|
12
|
-
pylitematic-0.0.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|