maps4fs 1.8.13__tar.gz → 1.8.15__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.
- {maps4fs-1.8.13 → maps4fs-1.8.15}/PKG-INFO +7 -1
- {maps4fs-1.8.13 → maps4fs-1.8.15}/README.md +6 -0
- {maps4fs-1.8.13/maps4fs/generator → maps4fs-1.8.15/maps4fs/generator/component}/background.py +50 -214
- maps4fs-1.8.15/maps4fs/generator/component/base/component_image.py +90 -0
- maps4fs-1.8.15/maps4fs/generator/component/base/component_mesh.py +125 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/component/base/component_xml.py +13 -0
- {maps4fs-1.8.13/maps4fs/generator → maps4fs-1.8.15/maps4fs/generator/component}/grle.py +160 -178
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/dtm.py +0 -4
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/game.py +54 -2
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/map.py +1 -1
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/satellite.py +2 -2
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/settings.py +10 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/texture.py +6 -1
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs.egg-info/PKG-INFO +7 -1
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs.egg-info/SOURCES.txt +4 -2
- {maps4fs-1.8.13 → maps4fs-1.8.15}/pyproject.toml +1 -1
- {maps4fs-1.8.13 → maps4fs-1.8.15}/LICENSE.md +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/component/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/component/base/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/component/base/component.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/component/config.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/component/i3d.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dem.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/base/wcs.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/base/wms.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/bavaria.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/canada.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/england.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/hessen.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/niedersachsen.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/nrw.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/srtm.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/usgs.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/dtm/utils.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/logger.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/toolbox/__init__.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/toolbox/background.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/toolbox/custom_osm.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs/toolbox/dem.py +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs.egg-info/requires.txt +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/setup.cfg +0 -0
- {maps4fs-1.8.13 → maps4fs-1.8.15}/tests/test_generator.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.15
|
4
4
|
Summary: Generate map templates for Farming Simulator from real places.
|
5
5
|
Author-email: iwatkot <iwatkot@gmail.com>
|
6
6
|
License: MIT License
|
@@ -595,6 +595,10 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
595
595
|
|
596
596
|
- Add Farmyards - if enabled, the tool will create farmlands from the regions that are marked as farmyards in the OSM data. Those farmlands will not have fields and also will not be drawn on textures. By default, it's turned off.
|
597
597
|
|
598
|
+
- Base price - the base price of the farmland. It's used to calculate the price of the farmland in the game. In default in-game maps this value equals to 60000.
|
599
|
+
|
600
|
+
- Price scale - is a value in percent which will be applied to all farmnlands. The price per Ha will be calculated as `base_price * price_scale / 100`. By default, it's set to 100%.
|
601
|
+
|
598
602
|
- Base grass - you can select which plant will be used as a base grass on the map.
|
599
603
|
|
600
604
|
- Plants island minimum size - when random plants are enabled, the generator will add islands of differents plants to the map and choose the random size of those island between the minimum and maximum values. This one is the minimum size of the island in meters.
|
@@ -621,6 +625,8 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
621
625
|
|
622
626
|
- Skip drains - if enabled, the tool will not generate the drains and ditches on the map. By default, it's set to False. Use this if you don't need the drains on the map.
|
623
627
|
|
628
|
+
- Use cache - if enabled, the tool will use the cached OSM data for generating the map. It's useful when you're generating the same map multiple times and don't want to download the OSM data each time. But if you've made some changes to the OSM data, you should disable this option to get the updated data. By default, it's set to True. This option has no effect when you're using the custom OSM file.
|
629
|
+
|
624
630
|
### Splines Advanced settings
|
625
631
|
|
626
632
|
- Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
|
@@ -569,6 +569,10 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
569
569
|
|
570
570
|
- Add Farmyards - if enabled, the tool will create farmlands from the regions that are marked as farmyards in the OSM data. Those farmlands will not have fields and also will not be drawn on textures. By default, it's turned off.
|
571
571
|
|
572
|
+
- Base price - the base price of the farmland. It's used to calculate the price of the farmland in the game. In default in-game maps this value equals to 60000.
|
573
|
+
|
574
|
+
- Price scale - is a value in percent which will be applied to all farmnlands. The price per Ha will be calculated as `base_price * price_scale / 100`. By default, it's set to 100%.
|
575
|
+
|
572
576
|
- Base grass - you can select which plant will be used as a base grass on the map.
|
573
577
|
|
574
578
|
- Plants island minimum size - when random plants are enabled, the generator will add islands of differents plants to the map and choose the random size of those island between the minimum and maximum values. This one is the minimum size of the island in meters.
|
@@ -595,6 +599,8 @@ You can also apply some advanced settings to the map generation process.<br>
|
|
595
599
|
|
596
600
|
- Skip drains - if enabled, the tool will not generate the drains and ditches on the map. By default, it's set to False. Use this if you don't need the drains on the map.
|
597
601
|
|
602
|
+
- Use cache - if enabled, the tool will use the cached OSM data for generating the map. It's useful when you're generating the same map multiple times and don't want to download the OSM data each time. But if you've made some changes to the OSM data, you should disable this option to get the updated data. By default, it's set to True. This option has no effect when you're using the custom OSM file.
|
603
|
+
|
598
604
|
### Splines Advanced settings
|
599
605
|
|
600
606
|
- Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
|
{maps4fs-1.8.13/maps4fs/generator → maps4fs-1.8.15/maps4fs/generator/component}/background.py
RENAMED
@@ -10,21 +10,15 @@ from copy import deepcopy
|
|
10
10
|
|
11
11
|
import cv2
|
12
12
|
import numpy as np
|
13
|
-
import trimesh # type: ignore
|
14
|
-
from tqdm import tqdm
|
15
13
|
|
16
|
-
from maps4fs.generator.component.base.
|
14
|
+
from maps4fs.generator.component.base.component_image import ImageComponent
|
15
|
+
from maps4fs.generator.component.base.component_mesh import MeshComponent
|
17
16
|
from maps4fs.generator.dem import DEM
|
17
|
+
from maps4fs.generator.settings import Parameters
|
18
18
|
from maps4fs.generator.texture import Texture
|
19
19
|
|
20
|
-
DEFAULT_DISTANCE = 2048
|
21
|
-
FULL_NAME = "FULL"
|
22
|
-
FULL_PREVIEW_NAME = "PREVIEW"
|
23
|
-
ELEMENTS = [FULL_NAME, FULL_PREVIEW_NAME]
|
24
20
|
|
25
|
-
|
26
|
-
# pylint: disable=R0902
|
27
|
-
class Background(Component):
|
21
|
+
class Background(MeshComponent, ImageComponent):
|
28
22
|
"""Component for creating 3D obj files based on DEM data around the map.
|
29
23
|
|
30
24
|
Arguments:
|
@@ -38,11 +32,9 @@ class Background(Component):
|
|
38
32
|
info, warning. If not provided, default logging will be used.
|
39
33
|
"""
|
40
34
|
|
41
|
-
# pylint: disable=R0801
|
42
35
|
def preprocess(self) -> None:
|
43
36
|
"""Registers the DEMs for the background terrain."""
|
44
|
-
self.stl_preview_path
|
45
|
-
self.water_resources_path: str | None = None
|
37
|
+
self.stl_preview_path = os.path.join(self.previews_directory, "background_dem.stl")
|
46
38
|
|
47
39
|
if self.rotation:
|
48
40
|
self.logger.debug("Rotation is enabled: %s.", self.rotation)
|
@@ -50,7 +42,7 @@ class Background(Component):
|
|
50
42
|
else:
|
51
43
|
output_size_multiplier = 1
|
52
44
|
|
53
|
-
self.background_size = self.map_size +
|
45
|
+
self.background_size = self.map_size + Parameters.BACKGROUND_DISTANCE * 2
|
54
46
|
self.rotated_size = int(self.background_size * output_size_multiplier)
|
55
47
|
|
56
48
|
self.background_directory = os.path.join(self.map_directory, "background")
|
@@ -58,13 +50,17 @@ class Background(Component):
|
|
58
50
|
os.makedirs(self.background_directory, exist_ok=True)
|
59
51
|
os.makedirs(self.water_directory, exist_ok=True)
|
60
52
|
|
61
|
-
self.
|
53
|
+
self.water_resources_path = os.path.join(self.water_directory, "water_resources.png")
|
54
|
+
|
55
|
+
self.output_path = os.path.join(self.background_directory, f"{Parameters.FULL}.png")
|
62
56
|
if self.map.custom_background_path:
|
63
|
-
self.
|
57
|
+
self.validate_np_for_mesh(self.map.custom_background_path, self.map_size)
|
64
58
|
shutil.copyfile(self.map.custom_background_path, self.output_path)
|
65
59
|
|
66
|
-
self.not_substracted_path = os.path.join(
|
67
|
-
|
60
|
+
self.not_substracted_path: str = os.path.join(
|
61
|
+
self.background_directory, "not_substracted.png"
|
62
|
+
)
|
63
|
+
self.not_resized_path: str = os.path.join(self.background_directory, "not_resized.png")
|
68
64
|
|
69
65
|
self.dem = DEM(
|
70
66
|
self.game,
|
@@ -80,39 +76,6 @@ class Background(Component):
|
|
80
76
|
self.dem.set_output_resolution((self.rotated_size, self.rotated_size))
|
81
77
|
self.dem.set_dem_path(self.output_path)
|
82
78
|
|
83
|
-
def check_custom_background(self, image_path: str) -> None:
|
84
|
-
"""Checks if the custom background image meets the requirements.
|
85
|
-
|
86
|
-
Arguments:
|
87
|
-
image_path (str): The path to the custom background image.
|
88
|
-
|
89
|
-
Raises:
|
90
|
-
ValueError: If the custom background image does not meet the requirements.
|
91
|
-
"""
|
92
|
-
image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
|
93
|
-
if image.shape[0] != image.shape[1]:
|
94
|
-
raise ValueError("The custom background image must be a square.")
|
95
|
-
|
96
|
-
if image.shape[0] != self.map_size + DEFAULT_DISTANCE * 2:
|
97
|
-
raise ValueError("The custom background image must have the size of the map + 4096.")
|
98
|
-
|
99
|
-
if len(image.shape) != 2:
|
100
|
-
raise ValueError("The custom background image must be a grayscale image.")
|
101
|
-
|
102
|
-
if image.dtype != np.uint16:
|
103
|
-
raise ValueError("The custom background image must be a 16-bit grayscale image.")
|
104
|
-
|
105
|
-
def is_preview(self, name: str) -> bool:
|
106
|
-
"""Checks if the DEM is a preview.
|
107
|
-
|
108
|
-
Arguments:
|
109
|
-
name (str): The name of the DEM.
|
110
|
-
|
111
|
-
Returns:
|
112
|
-
bool: True if the DEM is a preview, False otherwise.
|
113
|
-
"""
|
114
|
-
return name == FULL_PREVIEW_NAME
|
115
|
-
|
116
79
|
def process(self) -> None:
|
117
80
|
"""Launches the component processing. Iterates over all tiles and processes them
|
118
81
|
as a result the DEM files will be saved, then based on them the obj files will be
|
@@ -123,12 +86,12 @@ class Background(Component):
|
|
123
86
|
self.dem.process()
|
124
87
|
|
125
88
|
shutil.copyfile(self.dem.dem_path, self.not_substracted_path)
|
126
|
-
self.
|
89
|
+
self.save_map_dem(self.dem.dem_path, save_path=self.not_resized_path)
|
127
90
|
|
128
91
|
if self.map.dem_settings.water_depth:
|
129
92
|
self.subtraction()
|
130
93
|
|
131
|
-
cutted_dem_path = self.
|
94
|
+
cutted_dem_path = self.save_map_dem(self.dem.dem_path)
|
132
95
|
if self.game.additional_dem_name is not None:
|
133
96
|
self.make_copy(cutted_dem_path, self.game.additional_dem_name)
|
134
97
|
|
@@ -184,9 +147,9 @@ class Background(Component):
|
|
184
147
|
|
185
148
|
def qgis_sequence(self) -> None:
|
186
149
|
"""Generates QGIS scripts for creating bounding box layers and rasterizing them."""
|
187
|
-
qgis_layer = (f"Background_{
|
150
|
+
qgis_layer = (f"Background_{Parameters.FULL}", *self.dem.get_espg3857_bbox())
|
188
151
|
qgis_layer_with_margin = (
|
189
|
-
f"Background_{
|
152
|
+
f"Background_{Parameters.FULL}_margin",
|
190
153
|
*self.dem.get_espg3857_bbox(add_margin=True),
|
191
154
|
)
|
192
155
|
self.create_qgis_scripts([qgis_layer, qgis_layer_with_margin])
|
@@ -214,10 +177,9 @@ class Background(Component):
|
|
214
177
|
create_preview=True,
|
215
178
|
remove_center=self.map.background_settings.remove_center,
|
216
179
|
include_zeros=False,
|
217
|
-
)
|
180
|
+
)
|
218
181
|
|
219
|
-
|
220
|
-
def cutout(self, dem_path: str, save_path: str | None = None) -> str:
|
182
|
+
def save_map_dem(self, dem_path: str, save_path: str | None = None) -> str:
|
221
183
|
"""Cuts out the center of the DEM (the actual map) and saves it as a separate file.
|
222
184
|
|
223
185
|
Arguments:
|
@@ -228,14 +190,8 @@ class Background(Component):
|
|
228
190
|
str -- The path to the cutout DEM file.
|
229
191
|
"""
|
230
192
|
dem_data = cv2.imread(dem_path, cv2.IMREAD_UNCHANGED)
|
231
|
-
|
232
|
-
center = (dem_data.shape[0] // 2, dem_data.shape[1] // 2)
|
233
193
|
half_size = self.map_size // 2
|
234
|
-
|
235
|
-
x2 = center[0] + half_size
|
236
|
-
y1 = center[1] - half_size
|
237
|
-
y2 = center[1] + half_size
|
238
|
-
dem_data = dem_data[x1:x2, y1:y2]
|
194
|
+
dem_data = self.cut_out_np(dem_data, half_size, return_cutout=True)
|
239
195
|
|
240
196
|
if save_path:
|
241
197
|
cv2.imwrite(save_path, dem_data)
|
@@ -260,26 +216,6 @@ class Background(Component):
|
|
260
216
|
|
261
217
|
return main_dem_path
|
262
218
|
|
263
|
-
def remove_center(self, dem_data: np.ndarray, resize_factor: float) -> np.ndarray:
|
264
|
-
"""Removes the center part of the DEM data.
|
265
|
-
|
266
|
-
Arguments:
|
267
|
-
dem_data (np.ndarray) -- The DEM data as a numpy array.
|
268
|
-
resize_factor (float) -- The resize factor of the DEM data.
|
269
|
-
|
270
|
-
Returns:
|
271
|
-
np.ndarray -- The DEM data with the center part removed.
|
272
|
-
"""
|
273
|
-
center = (dem_data.shape[0] // 2, dem_data.shape[1] // 2)
|
274
|
-
half_size = int(self.map_size // 2 * resize_factor)
|
275
|
-
x1 = center[0] - half_size
|
276
|
-
x2 = center[0] + half_size
|
277
|
-
y1 = center[1] - half_size
|
278
|
-
y2 = center[1] + half_size
|
279
|
-
dem_data[x1:x2, y1:y2] = 0
|
280
|
-
return dem_data
|
281
|
-
|
282
|
-
# pylint: disable=R0913, R0917, R0915
|
283
219
|
def plane_from_np(
|
284
220
|
self,
|
285
221
|
dem_data: np.ndarray,
|
@@ -302,110 +238,29 @@ class Background(Component):
|
|
302
238
|
resize_factor = 1 / self.map.background_settings.resize_factor
|
303
239
|
dem_data = cv2.resize(dem_data, (0, 0), fx=resize_factor, fy=resize_factor)
|
304
240
|
if remove_center:
|
305
|
-
|
241
|
+
half_size = int(self.map_size // 2 * resize_factor)
|
242
|
+
dem_data = self.cut_out_np(dem_data, half_size, set_zeros=True)
|
306
243
|
self.logger.debug("Center removed from DEM data.")
|
307
244
|
self.logger.debug(
|
308
245
|
"DEM data resized to shape: %s with factor: %s", dem_data.shape, resize_factor
|
309
246
|
)
|
310
247
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
ground = z.max()
|
321
|
-
self.logger.debug("Ground level: %s", ground)
|
322
|
-
|
323
|
-
self.logger.debug(
|
324
|
-
"Starting to generate a mesh for with shape: %s x %s. This may take a while.",
|
325
|
-
cols,
|
326
|
-
rows,
|
248
|
+
mesh = self.mesh_from_np(
|
249
|
+
dem_data,
|
250
|
+
include_zeros=include_zeros,
|
251
|
+
z_scaling_factor=self.get_z_scaling_factor(),
|
252
|
+
resize_factor=resize_factor,
|
253
|
+
apply_decimation=self.map.background_settings.apply_decimation,
|
254
|
+
decimation_percent=self.map.background_settings.decimation_percent,
|
255
|
+
decimation_agression=self.map.background_settings.decimation_agression,
|
327
256
|
)
|
328
257
|
|
329
|
-
vertices = np.column_stack([x.ravel(), y.ravel(), z.ravel()])
|
330
|
-
faces = []
|
331
|
-
|
332
|
-
skipped = 0
|
333
|
-
|
334
|
-
for i in tqdm(range(rows - 1), desc="Generating mesh", unit="row"):
|
335
|
-
for j in range(cols - 1):
|
336
|
-
top_left = i * cols + j
|
337
|
-
top_right = top_left + 1
|
338
|
-
bottom_left = top_left + cols
|
339
|
-
bottom_right = bottom_left + 1
|
340
|
-
|
341
|
-
if (
|
342
|
-
ground in [z[i, j], z[i, j + 1], z[i + 1, j], z[i + 1, j + 1]]
|
343
|
-
and not include_zeros
|
344
|
-
):
|
345
|
-
skipped += 1
|
346
|
-
continue
|
347
|
-
|
348
|
-
faces.append([top_left, bottom_left, bottom_right])
|
349
|
-
faces.append([top_left, bottom_right, top_right])
|
350
|
-
|
351
|
-
self.logger.debug("Skipped faces: %s", skipped)
|
352
|
-
|
353
|
-
faces = np.array(faces) # type: ignore
|
354
|
-
mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
|
355
|
-
|
356
|
-
# Apply rotation: 180 degrees around Y-axis and Z-axis
|
357
|
-
rotation_matrix_y = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
|
358
|
-
rotation_matrix_z = trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
|
359
|
-
mesh.apply_transform(rotation_matrix_y)
|
360
|
-
mesh.apply_transform(rotation_matrix_z)
|
361
|
-
|
362
|
-
# if not include_zeros:
|
363
|
-
z_scaling_factor = self.get_z_scaling_factor()
|
364
|
-
self.logger.debug("Z scaling factor: %s", z_scaling_factor)
|
365
|
-
mesh.apply_scale([1 / resize_factor, 1 / resize_factor, z_scaling_factor])
|
366
|
-
|
367
|
-
old_faces = len(mesh.faces)
|
368
|
-
self.logger.debug("Mesh generated with %s faces.", old_faces)
|
369
|
-
|
370
|
-
if self.map.background_settings.apply_decimation:
|
371
|
-
percent = self.map.background_settings.decimation_percent / 100
|
372
|
-
mesh = mesh.simplify_quadric_decimation(
|
373
|
-
percent=percent, aggression=self.map.background_settings.decimation_agression
|
374
|
-
)
|
375
|
-
|
376
|
-
new_faces = len(mesh.faces)
|
377
|
-
decimation_percent = (old_faces - new_faces) / old_faces * 100
|
378
|
-
|
379
|
-
self.logger.debug(
|
380
|
-
"Mesh simplified to %s faces. Decimation percent: %s", new_faces, decimation_percent
|
381
|
-
)
|
382
|
-
|
383
258
|
mesh.export(save_path)
|
384
259
|
self.logger.debug("Obj file saved: %s", save_path)
|
385
260
|
|
386
261
|
if create_preview:
|
387
|
-
# Simplify the preview mesh to reduce the size of the file.
|
388
|
-
# mesh = mesh.simplify_quadric_decimation(face_count=len(mesh.faces) // 2**7)
|
389
|
-
|
390
|
-
# Apply scale to make the preview mesh smaller in the UI.
|
391
262
|
mesh.apply_scale([0.5, 0.5, 0.5])
|
392
|
-
self.mesh_to_stl(mesh)
|
393
|
-
|
394
|
-
def mesh_to_stl(self, mesh: trimesh.Trimesh) -> None:
|
395
|
-
"""Converts the mesh to an STL file and saves it in the previews directory.
|
396
|
-
Uses powerful simplification to reduce the size of the file since it will be used
|
397
|
-
only for the preview.
|
398
|
-
|
399
|
-
Arguments:
|
400
|
-
mesh (trimesh.Trimesh) -- The mesh to convert to an STL file.
|
401
|
-
"""
|
402
|
-
mesh = mesh.simplify_quadric_decimation(face_count=len(mesh.faces) // 2**6)
|
403
|
-
preview_path = os.path.join(self.previews_directory, "background_dem.stl")
|
404
|
-
mesh.export(preview_path)
|
405
|
-
|
406
|
-
self.logger.debug("STL file saved: %s", preview_path)
|
407
|
-
|
408
|
-
self.stl_preview_path = preview_path # pylint: disable=attribute-defined-outside-init
|
263
|
+
self.mesh_to_stl(mesh, save_path=self.stl_preview_path)
|
409
264
|
|
410
265
|
def previews(self) -> list[str]:
|
411
266
|
"""Returns the path to the image previews paths and the path to the STL preview file.
|
@@ -421,8 +276,13 @@ class Background(Component):
|
|
421
276
|
background_dem_preview_image = cv2.resize(
|
422
277
|
background_dem_preview_image, (0, 0), fx=1 / 4, fy=1 / 4
|
423
278
|
)
|
424
|
-
background_dem_preview_image = cv2.normalize(
|
425
|
-
background_dem_preview_image,
|
279
|
+
background_dem_preview_image = cv2.normalize(
|
280
|
+
background_dem_preview_image,
|
281
|
+
dst=np.empty_like(background_dem_preview_image),
|
282
|
+
alpha=0,
|
283
|
+
beta=255,
|
284
|
+
norm_type=cv2.NORM_MINMAX,
|
285
|
+
dtype=cv2.CV_8U,
|
426
286
|
)
|
427
287
|
background_dem_preview_image = cv2.cvtColor(
|
428
288
|
background_dem_preview_image, cv2.COLOR_GRAY2BGR
|
@@ -431,7 +291,7 @@ class Background(Component):
|
|
431
291
|
cv2.imwrite(background_dem_preview_path, background_dem_preview_image)
|
432
292
|
preview_paths.append(background_dem_preview_path)
|
433
293
|
|
434
|
-
if self.stl_preview_path:
|
294
|
+
if os.path.isfile(self.stl_preview_path):
|
435
295
|
preview_paths.append(self.stl_preview_path)
|
436
296
|
|
437
297
|
return preview_paths
|
@@ -483,26 +343,11 @@ class Background(Component):
|
|
483
343
|
|
484
344
|
dem_data = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
485
345
|
|
486
|
-
self.logger.debug(
|
487
|
-
"DEM data before normalization. Shape: %s, dtype: %s. Min: %s, max: %s.",
|
488
|
-
dem_data.shape,
|
489
|
-
dem_data.dtype,
|
490
|
-
dem_data.min(),
|
491
|
-
dem_data.max(),
|
492
|
-
)
|
493
|
-
|
494
346
|
# Create an empty array with the same shape and type as dem_data.
|
495
347
|
dem_data_normalized = np.empty_like(dem_data)
|
496
348
|
|
497
349
|
# Normalize the DEM data to the range [0, 255]
|
498
350
|
cv2.normalize(dem_data, dem_data_normalized, 0, 255, cv2.NORM_MINMAX)
|
499
|
-
self.logger.debug(
|
500
|
-
"DEM data after normalization. Shape: %s, dtype: %s. Min: %s, max: %s.",
|
501
|
-
dem_data_normalized.shape,
|
502
|
-
dem_data_normalized.dtype,
|
503
|
-
dem_data_normalized.min(),
|
504
|
-
dem_data_normalized.max(),
|
505
|
-
)
|
506
351
|
dem_data_colored = cv2.applyColorMap(dem_data_normalized, cv2.COLORMAP_JET)
|
507
352
|
|
508
353
|
cv2.imwrite(colored_dem_path, dem_data_colored)
|
@@ -528,7 +373,7 @@ class Background(Component):
|
|
528
373
|
if not background_layers:
|
529
374
|
return
|
530
375
|
|
531
|
-
self.background_texture = Texture(
|
376
|
+
self.background_texture = Texture(
|
532
377
|
self.game,
|
533
378
|
self.map,
|
534
379
|
self.coordinates,
|
@@ -555,13 +400,10 @@ class Background(Component):
|
|
555
400
|
# Merge all images into one.
|
556
401
|
background_image = np.zeros((self.background_size, self.background_size), dtype=np.uint8)
|
557
402
|
for path in background_paths:
|
558
|
-
|
559
|
-
background_image = cv2.add(background_image,
|
403
|
+
background_layer = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
|
404
|
+
background_image = cv2.add(background_image, background_layer) # type: ignore
|
560
405
|
|
561
|
-
|
562
|
-
cv2.imwrite(background_save_path, background_image)
|
563
|
-
self.logger.debug("Background texture saved: %s", background_save_path)
|
564
|
-
self.water_resources_path = background_save_path # pylint: disable=W0201
|
406
|
+
cv2.imwrite(self.water_resources_path, background_image)
|
565
407
|
|
566
408
|
def subtraction(self) -> None:
|
567
409
|
"""Subtracts the water depth from the DEM data where the water resources are located."""
|
@@ -569,20 +411,14 @@ class Background(Component):
|
|
569
411
|
self.logger.warning("Water resources texture not found.")
|
570
412
|
return
|
571
413
|
|
572
|
-
# Single channeled 8 bit image, where the water have values of 255, and the rest 0.
|
573
414
|
water_resources_image = cv2.imread(self.water_resources_path, cv2.IMREAD_UNCHANGED)
|
574
|
-
mask = water_resources_image == 255
|
575
|
-
|
576
|
-
# Make mask a little bit smaller (1 pixel).
|
577
|
-
mask = cv2.erode(mask.astype(np.uint8), np.ones((3, 3), np.uint8), iterations=1).astype(
|
578
|
-
bool
|
579
|
-
)
|
580
|
-
|
581
415
|
dem_image = cv2.imread(self.output_path, cv2.IMREAD_UNCHANGED)
|
582
416
|
|
583
|
-
|
584
|
-
|
585
|
-
|
417
|
+
dem_image = self.subtract_by_mask(
|
418
|
+
dem_image,
|
419
|
+
water_resources_image,
|
420
|
+
self.map.dem_settings.water_depth,
|
421
|
+
)
|
586
422
|
|
587
423
|
# Save the modified dem_image back to the output path
|
588
424
|
cv2.imwrite(self.output_path, dem_image)
|
@@ -590,7 +426,7 @@ class Background(Component):
|
|
590
426
|
|
591
427
|
def generate_water_resources_obj(self) -> None:
|
592
428
|
"""Generates 3D obj files based on water resources data."""
|
593
|
-
if not self.water_resources_path:
|
429
|
+
if not os.path.isfile(self.water_resources_path):
|
594
430
|
self.logger.warning("Water resources texture not found.")
|
595
431
|
return
|
596
432
|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
"""Base class for all components that primarily used to work with images."""
|
2
|
+
|
3
|
+
import cv2
|
4
|
+
import numpy as np
|
5
|
+
|
6
|
+
from maps4fs.generator.component.base.component import Component
|
7
|
+
|
8
|
+
|
9
|
+
class ImageComponent(Component):
|
10
|
+
"""Base class for all components that primarily used to work with images."""
|
11
|
+
|
12
|
+
@staticmethod
|
13
|
+
def polygon_points_to_np(
|
14
|
+
polygon_points: list[tuple[int, int]], divide: int | None = None
|
15
|
+
) -> np.ndarray:
|
16
|
+
"""Converts the polygon points to a NumPy array.
|
17
|
+
|
18
|
+
Arguments:
|
19
|
+
polygon_points (list[tuple[int, int]]): The polygon points.
|
20
|
+
divide (int, optional): The number to divide the points by. Defaults to None.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
np.array: The NumPy array of the polygon points.
|
24
|
+
"""
|
25
|
+
array = np.array(polygon_points, dtype=np.int32).reshape((-1, 1, 2))
|
26
|
+
if divide:
|
27
|
+
return array // divide
|
28
|
+
return array
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
def cut_out_np(
|
32
|
+
image: np.ndarray, half_size: int, set_zeros: bool = False, return_cutout: bool = False
|
33
|
+
) -> np.ndarray:
|
34
|
+
"""Cuts out a square from the center of the image.
|
35
|
+
|
36
|
+
Arguments:
|
37
|
+
image (np.ndarray): The image.
|
38
|
+
half_size (int): The half size of the square.
|
39
|
+
set_zeros (bool, optional): Whether to set the cutout to zeros. Defaults to False.
|
40
|
+
return_cutout (bool, optional): Whether to return the cutout. Defaults to False.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
np.ndarray: The image with the cutout or the cutout itself.
|
44
|
+
"""
|
45
|
+
center = (image.shape[0] // 2, image.shape[1] // 2)
|
46
|
+
x1 = center[0] - half_size
|
47
|
+
x2 = center[0] + half_size
|
48
|
+
y1 = center[1] - half_size
|
49
|
+
y2 = center[1] + half_size
|
50
|
+
|
51
|
+
if return_cutout:
|
52
|
+
return image[x1:x2, y1:y2]
|
53
|
+
|
54
|
+
if set_zeros:
|
55
|
+
image[x1:x2, y1:y2] = 0
|
56
|
+
|
57
|
+
return image
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def subtract_by_mask(
|
61
|
+
image: np.ndarray,
|
62
|
+
image_mask: np.ndarray,
|
63
|
+
subtract_by: int,
|
64
|
+
mask_by: int = 255,
|
65
|
+
erode_kernel: int | None = 3,
|
66
|
+
erode_iter: int | None = 1,
|
67
|
+
) -> np.ndarray:
|
68
|
+
"""Subtracts a value from the image where the mask is equal to the mask by value.
|
69
|
+
|
70
|
+
Arguments:
|
71
|
+
image (np.ndarray): The image.
|
72
|
+
image_mask (np.ndarray): The mask.
|
73
|
+
subtract_by (int): The value to subtract by.
|
74
|
+
mask_by (int, optional): The value to mask by. Defaults to 255.
|
75
|
+
erode_kernel (int, optional): The kernel size for the erosion. Defaults to 3.
|
76
|
+
erode_iter (int, optional): The number of iterations for the erosion. Defaults to 1.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
np.ndarray: The image with the subtracted value.
|
80
|
+
"""
|
81
|
+
mask = image_mask == mask_by
|
82
|
+
if erode_kernel and erode_iter:
|
83
|
+
mask = cv2.erode(
|
84
|
+
mask.astype(np.uint8),
|
85
|
+
np.ones((erode_kernel, erode_kernel), np.uint8),
|
86
|
+
iterations=erode_iter,
|
87
|
+
).astype(bool)
|
88
|
+
|
89
|
+
image[mask] = image[mask] - subtract_by
|
90
|
+
return image
|