pylitematic 0.0.0__tar.gz → 0.0.1__tar.gz
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-0.0.0 → pylitematic-0.0.1}/PKG-INFO +8 -8
- {pylitematic-0.0.0 → pylitematic-0.0.1}/README.md +1 -1
- {pylitematic-0.0.0 → pylitematic-0.0.1}/pyproject.toml +8 -8
- {pylitematic-0.0.0 → pylitematic-0.0.1}/setup.cfg +0 -0
- pylitematic-0.0.1/src/pylitematic/__init__.py +7 -0
- pylitematic-0.0.1/src/pylitematic/block_property.py +325 -0
- pylitematic-0.0.1/src/pylitematic/block_state.py +112 -0
- pylitematic-0.0.1/src/pylitematic/geometry.py +169 -0
- pylitematic-0.0.1/src/pylitematic/region.py +214 -0
- pylitematic-0.0.1/src/pylitematic/resource_location.py +69 -0
- pylitematic-0.0.1/src/pylitematic/schematic.py +262 -0
- pylitematic-0.0.1/src/pylitematic/test.py +23 -0
- {pylitematic-0.0.0 → pylitematic-0.0.1}/src/pylitematic.egg-info/PKG-INFO +8 -8
- pylitematic-0.0.1/src/pylitematic.egg-info/SOURCES.txt +17 -0
- {pylitematic-0.0.0 → pylitematic-0.0.1}/src/pylitematic.egg-info/dependency_links.txt +0 -0
- pylitematic-0.0.1/src/pylitematic.egg-info/requires.txt +3 -0
- {pylitematic-0.0.0 → pylitematic-0.0.1}/src/pylitematic.egg-info/top_level.txt +0 -0
- pylitematic-0.0.1/tests/test_block_property.py +93 -0
- pylitematic-0.0.1/tests/test_resource_location.py +38 -0
- pylitematic-0.0.0/src/pylitematic/__init__.py +0 -1
- pylitematic-0.0.0/src/pylitematic.egg-info/SOURCES.txt +0 -8
- pylitematic-0.0.0/src/pylitematic.egg-info/requires.txt +0 -3
@@ -1,18 +1,18 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pylitematic
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.1
|
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
|
-
Project-URL: Homepage, https://github.com/
|
8
|
-
Project-URL: Repository, https://github.com/
|
9
|
-
Project-URL: Issues, https://github.com/
|
7
|
+
Project-URL: Homepage, https://github.com/boscawinks/pylitematic
|
8
|
+
Project-URL: Repository, https://github.com/boscawinks/pylitematic
|
9
|
+
Project-URL: Issues, https://github.com/boscawinks/pylitematic/issues
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
|
-
Requires-Python: >=3.
|
11
|
+
Requires-Python: >=3.10.12
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
Requires-Dist: bitpacking>=0.1.0
|
14
|
-
Requires-Dist: nbtlib>=
|
15
|
-
Requires-Dist: numpy>=
|
14
|
+
Requires-Dist: nbtlib>=2.0.4
|
15
|
+
Requires-Dist: numpy>=2.2.6
|
16
16
|
|
17
17
|
# pylitematic
|
18
18
|
|
@@ -25,5 +25,5 @@ Load, modify, and save [Litematica](https://litematica.org/) schematics
|
|
25
25
|
|
26
26
|
`pylitematic` is available on pypi:
|
27
27
|
```bash
|
28
|
-
pip install --upgrade pylitematic
|
28
|
+
python3 -m pip install --upgrade pylitematic
|
29
29
|
```
|
@@ -1,30 +1,30 @@
|
|
1
1
|
[build-system]
|
2
|
-
requires = ["setuptools>=
|
2
|
+
requires = ["setuptools>=59.6.0"]
|
3
3
|
build-backend = "setuptools.build_meta"
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "pylitematic"
|
7
|
-
version = "0.0.
|
7
|
+
version = "0.0.1"
|
8
8
|
description = "Load, modify, and save Litematica schematics"
|
9
9
|
authors = [
|
10
10
|
{ name="Boscawinks", email="bosca.winks@gmx.de" }
|
11
11
|
]
|
12
12
|
dependencies = [
|
13
13
|
"bitpacking >=0.1.0",
|
14
|
-
"nbtlib >=
|
15
|
-
"numpy >=
|
14
|
+
"nbtlib >=2.0.4",
|
15
|
+
"numpy >=2.2.6"
|
16
16
|
]
|
17
17
|
readme = "README.md"
|
18
|
-
requires-python = ">=3.
|
18
|
+
requires-python = ">=3.10.12"
|
19
19
|
license = { text = "GPL-3.0-only" }
|
20
20
|
classifiers = [
|
21
21
|
"Programming Language :: Python :: 3",
|
22
22
|
]
|
23
23
|
|
24
24
|
[project.urls]
|
25
|
-
Homepage = "https://github.com/
|
26
|
-
Repository = "https://github.com/
|
27
|
-
Issues = "https://github.com/
|
25
|
+
Homepage = "https://github.com/boscawinks/pylitematic"
|
26
|
+
Repository = "https://github.com/boscawinks/pylitematic"
|
27
|
+
Issues = "https://github.com/boscawinks/pylitematic/issues"
|
28
28
|
|
29
29
|
[tool.setuptools.packages.find]
|
30
30
|
where = ["src"]
|
File without changes
|
@@ -0,0 +1,325 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
import json
|
5
|
+
import nbtlib
|
6
|
+
import re
|
7
|
+
from typing import Any
|
8
|
+
|
9
|
+
|
10
|
+
PROPERTY_NAME_REGEX: str = r"[a-z][a-z0-9_]*"
|
11
|
+
PROPERTY_NAME_PATTERN: re.Pattern = re.compile(PROPERTY_NAME_REGEX)
|
12
|
+
|
13
|
+
ENUM_VALUE_REGEX: str = r"[a-z]+(_[a-z]+)*" # snake case
|
14
|
+
ENUM_VALUE_PATTERN: re.Pattern = re.compile(ENUM_VALUE_REGEX)
|
15
|
+
|
16
|
+
|
17
|
+
class Property():
|
18
|
+
|
19
|
+
__slots__ = ("_name", "_value")
|
20
|
+
|
21
|
+
def __init__(self, name: str, value: Any | PropertyValue) -> None:
|
22
|
+
if not PROPERTY_NAME_PATTERN.fullmatch(name):
|
23
|
+
raise ValueError(f"Invalid property name {name!r}")
|
24
|
+
self._name = name
|
25
|
+
|
26
|
+
if not isinstance(value, PropertyValue):
|
27
|
+
value = PropertyValue.value_factory(value=value)
|
28
|
+
self._value = value
|
29
|
+
|
30
|
+
def __str__(self) -> str:
|
31
|
+
return f"{self._name}={self._value}"
|
32
|
+
|
33
|
+
def __repr__(self) -> str:
|
34
|
+
return (
|
35
|
+
f"{type(self).__name__}("
|
36
|
+
f"name: {self._name}, value: {self._value!r})")
|
37
|
+
|
38
|
+
def __eq__(self, other: Any) -> bool:
|
39
|
+
if not isinstance(other, Property):
|
40
|
+
return NotImplemented
|
41
|
+
return (self.name, self.value) == (other.name, other.value)
|
42
|
+
|
43
|
+
def __lt__(self, other: Any) -> bool:
|
44
|
+
if not isinstance(other, Property):
|
45
|
+
return NotImplemented
|
46
|
+
return (self.name, self.value) < (other.name, other.value)
|
47
|
+
|
48
|
+
@property
|
49
|
+
def name(self) -> str:
|
50
|
+
return self._name
|
51
|
+
|
52
|
+
@property
|
53
|
+
def value(self) -> Any:
|
54
|
+
return self._value.get()
|
55
|
+
|
56
|
+
@value.setter
|
57
|
+
def value(self, value: Any) -> None:
|
58
|
+
self._value.set(value)
|
59
|
+
|
60
|
+
def to_string(self) -> str:
|
61
|
+
return str(self)
|
62
|
+
|
63
|
+
@staticmethod
|
64
|
+
def from_string(string: str, value: str | None = None) -> Property:
|
65
|
+
if value is None:
|
66
|
+
# tread string as "name=value"
|
67
|
+
try:
|
68
|
+
string, value = string.split("=")
|
69
|
+
except ValueError as exc:
|
70
|
+
raise ValueError(f"Invalid property string {string!r}") from exc
|
71
|
+
return Property(name=string, value=PropertyValue.from_string(value))
|
72
|
+
|
73
|
+
def to_nbt(self) -> tuple[str, nbtlib.String]:
|
74
|
+
# return nbtlib.Compound(Name=nbtlib.String(self._name), Value=self._value.to_nbt()})
|
75
|
+
return self._name, self._value.to_nbt()
|
76
|
+
|
77
|
+
@staticmethod
|
78
|
+
def from_nbt(name: str, nbt: nbtlib.String) -> Property:
|
79
|
+
# return Property.from_string(name=nbt["Name"], value=str(nbt["Value"]))
|
80
|
+
return Property.from_string(string=name, value=str(nbt))
|
81
|
+
|
82
|
+
|
83
|
+
class Properties(dict):
|
84
|
+
|
85
|
+
def __init__(self, *args, **kwargs):
|
86
|
+
props = {}
|
87
|
+
for name, value in dict(*args, **kwargs).items():
|
88
|
+
self.validate_name(name)
|
89
|
+
props[name] = PropertyValue.value_factory(value)
|
90
|
+
super().__init__(props)
|
91
|
+
|
92
|
+
def __getitem__(self, key):
|
93
|
+
return super().__getitem__(key).get()
|
94
|
+
|
95
|
+
def __setitem__(self, key, value):
|
96
|
+
if key not in self:
|
97
|
+
self.validate_name(key)
|
98
|
+
super().__setitem__(key, PropertyValue.value_factory(value))
|
99
|
+
else:
|
100
|
+
super().__getitem__(key).set(value)
|
101
|
+
|
102
|
+
def __lt__(self, other: Any) -> bool:
|
103
|
+
if not isinstance(other, Properties):
|
104
|
+
return NotImplemented
|
105
|
+
return sorted(self.items()) < sorted(other.items())
|
106
|
+
|
107
|
+
def __hash__(self) -> int:
|
108
|
+
return hash(tuple(sorted(self)))
|
109
|
+
|
110
|
+
def __str__(self) -> str:
|
111
|
+
props_str = [f"{n}={v}" for n, v in sorted(super().items())]
|
112
|
+
return f"[{','.join(props_str)}]"
|
113
|
+
|
114
|
+
def __repr__(self) -> str:
|
115
|
+
props_reps = [f"{n}: {v!r}" for n, v in sorted(super().items())]
|
116
|
+
return f"{type(self).__name__}({', '.join(props_reps)})"
|
117
|
+
|
118
|
+
def get(self, key, default=None):
|
119
|
+
value = super().get(key, None)
|
120
|
+
if value is None:
|
121
|
+
return default
|
122
|
+
return value.get()
|
123
|
+
|
124
|
+
def setdefault(self, key, default=None):
|
125
|
+
if key not in self:
|
126
|
+
self[key] = default
|
127
|
+
return self[key]
|
128
|
+
|
129
|
+
def update(self, *args, **kwargs):
|
130
|
+
for name, value in dict(*args, **kwargs).items():
|
131
|
+
self[name] = value
|
132
|
+
|
133
|
+
def pop(self, key, default=None):
|
134
|
+
value = super().pop(key, None)
|
135
|
+
if value is None:
|
136
|
+
return default
|
137
|
+
return value.get()
|
138
|
+
|
139
|
+
def popitem(self):
|
140
|
+
name, value = super().popitem()
|
141
|
+
return name, value.get()
|
142
|
+
|
143
|
+
def values(self):
|
144
|
+
for value in super().values():
|
145
|
+
yield value.get()
|
146
|
+
|
147
|
+
def items(self):
|
148
|
+
for key, value in super().items():
|
149
|
+
yield key, value.get()
|
150
|
+
|
151
|
+
@staticmethod
|
152
|
+
def is_valid_name(name: str) -> bool:
|
153
|
+
return PROPERTY_NAME_PATTERN.fullmatch(name) is not None
|
154
|
+
|
155
|
+
@staticmethod
|
156
|
+
def validate_name(name: str) -> None:
|
157
|
+
if not Properties.is_valid_name(name=name):
|
158
|
+
raise ValueError(f"Invalid property name {name!r}")
|
159
|
+
|
160
|
+
def to_string(self) -> str:
|
161
|
+
return str(self)
|
162
|
+
|
163
|
+
@staticmethod
|
164
|
+
def from_string(string: str) -> Properties:
|
165
|
+
if string in ("", "[]"):
|
166
|
+
return Properties()
|
167
|
+
|
168
|
+
if not (string.startswith("[") and string.endswith("]")):
|
169
|
+
raise ValueError(f"Invalid properties string {string!r}")
|
170
|
+
string = string[1:-1]
|
171
|
+
|
172
|
+
props = {}
|
173
|
+
for prop in string.split(","):
|
174
|
+
try:
|
175
|
+
name, val_str = prop.split("=")
|
176
|
+
except ValueError as exc:
|
177
|
+
raise ValueError(f"Invalid property string {string!r}") from exc
|
178
|
+
if name in props:
|
179
|
+
ValueError(f"Duplicate property name {name!r}")
|
180
|
+
props[name] = PropertyValue.from_string(string=val_str).get()
|
181
|
+
|
182
|
+
return Properties(props)
|
183
|
+
|
184
|
+
def to_nbt(self) -> nbtlib.Compound:
|
185
|
+
return nbtlib.Compound(
|
186
|
+
{name: value.to_nbt() for name, value in sorted(super().items())})
|
187
|
+
|
188
|
+
@staticmethod
|
189
|
+
def from_nbt(nbt: nbtlib.Compound) -> Properties:
|
190
|
+
props = {}
|
191
|
+
for name, value in nbt.items():
|
192
|
+
props[name] = PropertyValue.from_nbt(nbt=value).get()
|
193
|
+
return Properties(props)
|
194
|
+
|
195
|
+
|
196
|
+
class PropertyValue(ABC):
|
197
|
+
|
198
|
+
__slots__ = ("_value")
|
199
|
+
__registry: dict[type, type[PropertyValue]] = {}
|
200
|
+
|
201
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
202
|
+
super().__init_subclass__(**kwargs)
|
203
|
+
py_type = cls.python_type()
|
204
|
+
if py_type in cls.__registry:
|
205
|
+
raise ValueError(
|
206
|
+
f"Duplicate Value subclass for type {py_type.__name__!r}:"
|
207
|
+
f" {cls.__registry[py_type].__name__} vs {cls.__name__}")
|
208
|
+
cls.__registry[py_type] = cls
|
209
|
+
|
210
|
+
def __init__(self, value: Any) -> None:
|
211
|
+
self.set(value)
|
212
|
+
|
213
|
+
def __str__(self) -> str:
|
214
|
+
return json.dumps(self._value)
|
215
|
+
|
216
|
+
def __repr__(self) -> str:
|
217
|
+
return f"{type(self).__name__}({self._value!r})"
|
218
|
+
|
219
|
+
def __hash__(self) -> int:
|
220
|
+
return hash((self.__class__, self._value))
|
221
|
+
|
222
|
+
def __eq__(self, other: Any) -> bool:
|
223
|
+
if not isinstance(other, self.__class__):
|
224
|
+
return NotImplemented
|
225
|
+
return self._value == other._value
|
226
|
+
|
227
|
+
def __lt__(self, other: Any) -> bool:
|
228
|
+
if not isinstance(other, PropertyValue):
|
229
|
+
return NotImplemented
|
230
|
+
return self._value < other._value
|
231
|
+
|
232
|
+
@classmethod
|
233
|
+
@abstractmethod
|
234
|
+
def is_valid_value(cls, value: Any) -> bool:
|
235
|
+
...
|
236
|
+
|
237
|
+
@classmethod
|
238
|
+
def validate_value(cls, value: Any) -> None:
|
239
|
+
if not isinstance(value, cls.python_type()):
|
240
|
+
raise TypeError(
|
241
|
+
f"{cls.__name__} expects value of type"
|
242
|
+
f" {cls.python_type().__name__}, got {type(value).__name__}"
|
243
|
+
f" ({value!r})")
|
244
|
+
if not cls.is_valid_value(value):
|
245
|
+
raise ValueError(f"Invalid value {value!r} for {cls.__name__}")
|
246
|
+
|
247
|
+
def get(self) -> Any:
|
248
|
+
return self._value
|
249
|
+
|
250
|
+
def set(self, value: Any) -> None:
|
251
|
+
self.validate_value(value=value)
|
252
|
+
self._value = self.python_type()(value)
|
253
|
+
|
254
|
+
@classmethod
|
255
|
+
@abstractmethod
|
256
|
+
def python_type(cls) -> type:
|
257
|
+
"""Return the native Python type this Value corresponds to."""
|
258
|
+
|
259
|
+
@staticmethod
|
260
|
+
def value_factory(value: Any) -> PropertyValue:
|
261
|
+
reg = PropertyValue.__registry
|
262
|
+
sub_cls = reg.get(type(value))
|
263
|
+
if sub_cls is None:
|
264
|
+
opt_str = ", ".join(map(lambda x: x.__name__, reg))
|
265
|
+
raise TypeError(
|
266
|
+
f"No Value subclass registered for {type(value).__name__} value"
|
267
|
+
f" {value!r}. Classes registered for: {opt_str}")
|
268
|
+
return sub_cls(value)
|
269
|
+
|
270
|
+
def to_string(self) -> str:
|
271
|
+
return str(self)
|
272
|
+
|
273
|
+
@staticmethod
|
274
|
+
def from_string(string: str) -> PropertyValue:
|
275
|
+
try:
|
276
|
+
value = json.loads(string)
|
277
|
+
except json.JSONDecodeError:
|
278
|
+
value = string
|
279
|
+
return PropertyValue.value_factory(value)
|
280
|
+
|
281
|
+
def to_nbt(self) -> nbtlib.String:
|
282
|
+
return nbtlib.String(self)
|
283
|
+
|
284
|
+
@staticmethod
|
285
|
+
def from_nbt(nbt: nbtlib.String) -> PropertyValue:
|
286
|
+
return PropertyValue.from_string(str(nbt))
|
287
|
+
|
288
|
+
|
289
|
+
class BooleanValue(PropertyValue):
|
290
|
+
|
291
|
+
@classmethod
|
292
|
+
def is_valid_value(cls, value: Any) -> bool:
|
293
|
+
return True
|
294
|
+
|
295
|
+
@classmethod
|
296
|
+
def python_type(cls) -> type:
|
297
|
+
"""Return the native Python type a BooleanValue corresponds to."""
|
298
|
+
return bool
|
299
|
+
|
300
|
+
|
301
|
+
class IntegerValue(PropertyValue):
|
302
|
+
|
303
|
+
@classmethod
|
304
|
+
def is_valid_value(cls, value: Any) -> bool:
|
305
|
+
return value >= 0
|
306
|
+
|
307
|
+
@classmethod
|
308
|
+
def python_type(cls) -> type:
|
309
|
+
"""Return the native Python type an IntegerValue corresponds to."""
|
310
|
+
return int
|
311
|
+
|
312
|
+
|
313
|
+
class EnumValue(PropertyValue):
|
314
|
+
|
315
|
+
def __str__(self) -> str:
|
316
|
+
return self._value
|
317
|
+
|
318
|
+
@classmethod
|
319
|
+
def is_valid_value(cls, value: Any) -> bool:
|
320
|
+
return ENUM_VALUE_PATTERN.fullmatch(value) is not None
|
321
|
+
|
322
|
+
@classmethod
|
323
|
+
def python_type(cls) -> type:
|
324
|
+
"""Return the native Python type an EnumValue corresponds to."""
|
325
|
+
return str
|
@@ -0,0 +1,112 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from copy import deepcopy
|
4
|
+
from nbtlib import Compound
|
5
|
+
from typing import Any, Iterator
|
6
|
+
|
7
|
+
from .resource_location import ResourceLocation
|
8
|
+
from .block_property import Properties
|
9
|
+
|
10
|
+
|
11
|
+
class BlockState:
|
12
|
+
|
13
|
+
__slots__ = ("_id", "_props")
|
14
|
+
|
15
|
+
def __init__(self, _id: str, **props: Any) -> None:
|
16
|
+
self._id: ResourceLocation = ResourceLocation.from_string(_id)
|
17
|
+
self._props: Properties = Properties(**props)
|
18
|
+
|
19
|
+
def __getitem__(self, name: str) -> Any:
|
20
|
+
try:
|
21
|
+
return self._props[name]
|
22
|
+
except KeyError as exc:
|
23
|
+
raise KeyError(
|
24
|
+
f"{type(self).__name__} '{self}' does not"
|
25
|
+
f" have {name!r} property") from exc
|
26
|
+
|
27
|
+
# def __getattr__(self, name: str) -> Any:
|
28
|
+
# return self[name]
|
29
|
+
|
30
|
+
def __contains__(self, name: str) -> bool:
|
31
|
+
return name in self._props
|
32
|
+
|
33
|
+
def __len__(self) -> int:
|
34
|
+
return len(self._props)
|
35
|
+
|
36
|
+
def __eq__(self, other: Any) -> bool:
|
37
|
+
if isinstance(other, str):
|
38
|
+
other = BlockState.from_string(other)
|
39
|
+
elif not isinstance(other, BlockState):
|
40
|
+
return NotImplemented
|
41
|
+
return (self.id, self._props) == (other.id, other._props)
|
42
|
+
|
43
|
+
def __lt__(self, other: Any) -> bool:
|
44
|
+
if not isinstance(other, BlockState):
|
45
|
+
return NotImplemented
|
46
|
+
return (self.id, self._props) < (other.id, other._props)
|
47
|
+
|
48
|
+
def __hash__(self) -> int:
|
49
|
+
return hash((self._id, self._props))
|
50
|
+
|
51
|
+
def __str__(self) -> str:
|
52
|
+
props_str = "" if not self._props else str(self._props)
|
53
|
+
return f"{self.id}{props_str}"
|
54
|
+
|
55
|
+
def __repr__(self) -> str:
|
56
|
+
return (
|
57
|
+
f"{type(self).__name__}("
|
58
|
+
f"id: {self._id!r}, props: {self._props!r})")
|
59
|
+
|
60
|
+
@property
|
61
|
+
def id(self) -> str:
|
62
|
+
return str(self._id)
|
63
|
+
|
64
|
+
def props(self) -> Iterator[tuple[str, Any]]:
|
65
|
+
return self._props.items()
|
66
|
+
|
67
|
+
def to_string(self) -> str:
|
68
|
+
return str(self)
|
69
|
+
|
70
|
+
@staticmethod
|
71
|
+
def from_string(string: str) -> BlockState:
|
72
|
+
idx = string.find("[") # basic parsing to separate block:id[name=value]
|
73
|
+
if idx == -1:
|
74
|
+
id, props = string, ""
|
75
|
+
else:
|
76
|
+
id, props = string[:idx], string[idx:]
|
77
|
+
|
78
|
+
state = BlockState(id)
|
79
|
+
state._props = Properties.from_string(props)
|
80
|
+
return state
|
81
|
+
|
82
|
+
def to_nbt(self) -> Compound:
|
83
|
+
nbt = Compound()
|
84
|
+
nbt["Name"] = self._id.to_nbt()
|
85
|
+
if self._props:
|
86
|
+
nbt["Properties"] = self._props.to_nbt()
|
87
|
+
return nbt
|
88
|
+
|
89
|
+
@staticmethod
|
90
|
+
def from_nbt(nbt: Compound) -> BlockState:
|
91
|
+
state = BlockState(str(nbt["Name"]))
|
92
|
+
state._props = Properties.from_nbt(nbt.get("Properties", Compound()))
|
93
|
+
return state
|
94
|
+
|
95
|
+
def with_id(self, id: str) -> BlockState:
|
96
|
+
state = BlockState(id)
|
97
|
+
state._props = deepcopy(self._props)
|
98
|
+
return state
|
99
|
+
|
100
|
+
def with_props(self, **props: Any) -> BlockState:
|
101
|
+
state = BlockState(self.id)
|
102
|
+
new_props = deepcopy(self._props)
|
103
|
+
for name, value in props.items():
|
104
|
+
if value is None:
|
105
|
+
del new_props[name]
|
106
|
+
else:
|
107
|
+
new_props[name] = value
|
108
|
+
state._props = new_props
|
109
|
+
return state
|
110
|
+
|
111
|
+
def without_props(self) -> BlockState:
|
112
|
+
return BlockState(self.id)
|
@@ -0,0 +1,169 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
import nbtlib
|
5
|
+
import numpy as np
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass(frozen=True)
|
9
|
+
class Vec3i:
|
10
|
+
_a: int
|
11
|
+
_b: int
|
12
|
+
_c: int
|
13
|
+
|
14
|
+
def __post_init__(self) -> None:
|
15
|
+
object.__setattr__(self, "_a", self._to_int(self._a))
|
16
|
+
object.__setattr__(self, "_b", self._to_int(self._b))
|
17
|
+
object.__setattr__(self, "_c", self._to_int(self._c))
|
18
|
+
|
19
|
+
def __str__(self) -> str:
|
20
|
+
return str(list(self))
|
21
|
+
|
22
|
+
def __add__(self, other) -> Vec3i:
|
23
|
+
arr = np.array(self)
|
24
|
+
other_arr = self._to_array(other)
|
25
|
+
try:
|
26
|
+
result = arr + other_arr
|
27
|
+
except Exception:
|
28
|
+
return NotImplemented
|
29
|
+
return type(self)(*result.astype(int))
|
30
|
+
|
31
|
+
def __radd__(self, other) -> Vec3i:
|
32
|
+
return self.__add__(other)
|
33
|
+
|
34
|
+
def __sub__(self, other) -> Vec3i:
|
35
|
+
arr = np.array(self)
|
36
|
+
other_arr = self._to_array(other)
|
37
|
+
try:
|
38
|
+
result = arr - other_arr
|
39
|
+
except Exception:
|
40
|
+
return NotImplemented
|
41
|
+
return type(self)(*result.astype(int))
|
42
|
+
|
43
|
+
def __rsub__(self, other) -> Vec3i:
|
44
|
+
arr = np.array(self)
|
45
|
+
try:
|
46
|
+
result = other - arr
|
47
|
+
except Exception:
|
48
|
+
return NotImplemented
|
49
|
+
return type(self)(*result.astype(int))
|
50
|
+
|
51
|
+
def __mul__(self, scalar: int) -> Vec3i:
|
52
|
+
return type(self)(
|
53
|
+
self._a * scalar, self._b * scalar, self._c * scalar)
|
54
|
+
|
55
|
+
def __floordiv__(self, scalar: int) -> Vec3i:
|
56
|
+
return type(self)(
|
57
|
+
self._a // scalar, self._b // scalar, self._c // scalar)
|
58
|
+
|
59
|
+
def __neg__(self) -> Vec3i:
|
60
|
+
return type(self)(-self._a, -self._b, -self._c)
|
61
|
+
|
62
|
+
def __getitem__(self, index: int) -> int:
|
63
|
+
return self.to_tuple()[index]
|
64
|
+
|
65
|
+
def __iter__(self):
|
66
|
+
return iter(self.to_tuple())
|
67
|
+
|
68
|
+
def __abs__(self) -> Vec3i:
|
69
|
+
return type(self)(*np.abs(self))
|
70
|
+
|
71
|
+
def __lt__(self, other):
|
72
|
+
return np.array(self) < self._to_array(other)
|
73
|
+
|
74
|
+
def __le__(self, other):
|
75
|
+
return np.array(self) <= self._to_array(other)
|
76
|
+
|
77
|
+
def __gt__(self, other):
|
78
|
+
return np.array(self) > self._to_array(other)
|
79
|
+
|
80
|
+
def __ge__(self, other):
|
81
|
+
return np.array(self) >= self._to_array(other)
|
82
|
+
|
83
|
+
def __eq__(self, other):
|
84
|
+
return np.array(self) == self._to_array(other)
|
85
|
+
|
86
|
+
def __ne__(self, other):
|
87
|
+
return np.array(self) != self._to_array(other)
|
88
|
+
|
89
|
+
def __array__(self, dtype: type | None = None, copy: bool = True):
|
90
|
+
arr = np.array([self._a, self._b, self._c], dtype=dtype)
|
91
|
+
if copy:
|
92
|
+
return arr.copy()
|
93
|
+
else:
|
94
|
+
return arr
|
95
|
+
|
96
|
+
def _to_array(self, other):
|
97
|
+
if isinstance(other, Vec3i):
|
98
|
+
return np.array(other)
|
99
|
+
else:
|
100
|
+
return other
|
101
|
+
|
102
|
+
@staticmethod
|
103
|
+
def _to_int(value) -> int:
|
104
|
+
if isinstance(value, (int, np.integer)):
|
105
|
+
return int(value)
|
106
|
+
elif isinstance(value, float):
|
107
|
+
if value.is_integer():
|
108
|
+
return int(value)
|
109
|
+
raise TypeError(
|
110
|
+
f"{type(value).__name__} value {value!r} is not"
|
111
|
+
" int, numpy integer, or whole float")
|
112
|
+
|
113
|
+
def to_tuple(self) -> tuple[int, int, int]:
|
114
|
+
return (self._a, self._b, self._c)
|
115
|
+
|
116
|
+
@classmethod
|
117
|
+
def from_tuple(cls, t: tuple[int, int, int]) -> Vec3i:
|
118
|
+
return cls(*t)
|
119
|
+
|
120
|
+
def to_nbt(self) -> nbtlib.Compound:
|
121
|
+
return nbtlib.Compound({
|
122
|
+
"x": nbtlib.Int(self._a),
|
123
|
+
"y": nbtlib.Int(self._b),
|
124
|
+
"z": nbtlib.Int(self._c),
|
125
|
+
})
|
126
|
+
|
127
|
+
@classmethod
|
128
|
+
def from_nbt(cls, nbt: nbtlib.Compound) -> Vec3i:
|
129
|
+
return cls(int(nbt["x"]), int(nbt["y"]), int(nbt["z"]))
|
130
|
+
|
131
|
+
|
132
|
+
@dataclass(frozen=True)
|
133
|
+
class BlockPosition(Vec3i):
|
134
|
+
|
135
|
+
@property
|
136
|
+
def x(self) -> int:
|
137
|
+
return self._a
|
138
|
+
|
139
|
+
@property
|
140
|
+
def y(self) -> int:
|
141
|
+
return self._b
|
142
|
+
|
143
|
+
@property
|
144
|
+
def z(self) -> int:
|
145
|
+
return self._c
|
146
|
+
|
147
|
+
def __repr__(self) -> str:
|
148
|
+
return f"{type(self).__name__}(x={self.x}, y={self.y}, z={self.z})"
|
149
|
+
|
150
|
+
|
151
|
+
@dataclass(frozen=True)
|
152
|
+
class Size3D(Vec3i):
|
153
|
+
|
154
|
+
@property
|
155
|
+
def width(self) -> int:
|
156
|
+
return self._a
|
157
|
+
|
158
|
+
@property
|
159
|
+
def height(self) -> int:
|
160
|
+
return self._b
|
161
|
+
|
162
|
+
@property
|
163
|
+
def length(self) -> int:
|
164
|
+
return self._c
|
165
|
+
|
166
|
+
def __repr__(self) -> str:
|
167
|
+
return (
|
168
|
+
f"{type(self).__name__}("
|
169
|
+
f"width={self.width}, height={self.height}, length={self.length})")
|