maps4fs 1.5.7__py3-none-any.whl → 1.7.1__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 +3 -1
- maps4fs/generator/background.py +92 -8
- maps4fs/generator/component.py +23 -9
- maps4fs/generator/dem.py +12 -49
- maps4fs/generator/dtm/__init__.py +0 -0
- maps4fs/generator/{dtm.py → dtm/dtm.py} +59 -71
- maps4fs/generator/dtm/srtm.py +226 -0
- maps4fs/generator/dtm/usgs.py +351 -0
- maps4fs/generator/game.py +1 -1
- maps4fs/generator/grle.py +94 -28
- maps4fs/generator/i3d.py +20 -14
- maps4fs/generator/map.py +22 -2
- maps4fs/generator/satellite.py +1 -1
- maps4fs/generator/settings.py +41 -4
- maps4fs/generator/texture.py +107 -59
- {maps4fs-1.5.7.dist-info → maps4fs-1.7.1.dist-info}/METADATA +58 -16
- maps4fs-1.7.1.dist-info/RECORD +27 -0
- {maps4fs-1.5.7.dist-info → maps4fs-1.7.1.dist-info}/WHEEL +1 -1
- maps4fs-1.5.7.dist-info/RECORD +0 -24
- {maps4fs-1.5.7.dist-info → maps4fs-1.7.1.dist-info}/LICENSE.md +0 -0
- {maps4fs-1.5.7.dist-info → maps4fs-1.7.1.dist-info}/top_level.txt +0 -0
maps4fs/generator/grle.py
CHANGED
@@ -10,13 +10,26 @@ import numpy as np
|
|
10
10
|
from shapely.geometry import Polygon # type: ignore
|
11
11
|
|
12
12
|
from maps4fs.generator.component import Component
|
13
|
-
from maps4fs.generator.texture import Texture
|
13
|
+
from maps4fs.generator.texture import PREVIEW_MAXIMUM_SIZE, Texture
|
14
14
|
|
15
|
-
ISLAND_SIZE_MIN = 10
|
16
|
-
ISLAND_SIZE_MAX = 200
|
17
15
|
ISLAND_DISTORTION = 0.3
|
18
|
-
|
19
|
-
|
16
|
+
|
17
|
+
|
18
|
+
def plant_to_pixel_value(plant_name: str) -> int | None:
|
19
|
+
"""Returns the pixel value representation of the plant.
|
20
|
+
If not found, returns None.
|
21
|
+
|
22
|
+
Arguments:
|
23
|
+
plant_name (str): name of the plant
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
int | None: pixel value of the plant or None if not found.
|
27
|
+
"""
|
28
|
+
plants = {
|
29
|
+
"smallDenseMix": 33,
|
30
|
+
"meadow": 131,
|
31
|
+
}
|
32
|
+
return plants.get(plant_name)
|
20
33
|
|
21
34
|
|
22
35
|
# pylint: disable=W0223
|
@@ -39,6 +52,7 @@ class GRLE(Component):
|
|
39
52
|
def preprocess(self) -> None:
|
40
53
|
"""Gets the path to the map I3D file from the game instance and saves it to the instance
|
41
54
|
attribute. If the game does not support I3D files, the attribute is set to None."""
|
55
|
+
self.preview_paths: dict[str, str] = {}
|
42
56
|
|
43
57
|
try:
|
44
58
|
grle_schema_path = self.game.grle_schema
|
@@ -89,6 +103,7 @@ class GRLE(Component):
|
|
89
103
|
else:
|
90
104
|
self.logger.warning("Adding plants it's not supported for the %s.", self.game.code)
|
91
105
|
|
106
|
+
# pylint: disable=no-member
|
92
107
|
def previews(self) -> list[str]:
|
93
108
|
"""Returns a list of paths to the preview images (empty list).
|
94
109
|
The component does not generate any preview images so it returns an empty list.
|
@@ -96,7 +111,57 @@ class GRLE(Component):
|
|
96
111
|
Returns:
|
97
112
|
list[str]: An empty list.
|
98
113
|
"""
|
99
|
-
|
114
|
+
preview_paths = []
|
115
|
+
for preview_name, preview_path in self.preview_paths.items():
|
116
|
+
save_path = os.path.join(self.previews_directory, f"{preview_name}.png")
|
117
|
+
# Resize the preview image to the maximum size allowed for previews.
|
118
|
+
image = cv2.imread(preview_path, cv2.IMREAD_GRAYSCALE)
|
119
|
+
if image.shape[0] > PREVIEW_MAXIMUM_SIZE or image.shape[1] > PREVIEW_MAXIMUM_SIZE:
|
120
|
+
image = cv2.resize(image, (PREVIEW_MAXIMUM_SIZE, PREVIEW_MAXIMUM_SIZE))
|
121
|
+
image_normalized = np.empty_like(image)
|
122
|
+
cv2.normalize(image, image_normalized, 0, 255, cv2.NORM_MINMAX)
|
123
|
+
image_colored = cv2.applyColorMap(image_normalized, cv2.COLORMAP_JET)
|
124
|
+
cv2.imwrite(save_path, image_colored)
|
125
|
+
preview_paths.append(save_path)
|
126
|
+
|
127
|
+
with_fields_save_path = os.path.join(
|
128
|
+
self.previews_directory, f"{preview_name}_with_fields.png"
|
129
|
+
)
|
130
|
+
image_with_fields = self.overlay_fields(image_colored)
|
131
|
+
if image_with_fields is None:
|
132
|
+
continue
|
133
|
+
cv2.imwrite(with_fields_save_path, image_with_fields) # pylint: disable=no-member
|
134
|
+
preview_paths.append(with_fields_save_path)
|
135
|
+
|
136
|
+
return preview_paths
|
137
|
+
|
138
|
+
def overlay_fields(self, farmlands_np: np.ndarray) -> np.ndarray | None:
|
139
|
+
"""Overlay fields on the farmlands preview image.
|
140
|
+
|
141
|
+
Arguments:
|
142
|
+
farmlands_np (np.ndarray): The farmlands preview image.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
np.ndarray | None: The farmlands preview image with fields overlayed on top of it.
|
146
|
+
"""
|
147
|
+
texture_component: Texture | None = self.map.get_component("Texture") # type: ignore
|
148
|
+
if not texture_component:
|
149
|
+
self.logger.warning("Texture component not found in the map.")
|
150
|
+
return None
|
151
|
+
|
152
|
+
fields_layer = texture_component.get_layer_by_usage("field")
|
153
|
+
fields_layer_path = fields_layer.get_preview_or_path( # type: ignore
|
154
|
+
self.game.weights_dir_path(self.map_directory)
|
155
|
+
)
|
156
|
+
if not fields_layer_path or not os.path.isfile(fields_layer_path):
|
157
|
+
self.logger.warning("Fields layer not found in the texture component.")
|
158
|
+
return None
|
159
|
+
fields_np = cv2.imread(fields_layer_path)
|
160
|
+
# Resize fields_np to the same size as farmlands_np.
|
161
|
+
fields_np = cv2.resize(fields_np, (farmlands_np.shape[1], farmlands_np.shape[0]))
|
162
|
+
|
163
|
+
# use fields_np as base layer and overlay farmlands_np on top of it with 50% alpha blending.
|
164
|
+
return cv2.addWeighted(fields_np, 0.5, farmlands_np, 0.5, 0)
|
100
165
|
|
101
166
|
# pylint: disable=R0801, R0914
|
102
167
|
def _add_farmlands(self) -> None:
|
@@ -114,12 +179,12 @@ class GRLE(Component):
|
|
114
179
|
self.logger.warning("Fields data not found in textures info layer.")
|
115
180
|
return
|
116
181
|
|
117
|
-
self.logger.
|
182
|
+
self.logger.debug("Found %s fields in textures info layer.", len(fields))
|
118
183
|
|
119
184
|
farmyards: list[list[tuple[int, int]]] | None = textures_info_layer.get("farmyards")
|
120
185
|
if farmyards and self.map.grle_settings.add_farmyards:
|
121
186
|
fields.extend(farmyards)
|
122
|
-
self.logger.
|
187
|
+
self.logger.debug("Found %s farmyards in textures info layer.", len(farmyards))
|
123
188
|
|
124
189
|
info_layer_farmlands_path = os.path.join(
|
125
190
|
self.game.weights_dir_path(self.map_directory), "infoLayer_farmlands.png"
|
@@ -155,7 +220,7 @@ class GRLE(Component):
|
|
155
220
|
angle=self.rotation,
|
156
221
|
)
|
157
222
|
except ValueError as e:
|
158
|
-
self.logger.
|
223
|
+
self.logger.debug(
|
159
224
|
"Farmland %s could not be fitted into the map bounds with error: %s",
|
160
225
|
farmland_id,
|
161
226
|
e,
|
@@ -180,7 +245,7 @@ class GRLE(Component):
|
|
180
245
|
try:
|
181
246
|
cv2.fillPoly(image, [field_np], farmland_id) # type: ignore
|
182
247
|
except Exception as e: # pylint: disable=W0718
|
183
|
-
self.logger.
|
248
|
+
self.logger.debug(
|
184
249
|
"Farmland %s could not be added to the InfoLayer PNG file with error: %s",
|
185
250
|
farmland_id,
|
186
251
|
e,
|
@@ -204,6 +269,8 @@ class GRLE(Component):
|
|
204
269
|
"Farmlands added to the InfoLayer PNG file: %s.", info_layer_farmlands_path
|
205
270
|
)
|
206
271
|
|
272
|
+
self.preview_paths["farmlands"] = info_layer_farmlands_path # type: ignore
|
273
|
+
|
207
274
|
# pylint: disable=R0915
|
208
275
|
def _add_plants(self) -> None:
|
209
276
|
"""Adds plants to the InfoLayer PNG file."""
|
@@ -270,10 +337,13 @@ class GRLE(Component):
|
|
270
337
|
grass_image[forest_image != 0] = 255
|
271
338
|
|
272
339
|
# B and G channels remain the same (zeros), while we change the R channel.
|
273
|
-
possible_R_values = [
|
340
|
+
possible_R_values = [65, 97, 129, 161, 193, 225] # pylint: disable=C0103
|
274
341
|
|
275
|
-
|
276
|
-
|
342
|
+
base_layer_pixel_value = plant_to_pixel_value(
|
343
|
+
self.map.grle_settings.base_grass # type:ignore
|
344
|
+
)
|
345
|
+
if not base_layer_pixel_value:
|
346
|
+
base_layer_pixel_value = 131
|
277
347
|
|
278
348
|
# pylint: disable=no-member
|
279
349
|
def create_island_of_plants(image: np.ndarray, count: int) -> np.ndarray:
|
@@ -290,23 +360,20 @@ class GRLE(Component):
|
|
290
360
|
# Randomly choose the value for the island.
|
291
361
|
plant_value = choice(possible_R_values)
|
292
362
|
# Randomly choose the size of the island.
|
293
|
-
island_size = randint(
|
363
|
+
island_size = randint(
|
364
|
+
self.map.grle_settings.plants_island_minimum_size, # type:ignore
|
365
|
+
self.map.grle_settings.plants_island_maximum_size, # type:ignore
|
366
|
+
)
|
294
367
|
# Randomly choose the position of the island.
|
295
|
-
# x = np.random.randint(0, image.shape[1] - island_size)
|
296
|
-
# y = np.random.randint(0, image.shape[0] - island_size)
|
297
368
|
x = randint(0, image.shape[1] - island_size)
|
298
369
|
y = randint(0, image.shape[0] - island_size)
|
299
370
|
|
300
|
-
# Randomly choose the shape of the island.
|
301
|
-
# shapes = ["circle", "ellipse", "polygon"]
|
302
|
-
# shape = choice(shapes)
|
303
|
-
|
304
371
|
try:
|
305
372
|
polygon_points = get_rounded_polygon(
|
306
|
-
num_vertices=
|
373
|
+
num_vertices=self.map.grle_settings.plants_island_vertex_count,
|
307
374
|
center=(x + island_size // 2, y + island_size // 2),
|
308
375
|
radius=island_size // 2,
|
309
|
-
rounding_radius=
|
376
|
+
rounding_radius=self.map.grle_settings.plants_island_rounding_radius,
|
310
377
|
)
|
311
378
|
if not polygon_points:
|
312
379
|
continue
|
@@ -355,16 +422,16 @@ class GRLE(Component):
|
|
355
422
|
grass_image_copy = grass_image.copy()
|
356
423
|
if forest_image is not None:
|
357
424
|
# Add the forest layer to the base image, to merge the masks.
|
358
|
-
grass_image_copy[forest_image != 0] =
|
359
|
-
|
360
|
-
grass_image_copy[grass_image != 0] =
|
425
|
+
grass_image_copy[forest_image != 0] = base_layer_pixel_value
|
426
|
+
|
427
|
+
grass_image_copy[grass_image != 0] = base_layer_pixel_value
|
361
428
|
|
362
429
|
# Add islands of plants to the base image.
|
363
|
-
island_count = self.map_size
|
430
|
+
island_count = int(self.map_size * self.map.grle_settings.plants_island_percent // 100)
|
364
431
|
self.logger.debug("Adding %s islands of plants to the base image.", island_count)
|
365
432
|
if self.map.grle_settings.random_plants:
|
366
433
|
grass_image_copy = create_island_of_plants(grass_image_copy, island_count)
|
367
|
-
|
434
|
+
self.logger.info("Added %s islands of plants to the base image.", island_count)
|
368
435
|
|
369
436
|
# Sligtly reduce the size of the grass_image, that we'll use as mask.
|
370
437
|
kernel = np.ones((3, 3), np.uint8)
|
@@ -380,7 +447,6 @@ class GRLE(Component):
|
|
380
447
|
grass_image_copy[:, 0] = 0 # Left side
|
381
448
|
grass_image_copy[:, -1] = 0 # Right side
|
382
449
|
|
383
|
-
# Value of 33 represents the base grass plant.
|
384
450
|
# After painting it with base grass, we'll create multiple islands of different plants.
|
385
451
|
# On the final step, we'll remove all the values which in pixels
|
386
452
|
# where zerons in the original base image (so we don't paint grass where it should not be).
|
maps4fs/generator/i3d.py
CHANGED
@@ -14,14 +14,10 @@ import numpy as np
|
|
14
14
|
from maps4fs.generator.component import Component
|
15
15
|
from maps4fs.generator.texture import Texture
|
16
16
|
|
17
|
-
DEFAULT_HEIGHT_SCALE = 2000
|
18
17
|
DISPLACEMENT_LAYER_SIZE_FOR_BIG_MAPS = 32768
|
19
|
-
DEFAULT_MAX_LOD_DISTANCE = 10000
|
20
|
-
DEFAULT_MAX_LOD_OCCLUDER_DISTANCE = 10000
|
21
18
|
NODE_ID_STARTING_VALUE = 2000
|
22
19
|
SPLINES_NODE_ID_STARTING_VALUE = 5000
|
23
20
|
TREE_NODE_ID_STARTING_VALUE = 10000
|
24
|
-
DEFAULT_FOREST_DENSITY = 10
|
25
21
|
|
26
22
|
|
27
23
|
# pylint: disable=R0903
|
@@ -81,6 +77,20 @@ class I3d(Component):
|
|
81
77
|
|
82
78
|
root = tree.getroot()
|
83
79
|
for map_elem in root.iter("Scene"):
|
80
|
+
for terrain_elem in map_elem.iter("TerrainTransformGroup"):
|
81
|
+
if self.map.shared_settings.change_height_scale:
|
82
|
+
suggested_height_scale = self.map.shared_settings.height_scale_value
|
83
|
+
if suggested_height_scale is not None and suggested_height_scale > 255:
|
84
|
+
new_height_scale = int(
|
85
|
+
self.map.shared_settings.height_scale_value # type: ignore
|
86
|
+
)
|
87
|
+
terrain_elem.set("heightScale", str(new_height_scale))
|
88
|
+
self.logger.info(
|
89
|
+
"heightScale attribute set to %s in TerrainTransformGroup element.",
|
90
|
+
new_height_scale,
|
91
|
+
)
|
92
|
+
|
93
|
+
self.logger.debug("TerrainTransformGroup element updated in I3D file.")
|
84
94
|
sun_elem = map_elem.find(".//Light[@name='sun']")
|
85
95
|
|
86
96
|
if sun_elem is not None:
|
@@ -97,10 +107,6 @@ class I3d(Component):
|
|
97
107
|
)
|
98
108
|
|
99
109
|
if self.map_size > 4096:
|
100
|
-
terrain_elem = root.find(".//TerrainTransformGroup")
|
101
|
-
if terrain_elem is None:
|
102
|
-
self.logger.warning("TerrainTransformGroup element not found in I3D file.")
|
103
|
-
return
|
104
110
|
displacement_layer = terrain_elem.find(".//DisplacementLayer") # pylint: disable=W0631
|
105
111
|
|
106
112
|
if displacement_layer is not None:
|
@@ -146,7 +152,7 @@ class I3d(Component):
|
|
146
152
|
self.logger.warning("Roads polylines data not found in textures info layer.")
|
147
153
|
return
|
148
154
|
|
149
|
-
self.logger.
|
155
|
+
self.logger.debug("Found %s roads polylines in textures info layer.", len(roads_polylines))
|
150
156
|
self.logger.debug("Starging to add roads polylines to the I3D file.")
|
151
157
|
|
152
158
|
root = tree.getroot()
|
@@ -188,7 +194,7 @@ class I3d(Component):
|
|
188
194
|
linestring_points=road, angle=self.rotation
|
189
195
|
)
|
190
196
|
except ValueError as e:
|
191
|
-
self.logger.
|
197
|
+
self.logger.debug(
|
192
198
|
"Road %s could not be fitted into the map bounds with error: %s",
|
193
199
|
road_id,
|
194
200
|
e,
|
@@ -239,7 +245,7 @@ class I3d(Component):
|
|
239
245
|
y = max(0, min(y, dem_y_size - 1))
|
240
246
|
|
241
247
|
z = not_resized_dem[y, x]
|
242
|
-
z
|
248
|
+
z *= self.get_z_scaling_factor() # type: ignore
|
243
249
|
|
244
250
|
cv_node = ET.Element("cv")
|
245
251
|
cv_node.set("c", f"{cx}, {z}, {cy}")
|
@@ -291,7 +297,7 @@ class I3d(Component):
|
|
291
297
|
self.logger.warning("Fields data not found in textures info layer.")
|
292
298
|
return
|
293
299
|
|
294
|
-
self.logger.
|
300
|
+
self.logger.debug("Found %s fields in textures info layer.", len(fields))
|
295
301
|
self.logger.debug("Starging to add fields to the I3D file.")
|
296
302
|
|
297
303
|
root = tree.getroot()
|
@@ -313,7 +319,7 @@ class I3d(Component):
|
|
313
319
|
polygon_points=field, angle=self.rotation
|
314
320
|
)
|
315
321
|
except ValueError as e:
|
316
|
-
self.logger.
|
322
|
+
self.logger.debug(
|
317
323
|
"Field %s could not be fitted into the map bounds with error: %s",
|
318
324
|
field_id,
|
319
325
|
e,
|
@@ -327,7 +333,7 @@ class I3d(Component):
|
|
327
333
|
try:
|
328
334
|
cx, cy = self.get_polygon_center(field_ccs)
|
329
335
|
except Exception as e: # pylint: disable=W0718
|
330
|
-
self.logger.
|
336
|
+
self.logger.debug(
|
331
337
|
"Field %s could not be fitted into the map bounds.", field_id
|
332
338
|
)
|
333
339
|
self.logger.debug("Error: %s", e)
|
maps4fs/generator/map.py
CHANGED
@@ -8,7 +8,7 @@ import shutil
|
|
8
8
|
from typing import Any, Generator
|
9
9
|
|
10
10
|
from maps4fs.generator.component import Component
|
11
|
-
from maps4fs.generator.dtm import DTMProvider, DTMProviderSettings
|
11
|
+
from maps4fs.generator.dtm.dtm import DTMProvider, DTMProviderSettings
|
12
12
|
from maps4fs.generator.game import Game
|
13
13
|
from maps4fs.generator.settings import (
|
14
14
|
BackgroundSettings,
|
@@ -16,6 +16,7 @@ from maps4fs.generator.settings import (
|
|
16
16
|
GRLESettings,
|
17
17
|
I3DSettings,
|
18
18
|
SatelliteSettings,
|
19
|
+
SharedSettings,
|
19
20
|
SplineSettings,
|
20
21
|
TextureSettings,
|
21
22
|
)
|
@@ -88,6 +89,17 @@ class Map:
|
|
88
89
|
|
89
90
|
self.dem_settings = dem_settings
|
90
91
|
self.logger.info("DEM settings: %s", dem_settings)
|
92
|
+
if self.dem_settings.water_depth > 0:
|
93
|
+
# Make sure that the plateau value is >= water_depth
|
94
|
+
self.dem_settings.plateau = max(
|
95
|
+
self.dem_settings.plateau, self.dem_settings.water_depth
|
96
|
+
)
|
97
|
+
self.logger.info(
|
98
|
+
"Plateau value was set to %s to be >= water_depth value %s",
|
99
|
+
self.dem_settings.plateau,
|
100
|
+
self.dem_settings.water_depth,
|
101
|
+
)
|
102
|
+
|
91
103
|
self.background_settings = background_settings
|
92
104
|
self.logger.info("Background settings: %s", background_settings)
|
93
105
|
self.grle_settings = grle_settings
|
@@ -123,6 +135,8 @@ class Map:
|
|
123
135
|
with open(save_path, "w", encoding="utf-8") as file:
|
124
136
|
json.dump(settings_json, file, indent=4)
|
125
137
|
|
138
|
+
self.shared_settings = SharedSettings()
|
139
|
+
|
126
140
|
self.texture_custom_schema = kwargs.get("texture_custom_schema", None)
|
127
141
|
if self.texture_custom_schema:
|
128
142
|
save_path = os.path.join(self.map_directory, "texture_custom_schema.json")
|
@@ -132,11 +146,17 @@ class Map:
|
|
132
146
|
|
133
147
|
self.tree_custom_schema = kwargs.get("tree_custom_schema", None)
|
134
148
|
if self.tree_custom_schema:
|
149
|
+
self.logger.info("Custom tree schema contains %s trees", len(self.tree_custom_schema))
|
135
150
|
save_path = os.path.join(self.map_directory, "tree_custom_schema.json")
|
136
151
|
with open(save_path, "w", encoding="utf-8") as file:
|
137
152
|
json.dump(self.tree_custom_schema, file, indent=4)
|
138
153
|
self.logger.debug("Tree custom schema saved to %s", save_path)
|
139
154
|
|
155
|
+
self.custom_background_path = kwargs.get("custom_background_path", None)
|
156
|
+
if self.custom_background_path:
|
157
|
+
save_path = os.path.join(self.map_directory, "custom_background.png")
|
158
|
+
shutil.copyfile(self.custom_background_path, save_path)
|
159
|
+
|
140
160
|
try:
|
141
161
|
shutil.unpack_archive(game.template_path, self.map_directory)
|
142
162
|
self.logger.debug("Map template unpacked to %s", self.map_directory)
|
@@ -245,7 +265,7 @@ class Map:
|
|
245
265
|
str: Path to the archive.
|
246
266
|
"""
|
247
267
|
archive_path = shutil.make_archive(archive_path, "zip", self.map_directory)
|
248
|
-
self.logger.
|
268
|
+
self.logger.debug("Map packed to %s.zip", archive_path)
|
249
269
|
if remove_source:
|
250
270
|
try:
|
251
271
|
shutil.rmtree(self.map_directory)
|
maps4fs/generator/satellite.py
CHANGED
@@ -34,7 +34,7 @@ class Satellite(Component):
|
|
34
34
|
"""Downloads the satellite images for the map."""
|
35
35
|
self.image_paths = [] # pylint: disable=W0201
|
36
36
|
if not self.map.satellite_settings.download_images:
|
37
|
-
self.logger.
|
37
|
+
self.logger.debug("Satellite images download is disabled.")
|
38
38
|
return
|
39
39
|
|
40
40
|
margin = self.map.satellite_settings.satellite_margin
|
maps4fs/generator/settings.py
CHANGED
@@ -4,12 +4,29 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from typing import Any
|
6
6
|
|
7
|
-
from pydantic import BaseModel
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
8
|
+
|
9
|
+
|
10
|
+
class SharedSettings(BaseModel):
|
11
|
+
"""Represents the shared settings for all components."""
|
12
|
+
|
13
|
+
mesh_z_scaling_factor: float | None = None
|
14
|
+
height_scale_multiplier: float | None = None
|
15
|
+
height_scale_value: float | None = None
|
16
|
+
change_height_scale: bool = False
|
17
|
+
|
18
|
+
model_config = ConfigDict(
|
19
|
+
frozen=False,
|
20
|
+
)
|
8
21
|
|
9
22
|
|
10
23
|
class SettingsModel(BaseModel):
|
11
24
|
"""Base class for settings models. It provides methods to convert settings to and from JSON."""
|
12
25
|
|
26
|
+
model_config = ConfigDict(
|
27
|
+
frozen=False,
|
28
|
+
)
|
29
|
+
|
13
30
|
@classmethod
|
14
31
|
def all_settings_to_json(cls) -> dict[str, dict[str, Any]]:
|
15
32
|
"""Get all settings of the current class and its subclasses as a dictionary.
|
@@ -25,18 +42,28 @@ class SettingsModel(BaseModel):
|
|
25
42
|
return all_settings
|
26
43
|
|
27
44
|
@classmethod
|
28
|
-
def all_settings_from_json(
|
45
|
+
def all_settings_from_json(
|
46
|
+
cls, data: dict, flattening: bool = True
|
47
|
+
) -> dict[str, SettingsModel]:
|
29
48
|
"""Create settings instances from JSON data.
|
30
49
|
|
31
50
|
Arguments:
|
32
51
|
data (dict): JSON data.
|
52
|
+
flattening (bool): if set to True will flattet iterables to use the first element
|
53
|
+
of it.
|
33
54
|
|
34
55
|
Returns:
|
35
56
|
dict[str, Type[SettingsModel]]: Dictionary with settings instances.
|
36
57
|
"""
|
37
58
|
settings = {}
|
38
59
|
for subclass in cls.__subclasses__():
|
39
|
-
|
60
|
+
subclass_data = data[subclass.__name__]
|
61
|
+
if flattening:
|
62
|
+
for key, value in subclass_data.items():
|
63
|
+
if isinstance(value, (list, tuple)):
|
64
|
+
subclass_data[key] = value[0]
|
65
|
+
|
66
|
+
settings[subclass.__name__] = subclass(**subclass_data)
|
40
67
|
|
41
68
|
return settings
|
42
69
|
|
@@ -85,6 +112,10 @@ class BackgroundSettings(SettingsModel):
|
|
85
112
|
generate_background: bool = False
|
86
113
|
generate_water: bool = False
|
87
114
|
resize_factor: int = 8
|
115
|
+
remove_center: bool = False
|
116
|
+
apply_decimation: bool = False
|
117
|
+
decimation_percent: int = 25
|
118
|
+
decimation_agression: int = 3
|
88
119
|
|
89
120
|
|
90
121
|
class GRLESettings(SettingsModel):
|
@@ -100,6 +131,12 @@ class GRLESettings(SettingsModel):
|
|
100
131
|
farmland_margin: int = 0
|
101
132
|
random_plants: bool = True
|
102
133
|
add_farmyards: bool = False
|
134
|
+
base_grass: tuple | str = ("smallDenseMix", "meadow")
|
135
|
+
plants_island_minimum_size: int = 10
|
136
|
+
plants_island_maximum_size: int = 200
|
137
|
+
plants_island_vertex_count: int = 30
|
138
|
+
plants_island_rounding_radius: int = 15
|
139
|
+
plants_island_percent: int = 100
|
103
140
|
|
104
141
|
|
105
142
|
class I3DSettings(SettingsModel):
|
@@ -146,5 +183,5 @@ class SatelliteSettings(SettingsModel):
|
|
146
183
|
"""
|
147
184
|
|
148
185
|
download_images: bool = False
|
149
|
-
satellite_margin: int =
|
186
|
+
satellite_margin: int = 0
|
150
187
|
zoom_level: int = 14
|