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/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
- TREE_NODE_ID_STARTING_VALUE = 4000
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.info("I3D file processing is not implemented for this game.")
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._add_forests()
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.info("I3D is not obtained, skipping the update.")
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.info("Map I3D file saved to: %s.", self._map_i3d_path)
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.fit_polygon_into_bounds(field, angle=self.rotation)
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.info("Map I3D file saved to: %s.", self._map_i3d_path)
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
- try:
336
- tree_schema_path = self.game.tree_schema
337
- except ValueError:
338
- self.logger.warning("Tree schema path not set for the Game %s.", self.game.code)
339
- return
340
-
341
- if not os.path.isfile(tree_schema_path):
342
- self.logger.warning("Tree schema file was not found: %s.", tree_schema_path)
343
- return
344
-
345
- try:
346
- with open(tree_schema_path, "r", encoding="utf-8") as tree_schema_file:
347
- tree_schema: list[dict[str, str | int]] = json.load(tree_schema_file)
348
- except json.JSONDecodeError as e:
349
- self.logger.warning(
350
- "Could not load tree schema from %s with error: %s", tree_schema_path, e
351
- )
352
- return
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((xcs, ycs), self.forest_density) # type: ignore
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.info("Map I3D file saved to: %s.", self._map_i3d_path)
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
- # pylint: disable=R0913, R0902
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.kwargs = kwargs
57
- self.logger.info("Additional arguments: %s", kwargs)
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
- **self.kwargs,
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.info("Map directory removed: %s", self.map_directory)
369
+ self.logger.debug("Map directory removed: %s", self.map_directory)
158
370
  except Exception as e: # pylint: disable=W0718
159
- self.logger.error("Error removing map directory %s: %s", self.map_directory, e)
371
+ self.logger.debug("Error removing map directory %s: %s", self.map_directory, e)
160
372
  return archive_path