maps4fs 1.2.4__py3-none-any.whl → 1.5.0__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.
- maps4fs/__init__.py +10 -1
- maps4fs/generator/background.py +27 -29
- maps4fs/generator/component.py +80 -24
- maps4fs/generator/config.py +1 -1
- maps4fs/generator/dem.py +11 -13
- maps4fs/generator/game.py +1 -1
- maps4fs/generator/grle.py +18 -14
- maps4fs/generator/i3d.py +192 -35
- maps4fs/generator/map.py +219 -7
- maps4fs/generator/texture.py +108 -34
- {maps4fs-1.2.4.dist-info → maps4fs-1.5.0.dist-info}/METADATA +68 -35
- maps4fs-1.5.0.dist-info/RECORD +21 -0
- maps4fs-1.2.4.dist-info/RECORD +0 -21
- {maps4fs-1.2.4.dist-info → maps4fs-1.5.0.dist-info}/LICENSE.md +0 -0
- {maps4fs-1.2.4.dist-info → maps4fs-1.5.0.dist-info}/WHEEL +0 -0
- {maps4fs-1.2.4.dist-info → maps4fs-1.5.0.dist-info}/top_level.txt +0 -0
maps4fs/generator/i3d.py
CHANGED
@@ -19,7 +19,8 @@ DISPLACEMENT_LAYER_SIZE_FOR_BIG_MAPS = 32768
|
|
19
19
|
DEFAULT_MAX_LOD_DISTANCE = 10000
|
20
20
|
DEFAULT_MAX_LOD_OCCLUDER_DISTANCE = 10000
|
21
21
|
NODE_ID_STARTING_VALUE = 2000
|
22
|
-
|
22
|
+
SPLINES_NODE_ID_STARTING_VALUE = 5000
|
23
|
+
TREE_NODE_ID_STARTING_VALUE = 10000
|
23
24
|
DEFAULT_FOREST_DENSITY = 10
|
24
25
|
|
25
26
|
|
@@ -43,28 +44,25 @@ class I3d(Component):
|
|
43
44
|
def preprocess(self) -> None:
|
44
45
|
"""Gets the path to the map I3D file from the game instance and saves it to the instance
|
45
46
|
attribute. If the game does not support I3D files, the attribute is set to None."""
|
46
|
-
self.auto_process = self.kwargs.get("auto_process", False)
|
47
|
-
|
48
47
|
try:
|
49
48
|
self._map_i3d_path = self.game.i3d_file_path(self.map_directory)
|
50
49
|
self.logger.debug("Map I3D path: %s.", self._map_i3d_path)
|
51
50
|
except NotImplementedError:
|
52
|
-
self.logger.
|
51
|
+
self.logger.warning("I3D file processing is not implemented for this game.")
|
53
52
|
self._map_i3d_path = None
|
54
53
|
|
55
|
-
self.forest_density = self.kwargs.get("forest_density", DEFAULT_FOREST_DENSITY)
|
56
|
-
self.logger.info("Forest density: %s.", self.forest_density)
|
57
|
-
|
58
54
|
def process(self) -> None:
|
59
55
|
"""Updates the map I3D file with the default settings."""
|
60
56
|
self._update_i3d_file()
|
61
57
|
self._add_fields()
|
62
|
-
self.
|
58
|
+
if self.game.code == "FS25":
|
59
|
+
self._add_forests()
|
60
|
+
self._add_splines()
|
63
61
|
|
64
62
|
def _get_tree(self) -> ET.ElementTree | None:
|
65
63
|
"""Returns the ElementTree instance of the map I3D file."""
|
66
64
|
if not self._map_i3d_path:
|
67
|
-
self.logger.
|
65
|
+
self.logger.debug("I3D is not obtained, skipping the update.")
|
68
66
|
return None
|
69
67
|
if not os.path.isfile(self._map_i3d_path):
|
70
68
|
self.logger.warning("I3D file not found: %s.", self._map_i3d_path)
|
@@ -84,7 +82,7 @@ class I3d(Component):
|
|
84
82
|
root = tree.getroot()
|
85
83
|
for map_elem in root.iter("Scene"):
|
86
84
|
for terrain_elem in map_elem.iter("TerrainTransformGroup"):
|
87
|
-
if self.auto_process:
|
85
|
+
if self.map.dem_settings.auto_process:
|
88
86
|
terrain_elem.set("heightScale", str(DEFAULT_HEIGHT_SCALE))
|
89
87
|
self.logger.debug(
|
90
88
|
"heightScale attribute set to %s in TerrainTransformGroup element.",
|
@@ -122,7 +120,7 @@ class I3d(Component):
|
|
122
120
|
)
|
123
121
|
|
124
122
|
tree.write(self._map_i3d_path) # type: ignore
|
125
|
-
self.logger.
|
123
|
+
self.logger.debug("Map I3D file saved to: %s.", self._map_i3d_path)
|
126
124
|
|
127
125
|
def previews(self) -> list[str]:
|
128
126
|
"""Returns a list of paths to the preview images (empty list).
|
@@ -133,6 +131,157 @@ class I3d(Component):
|
|
133
131
|
"""
|
134
132
|
return []
|
135
133
|
|
134
|
+
# pylint: disable=R0914, R0915
|
135
|
+
def _add_splines(self) -> None:
|
136
|
+
"""Adds splines to the map I3D file."""
|
137
|
+
splines_i3d_path = os.path.join(self.map_directory, "map", "splines.i3d")
|
138
|
+
if not os.path.isfile(splines_i3d_path):
|
139
|
+
self.logger.warning("Splines I3D file not found: %s.", splines_i3d_path)
|
140
|
+
return
|
141
|
+
|
142
|
+
tree = ET.parse(splines_i3d_path)
|
143
|
+
|
144
|
+
textures_info_layer_path = self.get_infolayer_path("textures")
|
145
|
+
if not textures_info_layer_path:
|
146
|
+
return
|
147
|
+
|
148
|
+
with open(textures_info_layer_path, "r", encoding="utf-8") as textures_info_layer_file:
|
149
|
+
textures_info_layer = json.load(textures_info_layer_file)
|
150
|
+
|
151
|
+
roads_polylines: list[list[tuple[int, int]]] | None = textures_info_layer.get(
|
152
|
+
"roads_polylines"
|
153
|
+
)
|
154
|
+
|
155
|
+
if not roads_polylines:
|
156
|
+
self.logger.warning("Roads polylines data not found in textures info layer.")
|
157
|
+
return
|
158
|
+
|
159
|
+
self.logger.info("Found %s roads polylines in textures info layer.", len(roads_polylines))
|
160
|
+
self.logger.debug("Starging to add roads polylines to the I3D file.")
|
161
|
+
|
162
|
+
root = tree.getroot()
|
163
|
+
# Find <Shapes> element in the I3D file.
|
164
|
+
shapes_node = root.find(".//Shapes")
|
165
|
+
# Find <Scene> element in the I3D file.
|
166
|
+
scene_node = root.find(".//Scene")
|
167
|
+
|
168
|
+
# Read the not resized DEM to obtain Z values for spline points.
|
169
|
+
background_component = self.map.get_component("Background")
|
170
|
+
if not background_component:
|
171
|
+
self.logger.warning("Background component not found.")
|
172
|
+
return
|
173
|
+
|
174
|
+
# pylint: disable=no-member
|
175
|
+
not_resized_dem = cv2.imread(
|
176
|
+
background_component.not_resized_path, cv2.IMREAD_UNCHANGED # type: ignore
|
177
|
+
)
|
178
|
+
self.logger.debug(
|
179
|
+
"Not resized DEM loaded from: %s. Shape: %s.",
|
180
|
+
background_component.not_resized_path, # type: ignore
|
181
|
+
not_resized_dem.shape,
|
182
|
+
)
|
183
|
+
dem_x_size, dem_y_size = not_resized_dem.shape
|
184
|
+
|
185
|
+
if shapes_node is not None and scene_node is not None:
|
186
|
+
node_id = SPLINES_NODE_ID_STARTING_VALUE
|
187
|
+
user_attributes_node = root.find(".//UserAttributes")
|
188
|
+
if user_attributes_node is None:
|
189
|
+
self.logger.warning("UserAttributes node not found in I3D file.")
|
190
|
+
return
|
191
|
+
|
192
|
+
for road_id, road in enumerate(roads_polylines, start=1):
|
193
|
+
# Add to scene node
|
194
|
+
# <Shape name="spline01_CSV" translation="0 0 0" nodeId="11" shapeId="11"/>
|
195
|
+
|
196
|
+
try:
|
197
|
+
fitted_road = self.fit_object_into_bounds(
|
198
|
+
linestring_points=road, angle=self.rotation
|
199
|
+
)
|
200
|
+
except ValueError as e:
|
201
|
+
self.logger.warning(
|
202
|
+
"Road %s could not be fitted into the map bounds with error: %s",
|
203
|
+
road_id,
|
204
|
+
e,
|
205
|
+
)
|
206
|
+
continue
|
207
|
+
|
208
|
+
self.logger.debug("Road %s has %s points.", road_id, len(fitted_road))
|
209
|
+
fitted_road = self.interpolate_points(
|
210
|
+
fitted_road, num_points=self.map.spline_settings.spline_density
|
211
|
+
)
|
212
|
+
self.logger.debug(
|
213
|
+
"Road %s has %s points after interpolation.", road_id, len(fitted_road)
|
214
|
+
)
|
215
|
+
|
216
|
+
spline_name = f"spline{road_id}"
|
217
|
+
|
218
|
+
shape_node = ET.Element("Shape")
|
219
|
+
shape_node.set("name", spline_name)
|
220
|
+
shape_node.set("translation", "0 0 0")
|
221
|
+
shape_node.set("nodeId", str(node_id))
|
222
|
+
shape_node.set("shapeId", str(node_id))
|
223
|
+
|
224
|
+
scene_node.append(shape_node)
|
225
|
+
|
226
|
+
road_ccs = [self.top_left_coordinates_to_center(point) for point in fitted_road]
|
227
|
+
|
228
|
+
# Add to shapes node
|
229
|
+
# <NurbsCurve name="spline01_CSV" shapeId="11" degree="3" form="open">
|
230
|
+
|
231
|
+
nurbs_curve_node = ET.Element("NurbsCurve")
|
232
|
+
nurbs_curve_node.set("name", spline_name)
|
233
|
+
nurbs_curve_node.set("shapeId", str(node_id))
|
234
|
+
nurbs_curve_node.set("degree", "3")
|
235
|
+
nurbs_curve_node.set("form", "open")
|
236
|
+
|
237
|
+
# Now for each point in the road add the following entry to nurbs_curve_node
|
238
|
+
# <cv c="-224.548401, 427.297546, -2047.570312" />
|
239
|
+
# The second coordinate (Z) will be 0 at the moment.
|
240
|
+
|
241
|
+
for point_ccs, point in zip(road_ccs, fitted_road):
|
242
|
+
cx, cy = point_ccs
|
243
|
+
x, y = point
|
244
|
+
|
245
|
+
x = int(x)
|
246
|
+
y = int(y)
|
247
|
+
|
248
|
+
x = max(0, min(x, dem_x_size - 1))
|
249
|
+
y = max(0, min(y, dem_y_size - 1))
|
250
|
+
|
251
|
+
z = not_resized_dem[y, x]
|
252
|
+
z /= 32 # Yes, it's a magic number here.
|
253
|
+
|
254
|
+
cv_node = ET.Element("cv")
|
255
|
+
cv_node.set("c", f"{cx}, {z}, {cy}")
|
256
|
+
|
257
|
+
nurbs_curve_node.append(cv_node)
|
258
|
+
|
259
|
+
shapes_node.append(nurbs_curve_node)
|
260
|
+
|
261
|
+
# Add UserAttributes to the shape node.
|
262
|
+
# <UserAttribute nodeId="5000">
|
263
|
+
# <Attribute name="maxSpeedScale" type="integer" value="1"/>
|
264
|
+
# <Attribute name="speedLimit" type="integer" value="100"/>
|
265
|
+
# </UserAttribute>
|
266
|
+
|
267
|
+
user_attribute_node = ET.Element("UserAttribute")
|
268
|
+
user_attribute_node.set("nodeId", str(node_id))
|
269
|
+
|
270
|
+
attributes = [
|
271
|
+
("maxSpeedScale", "integer", "1"),
|
272
|
+
("speedLimit", "integer", "100"),
|
273
|
+
]
|
274
|
+
|
275
|
+
for name, attr_type, value in attributes:
|
276
|
+
user_attribute_node.append(I3d.create_attribute_node(name, attr_type, value))
|
277
|
+
|
278
|
+
user_attributes_node.append(user_attribute_node) # type: ignore
|
279
|
+
|
280
|
+
node_id += 1
|
281
|
+
|
282
|
+
tree.write(splines_i3d_path) # type: ignore
|
283
|
+
self.logger.debug("Splines I3D file saved to: %s.", splines_i3d_path)
|
284
|
+
|
136
285
|
# pylint: disable=R0914, R0915
|
137
286
|
def _add_fields(self) -> None:
|
138
287
|
"""Adds fields to the map I3D file."""
|
@@ -170,7 +319,9 @@ class I3d(Component):
|
|
170
319
|
|
171
320
|
for field in fields:
|
172
321
|
try:
|
173
|
-
fitted_field = self.
|
322
|
+
fitted_field = self.fit_object_into_bounds(
|
323
|
+
polygon_points=field, angle=self.rotation
|
324
|
+
)
|
174
325
|
except ValueError as e:
|
175
326
|
self.logger.warning(
|
176
327
|
"Field %s could not be fitted into the map bounds with error: %s",
|
@@ -239,7 +390,7 @@ class I3d(Component):
|
|
239
390
|
field_id += 1
|
240
391
|
|
241
392
|
tree.write(self._map_i3d_path) # type: ignore
|
242
|
-
self.logger.
|
393
|
+
self.logger.debug("Map I3D file saved to: %s.", self._map_i3d_path)
|
243
394
|
|
244
395
|
def get_name_indicator_node(self, node_id: int, field_id: int) -> tuple[ET.Element, int]:
|
245
396
|
"""Creates a name indicator node with given node ID and field ID.
|
@@ -332,24 +483,28 @@ class I3d(Component):
|
|
332
483
|
# pylint: disable=R0911
|
333
484
|
def _add_forests(self) -> None:
|
334
485
|
"""Adds forests to the map I3D file."""
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
486
|
+
custom_schema = self.kwargs.get("tree_custom_schema")
|
487
|
+
if custom_schema:
|
488
|
+
tree_schema = custom_schema
|
489
|
+
else:
|
490
|
+
try:
|
491
|
+
tree_schema_path = self.game.tree_schema
|
492
|
+
except ValueError:
|
493
|
+
self.logger.warning("Tree schema path not set for the Game %s.", self.game.code)
|
494
|
+
return
|
495
|
+
|
496
|
+
if not os.path.isfile(tree_schema_path):
|
497
|
+
self.logger.warning("Tree schema file was not found: %s.", tree_schema_path)
|
498
|
+
return
|
499
|
+
|
500
|
+
try:
|
501
|
+
with open(tree_schema_path, "r", encoding="utf-8") as tree_schema_file:
|
502
|
+
tree_schema = json.load(tree_schema_file) # type: ignore
|
503
|
+
except json.JSONDecodeError as e:
|
504
|
+
self.logger.warning(
|
505
|
+
"Could not load tree schema from %s with error: %s", tree_schema_path, e
|
506
|
+
)
|
507
|
+
return
|
353
508
|
|
354
509
|
texture_component: Texture | None = self.map.get_component("Texture") # type: ignore
|
355
510
|
if not texture_component:
|
@@ -395,14 +550,16 @@ class I3d(Component):
|
|
395
550
|
forest_image = cv2.imread(forest_image_path, cv2.IMREAD_UNCHANGED)
|
396
551
|
|
397
552
|
tree_count = 0
|
398
|
-
for x, y in self.non_empty_pixels(forest_image, step=self.forest_density):
|
553
|
+
for x, y in self.non_empty_pixels(forest_image, step=self.map.i3d_settings.forest_density):
|
399
554
|
xcs, ycs = self.top_left_coordinates_to_center((x, y))
|
400
555
|
node_id += 1
|
401
556
|
|
402
557
|
rotation = randint(-180, 180)
|
403
|
-
xcs, ycs = self.randomize_coordinates(
|
558
|
+
xcs, ycs = self.randomize_coordinates( # type: ignore
|
559
|
+
(xcs, ycs), self.map.i3d_settings.forest_density
|
560
|
+
)
|
404
561
|
|
405
|
-
random_tree = choice(tree_schema)
|
562
|
+
random_tree = choice(tree_schema) # type: ignore
|
406
563
|
tree_name = random_tree["name"]
|
407
564
|
tree_id = random_tree["reference_id"]
|
408
565
|
|
@@ -420,7 +577,7 @@ class I3d(Component):
|
|
420
577
|
self.logger.info("Added %s trees to the I3D file.", tree_count)
|
421
578
|
|
422
579
|
tree.write(self._map_i3d_path) # type: ignore
|
423
|
-
self.logger.
|
580
|
+
self.logger.debug("Map I3D file saved to: %s.", self._map_i3d_path)
|
424
581
|
|
425
582
|
@staticmethod
|
426
583
|
def randomize_coordinates(coordinates: tuple[int, int], density: int) -> tuple[float, float]:
|
maps4fs/generator/map.py
CHANGED
@@ -2,16 +2,151 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
import json
|
5
6
|
import os
|
6
7
|
import shutil
|
7
8
|
from typing import Any, Generator
|
8
9
|
|
10
|
+
from pydantic import BaseModel
|
11
|
+
|
9
12
|
from maps4fs.generator.component import Component
|
10
13
|
from maps4fs.generator.game import Game
|
11
14
|
from maps4fs.logger import Logger
|
12
15
|
|
13
16
|
|
14
|
-
|
17
|
+
class SettingsModel(BaseModel):
|
18
|
+
"""Base class for settings models. It provides methods to convert settings to and from JSON."""
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def all_settings_to_json(cls) -> dict[str, dict[str, Any]]:
|
22
|
+
"""Get all settings of the current class and its subclasses as a dictionary.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
dict[str, dict[str, Any]]: Dictionary with settings of the current class and its
|
26
|
+
subclasses.
|
27
|
+
"""
|
28
|
+
all_settings = {}
|
29
|
+
for subclass in cls.__subclasses__():
|
30
|
+
all_settings[subclass.__name__] = subclass().model_dump()
|
31
|
+
|
32
|
+
return all_settings
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def all_settings_from_json(cls, data: dict) -> dict[str, SettingsModel]:
|
36
|
+
"""Create settings instances from JSON data.
|
37
|
+
|
38
|
+
Arguments:
|
39
|
+
data (dict): JSON data.
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
dict[str, Type[SettingsModel]]: Dictionary with settings instances.
|
43
|
+
"""
|
44
|
+
settings = {}
|
45
|
+
for subclass in cls.__subclasses__():
|
46
|
+
settings[subclass.__name__] = subclass(**data[subclass.__name__])
|
47
|
+
|
48
|
+
return settings
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def all_settings(cls) -> list[SettingsModel]:
|
52
|
+
"""Get all settings of the current class and its subclasses.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
list[SettingsModel]: List with settings of the current class and its subclasses.
|
56
|
+
"""
|
57
|
+
settings = []
|
58
|
+
for subclass in cls.__subclasses__():
|
59
|
+
settings.append(subclass())
|
60
|
+
|
61
|
+
return settings
|
62
|
+
|
63
|
+
|
64
|
+
class DEMSettings(SettingsModel):
|
65
|
+
"""Represents the advanced settings for DEM component.
|
66
|
+
|
67
|
+
Attributes:
|
68
|
+
auto_process (bool): use the auto preset to change the multiplier.
|
69
|
+
multiplier (int): multiplier for the heightmap, every pixel will be multiplied by this
|
70
|
+
value.
|
71
|
+
blur_radius (int): radius of the blur filter.
|
72
|
+
plateau (int): plateau height, will be added to each pixel.
|
73
|
+
water_depth (int): water depth, will be subtracted from each pixel where the water
|
74
|
+
is present.
|
75
|
+
"""
|
76
|
+
|
77
|
+
auto_process: bool = True
|
78
|
+
multiplier: int = 1
|
79
|
+
blur_radius: int = 35
|
80
|
+
plateau: int = 0
|
81
|
+
water_depth: int = 0
|
82
|
+
|
83
|
+
|
84
|
+
class BackgroundSettings(SettingsModel):
|
85
|
+
"""Represents the advanced settings for background component.
|
86
|
+
|
87
|
+
Attributes:
|
88
|
+
generate_background (bool): generate obj files for the background terrain.
|
89
|
+
generate_water (bool): generate obj files for the water.
|
90
|
+
resize_factor (int): resize factor for the background terrain and water.
|
91
|
+
It will be used as 1 / resize_factor of the original size.
|
92
|
+
"""
|
93
|
+
|
94
|
+
generate_background: bool = True
|
95
|
+
generate_water: bool = True
|
96
|
+
resize_factor: int = 8
|
97
|
+
|
98
|
+
|
99
|
+
class GRLESettings(SettingsModel):
|
100
|
+
"""Represents the advanced settings for GRLE component.
|
101
|
+
|
102
|
+
Attributes:
|
103
|
+
farmland_margin (int): margin around the farmland.
|
104
|
+
random_plants (bool): generate random plants on the map or use the default one.
|
105
|
+
add_farmyards (bool): If True, regions of frarmyards will be added to the map
|
106
|
+
without corresponding fields.
|
107
|
+
"""
|
108
|
+
|
109
|
+
farmland_margin: int = 0
|
110
|
+
random_plants: bool = True
|
111
|
+
add_farmyards: bool = False
|
112
|
+
|
113
|
+
|
114
|
+
class I3DSettings(SettingsModel):
|
115
|
+
"""Represents the advanced settings for I3D component.
|
116
|
+
|
117
|
+
Attributes:
|
118
|
+
forest_density (int): density of the forest (distance between trees).
|
119
|
+
"""
|
120
|
+
|
121
|
+
forest_density: int = 10
|
122
|
+
|
123
|
+
|
124
|
+
class TextureSettings(SettingsModel):
|
125
|
+
"""Represents the advanced settings for texture component.
|
126
|
+
|
127
|
+
Attributes:
|
128
|
+
dissolve (bool): dissolve the texture into several images.
|
129
|
+
fields_padding (int): padding around the fields.
|
130
|
+
skip_drains (bool): skip drains generation.
|
131
|
+
"""
|
132
|
+
|
133
|
+
dissolve: bool = False
|
134
|
+
fields_padding: int = 0
|
135
|
+
skip_drains: bool = False
|
136
|
+
|
137
|
+
|
138
|
+
class SplineSettings(SettingsModel):
|
139
|
+
"""Represents the advanced settings for spline component.
|
140
|
+
|
141
|
+
Attributes:
|
142
|
+
spline_density (int): the number of extra points that will be added between each two
|
143
|
+
existing points.
|
144
|
+
"""
|
145
|
+
|
146
|
+
spline_density: int = 2
|
147
|
+
|
148
|
+
|
149
|
+
# pylint: disable=R0913, R0902, R0914
|
15
150
|
class Map:
|
16
151
|
"""Class used to generate map using all components.
|
17
152
|
|
@@ -23,7 +158,7 @@ class Map:
|
|
23
158
|
logger (Any): Logger instance
|
24
159
|
"""
|
25
160
|
|
26
|
-
def __init__( # pylint: disable=R0917
|
161
|
+
def __init__( # pylint: disable=R0917, R0915
|
27
162
|
self,
|
28
163
|
game: Game,
|
29
164
|
coordinates: tuple[float, float],
|
@@ -31,6 +166,13 @@ class Map:
|
|
31
166
|
rotation: int,
|
32
167
|
map_directory: str,
|
33
168
|
logger: Any = None,
|
169
|
+
custom_osm: str | None = None,
|
170
|
+
dem_settings: DEMSettings = DEMSettings(),
|
171
|
+
background_settings: BackgroundSettings = BackgroundSettings(),
|
172
|
+
grle_settings: GRLESettings = GRLESettings(),
|
173
|
+
i3d_settings: I3DSettings = I3DSettings(),
|
174
|
+
texture_settings: TextureSettings = TextureSettings(),
|
175
|
+
spline_settings: SplineSettings = SplineSettings(),
|
34
176
|
**kwargs,
|
35
177
|
):
|
36
178
|
if not logger:
|
@@ -53,12 +195,65 @@ class Map:
|
|
53
195
|
|
54
196
|
self.logger.info("Game was set to %s", game.code)
|
55
197
|
|
56
|
-
self.
|
57
|
-
self.logger.info("
|
198
|
+
self.custom_osm = custom_osm
|
199
|
+
self.logger.info("Custom OSM file: %s", custom_osm)
|
200
|
+
|
201
|
+
# Make a copy of a custom osm file to the map directory, so it will be
|
202
|
+
# included in the output archive.
|
203
|
+
if custom_osm:
|
204
|
+
copy_path = os.path.join(self.map_directory, "custom_osm.osm")
|
205
|
+
shutil.copyfile(custom_osm, copy_path)
|
206
|
+
self.logger.debug("Custom OSM file copied to %s", copy_path)
|
207
|
+
|
208
|
+
self.dem_settings = dem_settings
|
209
|
+
self.logger.info("DEM settings: %s", dem_settings)
|
210
|
+
self.background_settings = background_settings
|
211
|
+
self.logger.info("Background settings: %s", background_settings)
|
212
|
+
self.grle_settings = grle_settings
|
213
|
+
self.logger.info("GRLE settings: %s", grle_settings)
|
214
|
+
self.i3d_settings = i3d_settings
|
215
|
+
self.logger.info("I3D settings: %s", i3d_settings)
|
216
|
+
self.texture_settings = texture_settings
|
217
|
+
self.logger.info("Texture settings: %s", texture_settings)
|
218
|
+
self.spline_settings = spline_settings
|
219
|
+
self.logger.info("Spline settings: %s", spline_settings)
|
58
220
|
|
59
221
|
os.makedirs(self.map_directory, exist_ok=True)
|
60
222
|
self.logger.debug("Map directory created: %s", self.map_directory)
|
61
223
|
|
224
|
+
settings = [
|
225
|
+
dem_settings,
|
226
|
+
background_settings,
|
227
|
+
grle_settings,
|
228
|
+
i3d_settings,
|
229
|
+
texture_settings,
|
230
|
+
spline_settings,
|
231
|
+
]
|
232
|
+
|
233
|
+
settings_json = {}
|
234
|
+
|
235
|
+
for setting in settings:
|
236
|
+
settings_json[setting.__class__.__name__] = setting.model_dump()
|
237
|
+
|
238
|
+
save_path = os.path.join(self.map_directory, "generation_settings.json")
|
239
|
+
|
240
|
+
with open(save_path, "w", encoding="utf-8") as file:
|
241
|
+
json.dump(settings_json, file, indent=4)
|
242
|
+
|
243
|
+
self.texture_custom_schema = kwargs.get("texture_custom_schema", None)
|
244
|
+
if self.texture_custom_schema:
|
245
|
+
save_path = os.path.join(self.map_directory, "texture_custom_schema.json")
|
246
|
+
with open(save_path, "w", encoding="utf-8") as file:
|
247
|
+
json.dump(self.texture_custom_schema, file, indent=4)
|
248
|
+
self.logger.debug("Texture custom schema saved to %s", save_path)
|
249
|
+
|
250
|
+
self.tree_custom_schema = kwargs.get("tree_custom_schema", None)
|
251
|
+
if self.tree_custom_schema:
|
252
|
+
save_path = os.path.join(self.map_directory, "tree_custom_schema.json")
|
253
|
+
with open(save_path, "w", encoding="utf-8") as file:
|
254
|
+
json.dump(self.tree_custom_schema, file, indent=4)
|
255
|
+
self.logger.debug("Tree custom schema saved to %s", save_path)
|
256
|
+
|
62
257
|
try:
|
63
258
|
shutil.unpack_archive(game.template_path, self.map_directory)
|
64
259
|
self.logger.debug("Map template unpacked to %s", self.map_directory)
|
@@ -71,6 +266,14 @@ class Map:
|
|
71
266
|
Yields:
|
72
267
|
Generator[str, None, None]: Component names.
|
73
268
|
"""
|
269
|
+
self.logger.info(
|
270
|
+
"Starting map generation. Game code: %s. Coordinates: %s, size: %s. Rotation: %s.",
|
271
|
+
self.game.code,
|
272
|
+
self.coordinates,
|
273
|
+
self.size,
|
274
|
+
self.rotation,
|
275
|
+
)
|
276
|
+
|
74
277
|
for game_component in self.game.components:
|
75
278
|
component = game_component(
|
76
279
|
self.game,
|
@@ -81,7 +284,8 @@ class Map:
|
|
81
284
|
self.rotation,
|
82
285
|
self.map_directory,
|
83
286
|
self.logger,
|
84
|
-
|
287
|
+
texture_custom_schema=self.texture_custom_schema,
|
288
|
+
tree_custom_schema=self.tree_custom_schema,
|
85
289
|
)
|
86
290
|
self.components.append(component)
|
87
291
|
|
@@ -107,6 +311,14 @@ class Map:
|
|
107
311
|
)
|
108
312
|
raise e
|
109
313
|
|
314
|
+
self.logger.info(
|
315
|
+
"Map generation completed. Game code: %s. Coordinates: %s, size: %s. Rotation: %s.",
|
316
|
+
self.game.code,
|
317
|
+
self.coordinates,
|
318
|
+
self.size,
|
319
|
+
self.rotation,
|
320
|
+
)
|
321
|
+
|
110
322
|
def get_component(self, component_name: str) -> Component | None:
|
111
323
|
"""Get component by name.
|
112
324
|
|
@@ -154,7 +366,7 @@ class Map:
|
|
154
366
|
if remove_source:
|
155
367
|
try:
|
156
368
|
shutil.rmtree(self.map_directory)
|
157
|
-
self.logger.
|
369
|
+
self.logger.debug("Map directory removed: %s", self.map_directory)
|
158
370
|
except Exception as e: # pylint: disable=W0718
|
159
|
-
self.logger.
|
371
|
+
self.logger.debug("Error removing map directory %s: %s", self.map_directory, e)
|
160
372
|
return archive_path
|