OpenRCT2-ObjectCommon 0.2.3__tar.gz → 0.2.5__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.
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/PKG-INFO +2 -2
- openrct2_objectcommon-0.2.5/openrct2_object_common/blender/bake.py +163 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/props.py +5 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/pyproject.toml +2 -2
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/test_blender_shared.py +82 -2
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/.github/workflows/lint.yml +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/.github/workflows/publish.yml +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/.github/workflows/pytest.yml +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/.gitignore +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/.yamllint.yaml +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/LICENSE +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/README.md +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/__init__.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/__init__.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/lights.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/mesh_extract.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/modal.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/cli.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/config.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/objectjson.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/parkobj.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/placement.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/py.typed +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/testing.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/scripts/ci/set_version.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/conftest.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/test_blender.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/test_cli.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/test_config.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/test_objectjson.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/test_parkobj.py +0 -0
- {openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/tests/test_placement.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OpenRCT2-ObjectCommon
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Shared config, CLI, .parkobj, and object.json scaffolding for OpenRCT2 object generators.
|
|
5
5
|
Project-URL: Homepage, https://github.com/alex-parisi/OpenRCT2-ObjectCommon
|
|
6
6
|
Project-URL: Repository, https://github.com/alex-parisi/OpenRCT2-ObjectCommon
|
|
@@ -9,7 +9,7 @@ License-Expression: GPL-3.0-or-later
|
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Requires-Dist: numpy>=1.26
|
|
12
|
-
Requires-Dist: openrct2-x7-renderer>=0.3.
|
|
12
|
+
Requires-Dist: openrct2-x7-renderer>=0.3.9
|
|
13
13
|
Requires-Dist: pillow>=10.0
|
|
14
14
|
Requires-Dist: pyyaml>=6.0
|
|
15
15
|
Provides-Extra: blender
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Bake a material's procedural shader-node graph to an albedo texture.
|
|
2
|
+
|
|
3
|
+
The standalone renderer has no Cycles/``bpy`` at shade time, so it can't evaluate
|
|
4
|
+
Blender's procedural node graphs directly. Instead, when a material opts in
|
|
5
|
+
(``bake_procedural``), the add-on bakes the node graph's albedo (Cycles
|
|
6
|
+
``DIFFUSE``/``COLOR`` pass — colour without lighting, since the renderer does its
|
|
7
|
+
own shading) into an image at export and feeds it through the normal texture
|
|
8
|
+
path. Baked textures are UV-locked, so they are rotation-stable across sprites.
|
|
9
|
+
|
|
10
|
+
Shared by both add-ons. ``bpy``-dependent orchestration lives in
|
|
11
|
+
:func:`bake_materials`; :func:`_image_to_texture` is kept ``bpy``-free so it can
|
|
12
|
+
be unit-tested without Blender.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from openrct2_x7_renderer.mesh import Texture
|
|
19
|
+
|
|
20
|
+
from .mesh_extract import SceneError
|
|
21
|
+
|
|
22
|
+
_TARGET_NODE = "__orct2_bake_target"
|
|
23
|
+
|
|
24
|
+
# (identifier, label, description) tuples for a bpy EnumProperty; both add-ons
|
|
25
|
+
# expose the same bake resolutions.
|
|
26
|
+
BAKE_RESOLUTION_ITEMS = [
|
|
27
|
+
("64", "64×64", "Bake at 64×64"),
|
|
28
|
+
("128", "128×128", "Bake at 128×128"),
|
|
29
|
+
("256", "256×256", "Bake at 256×256"),
|
|
30
|
+
("512", "512×512", "Bake at 512×512"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _image_to_texture(image) -> Texture:
|
|
35
|
+
"""Convert a baked Blender image into a linear-RGB :class:`Texture`.
|
|
36
|
+
|
|
37
|
+
Blender stores image rows bottom-up; the renderer samples top-left, so the
|
|
38
|
+
rows are flipped (mirroring the UV V-flip in ``mesh_extract.extract_mesh``).
|
|
39
|
+
Float images are already linear, matching what the renderer expects.
|
|
40
|
+
"""
|
|
41
|
+
width, height = int(image.size[0]), int(image.size[1])
|
|
42
|
+
flat = np.asarray(image.pixels[:], dtype=np.float32).reshape(height, width, 4)
|
|
43
|
+
rgb = np.ascontiguousarray(flat[::-1, :, :3])
|
|
44
|
+
return Texture(width=width, height=height, pixels=rgb)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _bake_settings(mat, prop_attr):
|
|
48
|
+
"""The add-on's per-material settings if this material opts into baking."""
|
|
49
|
+
s = getattr(mat, prop_attr, None)
|
|
50
|
+
if s is not None and getattr(s, "bake_procedural", False):
|
|
51
|
+
return s
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _add_target_node(mat, image):
|
|
56
|
+
"""Add (and activate) an Image Texture node Cycles will bake into."""
|
|
57
|
+
mat.use_nodes = True
|
|
58
|
+
nodes = mat.node_tree.nodes
|
|
59
|
+
node = nodes.new("ShaderNodeTexImage")
|
|
60
|
+
node.name = _TARGET_NODE
|
|
61
|
+
node.image = image
|
|
62
|
+
node.select = True
|
|
63
|
+
nodes.active = node
|
|
64
|
+
return node
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _select_only(view_layer, obj) -> None:
|
|
68
|
+
for other in view_layer.objects:
|
|
69
|
+
other.select_set(False)
|
|
70
|
+
obj.select_set(True)
|
|
71
|
+
view_layer.objects.active = obj
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def bake_materials(context, objects, *, prop_attr) -> dict:
|
|
75
|
+
"""Bake every opted-in material on *objects* to a texture.
|
|
76
|
+
|
|
77
|
+
Returns ``{bpy.types.Material: Texture}``. Main-thread only (drives Cycles via
|
|
78
|
+
``bpy.ops``). Raises :class:`SceneError` with author-facing guidance when a
|
|
79
|
+
to-bake object has no UV map. Render engine, selection and the touched
|
|
80
|
+
materials' ``use_nodes`` are saved and restored.
|
|
81
|
+
|
|
82
|
+
*prop_attr* is the add-on's material settings attribute (``"vgs_material"`` /
|
|
83
|
+
``"vg_material"``); each opted-in material's ``bake_resolution`` sets its size.
|
|
84
|
+
"""
|
|
85
|
+
import bpy
|
|
86
|
+
|
|
87
|
+
# Plan: per object, the opted-in materials and their settings.
|
|
88
|
+
plan = []
|
|
89
|
+
for obj in objects:
|
|
90
|
+
if obj.type != "MESH":
|
|
91
|
+
continue
|
|
92
|
+
wanted = {}
|
|
93
|
+
for slot in obj.material_slots:
|
|
94
|
+
if slot.material is None:
|
|
95
|
+
continue
|
|
96
|
+
settings = _bake_settings(slot.material, prop_attr)
|
|
97
|
+
if settings is not None:
|
|
98
|
+
wanted[slot.material] = settings
|
|
99
|
+
if not wanted:
|
|
100
|
+
continue
|
|
101
|
+
if not obj.data.uv_layers:
|
|
102
|
+
name = next(iter(wanted)).name
|
|
103
|
+
raise SceneError(
|
|
104
|
+
f"{obj.name} / {name}: bake needs a UV map — "
|
|
105
|
+
"unwrap the object (U ▸ Smart UV Project)."
|
|
106
|
+
)
|
|
107
|
+
plan.append((obj, wanted))
|
|
108
|
+
if not plan:
|
|
109
|
+
return {}
|
|
110
|
+
|
|
111
|
+
scene = context.scene
|
|
112
|
+
view_layer = context.view_layer
|
|
113
|
+
prev_engine = scene.render.engine
|
|
114
|
+
prev_active = view_layer.objects.active
|
|
115
|
+
prev_selected = [o for o in view_layer.objects if o.select_get()]
|
|
116
|
+
|
|
117
|
+
result: dict = {}
|
|
118
|
+
try:
|
|
119
|
+
scene.render.engine = "CYCLES"
|
|
120
|
+
for obj, wanted in plan:
|
|
121
|
+
# Every material slot used by the mesh needs an active image node for
|
|
122
|
+
# the bake to succeed; non-target slots get a 1x1 throwaway.
|
|
123
|
+
touched = [] # (mat, node, image, prev_use_nodes, keep)
|
|
124
|
+
for slot in obj.material_slots:
|
|
125
|
+
mat = slot.material
|
|
126
|
+
if mat is None:
|
|
127
|
+
continue
|
|
128
|
+
keep = mat in wanted
|
|
129
|
+
res = int(wanted[mat].bake_resolution) if keep else 1
|
|
130
|
+
prev_use_nodes = mat.use_nodes
|
|
131
|
+
image = bpy.data.images.new(
|
|
132
|
+
f"__orct2_bake_{mat.name}",
|
|
133
|
+
width=res,
|
|
134
|
+
height=res,
|
|
135
|
+
float_buffer=True,
|
|
136
|
+
alpha=False,
|
|
137
|
+
)
|
|
138
|
+
node = _add_target_node(mat, image)
|
|
139
|
+
touched.append((mat, node, image, prev_use_nodes, keep))
|
|
140
|
+
|
|
141
|
+
_select_only(view_layer, obj)
|
|
142
|
+
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"COLOR"}, margin=4)
|
|
143
|
+
|
|
144
|
+
for mat, node, image, prev_use_nodes, keep in touched:
|
|
145
|
+
if keep:
|
|
146
|
+
result[mat] = _image_to_texture(image)
|
|
147
|
+
mat.node_tree.nodes.remove(node)
|
|
148
|
+
mat.use_nodes = prev_use_nodes
|
|
149
|
+
bpy.data.images.remove(image)
|
|
150
|
+
finally:
|
|
151
|
+
scene.render.engine = prev_engine
|
|
152
|
+
for o in view_layer.objects:
|
|
153
|
+
o.select_set(o in prev_selected)
|
|
154
|
+
view_layer.objects.active = prev_active
|
|
155
|
+
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def draw_bake(layout, ms) -> None:
|
|
160
|
+
"""Draw the bake toggle (+ resolution when enabled) into *layout*."""
|
|
161
|
+
layout.prop(ms, "bake_procedural")
|
|
162
|
+
if ms.bake_procedural:
|
|
163
|
+
layout.prop(ms, "bake_resolution")
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/props.py
RENAMED
|
@@ -62,6 +62,11 @@ DITHER_MODE_ITEMS = [
|
|
|
62
62
|
"Bayer (frame-stable)",
|
|
63
63
|
"Ordered dither locked to screen position; stable across animation frames",
|
|
64
64
|
),
|
|
65
|
+
(
|
|
66
|
+
"blue_noise",
|
|
67
|
+
"Blue noise (frame-stable)",
|
|
68
|
+
"Like Bayer but a blue-noise mask; less perceptible residual motion under rotation",
|
|
69
|
+
),
|
|
65
70
|
("none", "None", "No dithering; flat palette quantisation"),
|
|
66
71
|
]
|
|
67
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "OpenRCT2-ObjectCommon"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5"
|
|
4
4
|
description = "Shared config, CLI, .parkobj, and object.json scaffolding for OpenRCT2 object generators."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -9,7 +9,7 @@ authors = [
|
|
|
9
9
|
{ name = "Alex Parisi", email = "alex@atparisi.com" }
|
|
10
10
|
]
|
|
11
11
|
dependencies = [
|
|
12
|
-
"openrct2-x7-renderer>=0.3.
|
|
12
|
+
"openrct2-x7-renderer>=0.3.9",
|
|
13
13
|
"numpy>=1.26",
|
|
14
14
|
"pillow>=10.0",
|
|
15
15
|
"pyyaml>=6.0",
|
|
@@ -7,8 +7,10 @@ collection), so these modules import and run without a Blender runtime.
|
|
|
7
7
|
from types import SimpleNamespace
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
|
+
import pytest
|
|
10
11
|
from mathutils import Matrix, Vector # provided by conftest fake
|
|
11
|
-
from openrct2_object_common.blender import mesh_extract, props
|
|
12
|
+
from openrct2_object_common.blender import bake, mesh_extract, props
|
|
13
|
+
from openrct2_object_common.blender.mesh_extract import SceneError
|
|
12
14
|
from openrct2_x7_renderer.constants import MaterialFlag
|
|
13
15
|
|
|
14
16
|
# ===========================================================================
|
|
@@ -50,8 +52,10 @@ def test_shared_light_propertygroup_defined():
|
|
|
50
52
|
def test_dither_mode_items_match_renderer_modes():
|
|
51
53
|
# The add-on enum identifiers must be exactly the strings make_context /
|
|
52
54
|
# Context accept, and the default must be one of them.
|
|
55
|
+
from openrct2_x7_renderer.ray_trace import DITHER_MODES
|
|
56
|
+
|
|
53
57
|
ids = {ident for ident, _label, _desc in props.DITHER_MODE_ITEMS}
|
|
54
|
-
assert ids ==
|
|
58
|
+
assert ids == set(DITHER_MODES)
|
|
55
59
|
assert props.DEFAULT_DITHER_MODE in ids
|
|
56
60
|
|
|
57
61
|
|
|
@@ -313,3 +317,79 @@ def test_load_preview_returns_none_when_both_fail(monkeypatch):
|
|
|
313
317
|
monkeypatch.setattr(mesh_extract, "read_png", _boom)
|
|
314
318
|
monkeypatch.setattr(mesh_extract, "quantize_to_indexed", _boom)
|
|
315
319
|
assert mesh_extract.load_preview("/img.png") is None
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# ===========================================================================
|
|
323
|
+
# bake.py
|
|
324
|
+
# ===========================================================================
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def test_image_to_texture_flips_rows_and_drops_alpha():
|
|
328
|
+
# 2x2 RGBA float image, Blender's bottom-up row order: bottom red, top blue.
|
|
329
|
+
pixels = [
|
|
330
|
+
1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, # bottom row
|
|
331
|
+
0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, # top row
|
|
332
|
+
]
|
|
333
|
+
image = SimpleNamespace(size=(2, 2), pixels=pixels)
|
|
334
|
+
tex = bake._image_to_texture(image)
|
|
335
|
+
assert tex.width == 2
|
|
336
|
+
assert tex.height == 2
|
|
337
|
+
assert tex.pixels.shape == (2, 2, 3)
|
|
338
|
+
# After the V-flip, row 0 is the top (blue), row 1 the bottom (red).
|
|
339
|
+
assert np.allclose(tex.pixels[0, 0], [0.0, 0.0, 1.0])
|
|
340
|
+
assert np.allclose(tex.pixels[1, 0], [1.0, 0.0, 0.0])
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class _BakeMat:
|
|
344
|
+
# A real bpy Material is hashable (by identity); SimpleNamespace is not,
|
|
345
|
+
# so use a plain class for materials used as dict keys.
|
|
346
|
+
def __init__(self, name, *, on, res="256"):
|
|
347
|
+
self.name = name
|
|
348
|
+
self.vg_material = SimpleNamespace(bake_procedural=on, bake_resolution=res)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _bake_mat(name, *, on, res="256"):
|
|
352
|
+
return _BakeMat(name, on=on, res=res)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _bake_obj(materials, *, has_uv):
|
|
356
|
+
return SimpleNamespace(
|
|
357
|
+
name="Cube",
|
|
358
|
+
type="MESH",
|
|
359
|
+
material_slots=[SimpleNamespace(material=m) for m in materials],
|
|
360
|
+
data=SimpleNamespace(uv_layers=[object()] if has_uv else []),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def test_bake_materials_no_opted_in_materials_returns_empty():
|
|
365
|
+
obj = _bake_obj([_bake_mat("Plain", on=False)], has_uv=True)
|
|
366
|
+
assert bake.bake_materials(SimpleNamespace(), [obj], prop_attr="vg_material") == {}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_bake_materials_skips_non_mesh_objects():
|
|
370
|
+
obj = SimpleNamespace(type="EMPTY")
|
|
371
|
+
assert bake.bake_materials(SimpleNamespace(), [obj], prop_attr="vg_material") == {}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_bake_materials_missing_uv_raises_guidance():
|
|
375
|
+
obj = _bake_obj([_bake_mat("Wood", on=True)], has_uv=False)
|
|
376
|
+
with pytest.raises(SceneError, match="UV map"):
|
|
377
|
+
bake.bake_materials(SimpleNamespace(), [obj], prop_attr="vg_material")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class _FakeLayout:
|
|
381
|
+
def __init__(self):
|
|
382
|
+
self.props: list[str] = []
|
|
383
|
+
|
|
384
|
+
def prop(self, _data, name):
|
|
385
|
+
self.props.append(name)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def test_draw_bake_shows_resolution_only_when_enabled():
|
|
389
|
+
on = _FakeLayout()
|
|
390
|
+
bake.draw_bake(on, SimpleNamespace(bake_procedural=True, bake_resolution="256"))
|
|
391
|
+
assert on.props == ["bake_procedural", "bake_resolution"]
|
|
392
|
+
|
|
393
|
+
off = _FakeLayout()
|
|
394
|
+
bake.draw_bake(off, SimpleNamespace(bake_procedural=False, bake_resolution="256"))
|
|
395
|
+
assert off.props == ["bake_procedural"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/lights.py
RENAMED
|
File without changes
|
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/blender/modal.py
RENAMED
|
File without changes
|
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/config.py
RENAMED
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/objectjson.py
RENAMED
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/parkobj.py
RENAMED
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/placement.py
RENAMED
|
File without changes
|
|
File without changes
|
{openrct2_objectcommon-0.2.3 → openrct2_objectcommon-0.2.5}/openrct2_object_common/testing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|