maps4fs 2.8.1__tar.gz → 2.8.3__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.
Potentially problematic release.
This version of maps4fs might be problematic. Click here for more details.
- {maps4fs-2.8.1 → maps4fs-2.8.3}/PKG-INFO +15 -11
- {maps4fs-2.8.1 → maps4fs-2.8.3}/README.md +13 -10
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/background.py +282 -1
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/base/component.py +23 -4
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/base/component_image.py +33 -0
- maps4fs-2.8.3/maps4fs/generator/component/base/component_mesh.py +678 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/game.py +11 -1
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/map.py +15 -1
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/settings.py +7 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs.egg-info/PKG-INFO +15 -11
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs.egg-info/requires.txt +1 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/pyproject.toml +2 -1
- maps4fs-2.8.1/maps4fs/generator/component/base/component_mesh.py +0 -271
- {maps4fs-2.8.1 → maps4fs-2.8.3}/LICENSE.md +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/__init__.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/__init__.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/__init__.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/base/__init__.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/base/component_xml.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/config.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/dem.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/grle.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/i3d.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/layer.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/satellite.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/component/texture.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/config.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/qgis.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/statistics.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/generator/utils.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs/logger.py +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs.egg-info/SOURCES.txt +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs.egg-info/dependency_links.txt +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/maps4fs.egg-info/top_level.txt +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/setup.cfg +0 -0
- {maps4fs-2.8.1 → maps4fs-2.8.3}/tests/test_generator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maps4fs
|
|
3
|
-
Version: 2.8.
|
|
3
|
+
Version: 2.8.3
|
|
4
4
|
Summary: Generate map templates for Farming Simulator from real places.
|
|
5
5
|
Author-email: iwatkot <iwatkot@gmail.com>
|
|
6
6
|
License: GNU Affero General Public License v3.0
|
|
@@ -26,18 +26,22 @@ Requires-Dist: scipy
|
|
|
26
26
|
Requires-Dist: pydtmdl
|
|
27
27
|
Requires-Dist: manifold3d
|
|
28
28
|
Requires-Dist: fast-simplification
|
|
29
|
+
Requires-Dist: open3d
|
|
29
30
|
Dynamic: license-file
|
|
30
31
|
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
<div align="center" markdown>
|
|
33
|
+
|
|
34
|
+
[](https://github.com/iwatkot/maps4fs)
|
|
35
|
+
[](https://github.com/iwatkot/pydtmdl)
|
|
36
|
+
[](https://github.com/iwatkot/pygmdl)
|
|
37
|
+
[](https://github.com/iwatkot/maps4fsapi)
|
|
38
|
+
[](https://github.com/iwatkot/maps4fsui)
|
|
39
|
+
[](https://github.com/iwatkot/maps4fsdata)
|
|
40
|
+
[](https://github.com/iwatkot/maps4fsupgrader)
|
|
41
|
+
[](https://github.com/iwatkot/maps4fsstats)
|
|
42
|
+
[](https://github.com/iwatkot/maps4fsbot)
|
|
43
|
+
|
|
44
|
+
</div>
|
|
41
45
|
|
|
42
46
|
<div align="center" markdown>
|
|
43
47
|
<a href="https://discord.gg/Sj5QKKyE42">
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
<div align="center" markdown>
|
|
2
|
+
|
|
3
|
+
[](https://github.com/iwatkot/maps4fs)
|
|
4
|
+
[](https://github.com/iwatkot/pydtmdl)
|
|
5
|
+
[](https://github.com/iwatkot/pygmdl)
|
|
6
|
+
[](https://github.com/iwatkot/maps4fsapi)
|
|
7
|
+
[](https://github.com/iwatkot/maps4fsui)
|
|
8
|
+
[](https://github.com/iwatkot/maps4fsdata)
|
|
9
|
+
[](https://github.com/iwatkot/maps4fsupgrader)
|
|
10
|
+
[](https://github.com/iwatkot/maps4fsstats)
|
|
11
|
+
[](https://github.com/iwatkot/maps4fsbot)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
11
14
|
|
|
12
15
|
<div align="center" markdown>
|
|
13
16
|
<a href="https://discord.gg/Sj5QKKyE42">
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import os
|
|
7
7
|
import shutil
|
|
8
8
|
from copy import deepcopy
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, Literal
|
|
10
10
|
|
|
11
11
|
import cv2
|
|
12
12
|
import numpy as np
|
|
@@ -57,6 +57,14 @@ class Background(MeshComponent, ImageComponent):
|
|
|
57
57
|
os.makedirs(self.background_directory, exist_ok=True)
|
|
58
58
|
os.makedirs(self.water_directory, exist_ok=True)
|
|
59
59
|
|
|
60
|
+
self.textured_mesh_directory = os.path.join(self.background_directory, "textured_mesh")
|
|
61
|
+
os.makedirs(self.textured_mesh_directory, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
self.assets_background_directory = os.path.join(self.map.assets_directory, "background")
|
|
64
|
+
self.assets_water_directory = os.path.join(self.map.assets_directory, "water")
|
|
65
|
+
os.makedirs(self.assets_background_directory, exist_ok=True)
|
|
66
|
+
os.makedirs(self.assets_water_directory, exist_ok=True)
|
|
67
|
+
|
|
60
68
|
self.water_resources_path = os.path.join(self.water_directory, "water_resources.png")
|
|
61
69
|
|
|
62
70
|
self.output_path = os.path.join(self.background_directory, f"{Parameters.FULL}.png")
|
|
@@ -110,8 +118,28 @@ class Background(MeshComponent, ImageComponent):
|
|
|
110
118
|
|
|
111
119
|
if self.map.background_settings.generate_background:
|
|
112
120
|
self.generate_obj_files()
|
|
121
|
+
if self.game.mesh_processing:
|
|
122
|
+
self.logger.debug("Mesh processing is enabled, will decimate, texture and convert.")
|
|
123
|
+
self.decimate_background_mesh()
|
|
124
|
+
self.texture_background_mesh()
|
|
125
|
+
background_conversion_result = self.convert_background_mesh_to_i3d()
|
|
126
|
+
if background_conversion_result:
|
|
127
|
+
self.add_note_file(asset="background")
|
|
128
|
+
else:
|
|
129
|
+
self.logger.warning(
|
|
130
|
+
"Mesh processing is disabled for the game, skipping background mesh processing."
|
|
131
|
+
)
|
|
113
132
|
if self.map.background_settings.generate_water:
|
|
114
133
|
self.generate_water_resources_obj()
|
|
134
|
+
if self.game.mesh_processing:
|
|
135
|
+
self.logger.debug("Mesh processing is enabled, will convert water mesh to i3d.")
|
|
136
|
+
water_conversion_result = self.convert_water_mesh_to_i3d()
|
|
137
|
+
if water_conversion_result:
|
|
138
|
+
self.add_note_file(asset="water")
|
|
139
|
+
else:
|
|
140
|
+
self.logger.warning(
|
|
141
|
+
"Mesh processing is disabled for the game, skipping water mesh processing."
|
|
142
|
+
)
|
|
115
143
|
|
|
116
144
|
def create_foundations(self, dem_image: np.ndarray) -> np.ndarray:
|
|
117
145
|
"""Creates foundations for buildings based on the DEM data.
|
|
@@ -253,6 +281,257 @@ class Background(MeshComponent, ImageComponent):
|
|
|
253
281
|
remove_center=self.map.background_settings.remove_center,
|
|
254
282
|
)
|
|
255
283
|
|
|
284
|
+
@staticmethod
|
|
285
|
+
def get_decimate_factor(map_size: int) -> float:
|
|
286
|
+
"""Returns the decimation factor based on the map size.
|
|
287
|
+
|
|
288
|
+
Arguments:
|
|
289
|
+
map_size (int): The size of the map in pixels.
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
ValueError: If the map size is too large for decimation.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
float -- The decimation factor.
|
|
296
|
+
"""
|
|
297
|
+
thresholds = {
|
|
298
|
+
2048: 0.1,
|
|
299
|
+
4096: 0.05,
|
|
300
|
+
8192: 0.025,
|
|
301
|
+
16384: 0.0125,
|
|
302
|
+
}
|
|
303
|
+
for threshold, factor in thresholds.items():
|
|
304
|
+
if map_size <= threshold:
|
|
305
|
+
return factor
|
|
306
|
+
raise ValueError(
|
|
307
|
+
"Map size is too large for decimation, perform manual decimation in Blender."
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def get_background_texture_resolution(map_size: int) -> int:
|
|
312
|
+
"""Returns the background texture resolution based on the map size.
|
|
313
|
+
|
|
314
|
+
Arguments:
|
|
315
|
+
map_size (int): The size of the map in pixels.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
int -- The background texture resolution.
|
|
319
|
+
"""
|
|
320
|
+
resolutions = {
|
|
321
|
+
2048: 2048,
|
|
322
|
+
4096: Parameters.MAXIMUM_BACKGROUND_TEXTURE_SIZE,
|
|
323
|
+
8192: Parameters.MAXIMUM_BACKGROUND_TEXTURE_SIZE,
|
|
324
|
+
16384: Parameters.MAXIMUM_BACKGROUND_TEXTURE_SIZE,
|
|
325
|
+
}
|
|
326
|
+
for threshold, resolution in resolutions.items():
|
|
327
|
+
if map_size <= threshold:
|
|
328
|
+
return resolution
|
|
329
|
+
return Parameters.MAXIMUM_BACKGROUND_TEXTURE_SIZE
|
|
330
|
+
|
|
331
|
+
def decimate_background_mesh(self) -> None:
|
|
332
|
+
""" ""Decimates the background mesh based on the map size."""
|
|
333
|
+
if not self.assets.background_mesh or not os.path.isfile(self.assets.background_mesh):
|
|
334
|
+
self.logger.warning("Background mesh not found, cannot generate i3d background.")
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
mesh = trimesh.load_mesh(self.assets.background_mesh, force="mesh")
|
|
339
|
+
except Exception as e:
|
|
340
|
+
self.logger.error("Could not load background mesh: %s", e)
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
decimate_factor = self.get_decimate_factor(self.map_size)
|
|
345
|
+
except ValueError as e:
|
|
346
|
+
self.logger.error("Could not determine decimation factor: %s", e)
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
decimated_save_path = os.path.join(
|
|
350
|
+
self.background_directory, f"{Parameters.DECIMATED_BACKGROUND}.obj"
|
|
351
|
+
)
|
|
352
|
+
try:
|
|
353
|
+
self.logger.debug("Decimating background mesh with factor %s.", decimate_factor)
|
|
354
|
+
decimated_mesh = self.decimate_mesh(mesh, decimate_factor)
|
|
355
|
+
self.logger.debug("Decimation completed.")
|
|
356
|
+
except Exception as e:
|
|
357
|
+
self.logger.error("Could not decimate background mesh: %s", e)
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
decimated_mesh.export(decimated_save_path)
|
|
361
|
+
self.logger.debug("Decimated background mesh saved: %s", decimated_save_path)
|
|
362
|
+
|
|
363
|
+
self.assets.decimated_background_mesh = decimated_save_path
|
|
364
|
+
|
|
365
|
+
def texture_background_mesh(self) -> None:
|
|
366
|
+
"""Textures the background mesh using satellite imagery."""
|
|
367
|
+
satellite_component = self.map.get_satellite_component()
|
|
368
|
+
if not satellite_component:
|
|
369
|
+
self.logger.warning("Satellite component not found, cannot texture background mesh.")
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
background_texture_path = satellite_component.assets.background
|
|
373
|
+
|
|
374
|
+
if not background_texture_path or not os.path.isfile(background_texture_path):
|
|
375
|
+
self.logger.warning("Background texture not found, cannot texture background mesh.")
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
decimated_background_mesh_path = self.assets.decimated_background_mesh
|
|
379
|
+
if not decimated_background_mesh_path or not os.path.isfile(decimated_background_mesh_path):
|
|
380
|
+
self.logger.warning(
|
|
381
|
+
"Decimated background mesh not found, cannot texture background mesh."
|
|
382
|
+
)
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
background_texture_resolution = self.get_background_texture_resolution(self.map_size)
|
|
386
|
+
non_resized_texture_image = cv2.imread(background_texture_path, cv2.IMREAD_UNCHANGED)
|
|
387
|
+
|
|
388
|
+
if non_resized_texture_image is None:
|
|
389
|
+
self.logger.error(
|
|
390
|
+
"Failed to read background texture image: %s", background_texture_path
|
|
391
|
+
)
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
resized_texture_image = cv2.resize(
|
|
395
|
+
non_resized_texture_image, # type: ignore
|
|
396
|
+
(background_texture_resolution, background_texture_resolution),
|
|
397
|
+
interpolation=cv2.INTER_AREA,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
resized_texture_save_path = os.path.join(
|
|
401
|
+
self.textured_mesh_directory,
|
|
402
|
+
"background_texture.jpg",
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
cv2.imwrite(resized_texture_save_path, resized_texture_image)
|
|
406
|
+
self.logger.debug("Resized background texture saved: %s", resized_texture_save_path)
|
|
407
|
+
|
|
408
|
+
decimated_mesh = trimesh.load_mesh(decimated_background_mesh_path, force="mesh")
|
|
409
|
+
|
|
410
|
+
if decimated_mesh is None:
|
|
411
|
+
self.logger.error("Failed to load decimated mesh after all retry attempts")
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
obj_save_path, mtl_save_path = self.texture_mesh(
|
|
416
|
+
decimated_mesh,
|
|
417
|
+
resized_texture_save_path,
|
|
418
|
+
output_directory=self.textured_mesh_directory,
|
|
419
|
+
output_name="background_textured_mesh",
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
self.assets.textured_background_mesh = obj_save_path
|
|
423
|
+
self.assets.textured_background_mtl = mtl_save_path
|
|
424
|
+
self.assets.resized_background_texture = resized_texture_save_path
|
|
425
|
+
self.logger.debug("Textured background mesh saved: %s", obj_save_path)
|
|
426
|
+
except Exception as e:
|
|
427
|
+
self.logger.error("Could not texture background mesh: %s", e)
|
|
428
|
+
return
|
|
429
|
+
|
|
430
|
+
def convert_background_mesh_to_i3d(self) -> bool:
|
|
431
|
+
"""Converts the textured background mesh to i3d format.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
bool -- True if the conversion was successful, False otherwise.
|
|
435
|
+
"""
|
|
436
|
+
if not self.assets.textured_background_mesh or not os.path.isfile(
|
|
437
|
+
self.assets.textured_background_mesh
|
|
438
|
+
):
|
|
439
|
+
self.logger.warning("Textured background mesh not found, cannot convert to i3d.")
|
|
440
|
+
return False
|
|
441
|
+
|
|
442
|
+
if not self.assets.resized_background_texture or not os.path.isfile(
|
|
443
|
+
self.assets.resized_background_texture
|
|
444
|
+
):
|
|
445
|
+
self.logger.warning("Resized background texture not found, cannot convert to i3d.")
|
|
446
|
+
return False
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
mesh = trimesh.load_mesh(self.assets.textured_background_mesh, force="mesh")
|
|
450
|
+
except Exception as e:
|
|
451
|
+
self.logger.error("Could not load textured background mesh: %s", e)
|
|
452
|
+
return False
|
|
453
|
+
|
|
454
|
+
try:
|
|
455
|
+
i3d_background_terrain = self.mesh_to_i3d(
|
|
456
|
+
mesh,
|
|
457
|
+
output_dir=self.assets_background_directory,
|
|
458
|
+
name=Parameters.BACKGROUND_TERRAIN,
|
|
459
|
+
texture_path=self.assets.resized_background_texture,
|
|
460
|
+
water_mesh=False,
|
|
461
|
+
)
|
|
462
|
+
self.logger.debug(
|
|
463
|
+
"Background mesh converted to i3d successfully: %s", i3d_background_terrain
|
|
464
|
+
)
|
|
465
|
+
self.assets.background_terrain_i3d = i3d_background_terrain
|
|
466
|
+
return True
|
|
467
|
+
except Exception as e:
|
|
468
|
+
self.logger.error("Could not convert background mesh to i3d: %s", e)
|
|
469
|
+
return False
|
|
470
|
+
|
|
471
|
+
def add_note_file(self, asset: Literal["background", "water"]) -> None:
|
|
472
|
+
"""Adds a note file to the background or water directory.
|
|
473
|
+
|
|
474
|
+
Arguments:
|
|
475
|
+
asset (Literal["background", "water"]): The asset type to add the note file to.
|
|
476
|
+
"""
|
|
477
|
+
filename = "DO_NOT_USE_THESE_FILES.txt"
|
|
478
|
+
note_template = (
|
|
479
|
+
"Please find the ready-to-use {asset} i3d files in the {asset_directory} directory."
|
|
480
|
+
)
|
|
481
|
+
directory = {
|
|
482
|
+
"background": self.background_directory,
|
|
483
|
+
"water": self.water_directory,
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
content = (
|
|
487
|
+
"The files in this directory can be used to create the mesh files manually in Blender. "
|
|
488
|
+
"However, it's recommended to use the ready-to-use i3d files located in the assets "
|
|
489
|
+
"directory. There you'll find the i3d files, that can be imported directly into the "
|
|
490
|
+
"Giants Editor without any additional processing."
|
|
491
|
+
)
|
|
492
|
+
note = note_template.format(
|
|
493
|
+
asset=asset,
|
|
494
|
+
asset_directory=f"assets/{asset}",
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
file_path = os.path.join(directory[asset], filename)
|
|
498
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
499
|
+
f.write(content + "\n\n" + note)
|
|
500
|
+
|
|
501
|
+
def convert_water_mesh_to_i3d(self) -> bool:
|
|
502
|
+
"""Converts the line-based water mesh to i3d format.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
bool -- True if the conversion was successful, False otherwise.
|
|
506
|
+
"""
|
|
507
|
+
if not self.assets.line_based_water_mesh or not os.path.isfile(
|
|
508
|
+
self.assets.line_based_water_mesh
|
|
509
|
+
):
|
|
510
|
+
self.logger.warning("Line-based water mesh not found, cannot convert to i3d.")
|
|
511
|
+
return False
|
|
512
|
+
|
|
513
|
+
try:
|
|
514
|
+
mesh = trimesh.load_mesh(self.assets.line_based_water_mesh, force="mesh")
|
|
515
|
+
except Exception as e:
|
|
516
|
+
self.logger.error("Could not load line-based water mesh: %s", e)
|
|
517
|
+
return False
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
i3d_water_resources = self.mesh_to_i3d(
|
|
521
|
+
mesh,
|
|
522
|
+
output_dir=self.assets_water_directory,
|
|
523
|
+
name=Parameters.WATER_RESOURCES,
|
|
524
|
+
water_mesh=True,
|
|
525
|
+
)
|
|
526
|
+
self.logger.debug(
|
|
527
|
+
"Water resources mesh converted to i3d successfully: %s", i3d_water_resources
|
|
528
|
+
)
|
|
529
|
+
self.assets.water_resources_i3d = i3d_water_resources
|
|
530
|
+
return True
|
|
531
|
+
except Exception as e:
|
|
532
|
+
self.logger.error("Could not convert water mesh to i3d: %s", e)
|
|
533
|
+
return False
|
|
534
|
+
|
|
256
535
|
def save_map_dem(self, dem_path: str, save_path: str | None = None) -> str:
|
|
257
536
|
"""Cuts out the center of the DEM (the actual map) and saves it as a separate file.
|
|
258
537
|
|
|
@@ -623,6 +902,8 @@ class Background(MeshComponent, ImageComponent):
|
|
|
623
902
|
mesh.export(line_based_save_path)
|
|
624
903
|
self.logger.debug("Line-based water mesh saved to %s", line_based_save_path)
|
|
625
904
|
|
|
905
|
+
self.assets.line_based_water_mesh = line_based_save_path
|
|
906
|
+
|
|
626
907
|
def mesh_from_3d_polygons(
|
|
627
908
|
self, polygons: list[shapely.Polygon], single_z_value: int | None = None
|
|
628
909
|
) -> Trimesh | None:
|
|
@@ -22,12 +22,31 @@ if TYPE_CHECKING:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class AttrDict(dict):
|
|
25
|
-
"""A dictionary that allows attribute-style access to its keys.
|
|
25
|
+
"""A dictionary that allows attribute-style access to its keys.
|
|
26
|
+
Allows safe access to non-existing keys, returning None instead of raising KeyError.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __getattr__(self, name: str) -> Any | None:
|
|
30
|
+
"""Returns the value of the given key or None if the key does not exist.
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
Arguments:
|
|
33
|
+
name (str): The key to retrieve.
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
Returns:
|
|
36
|
+
Any | None: The value of the key or None if the key does not exist.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
return self[name]
|
|
40
|
+
except KeyError:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
44
|
+
"""Sets the value of the given key.
|
|
45
|
+
|
|
46
|
+
Arguments:
|
|
47
|
+
name (str): The key to set.
|
|
48
|
+
value (Any): The value to set.
|
|
49
|
+
"""
|
|
31
50
|
self[name] = value
|
|
32
51
|
|
|
33
52
|
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
|
|
5
5
|
import cv2
|
|
6
6
|
import numpy as np
|
|
7
|
+
from PIL import Image, ImageFile
|
|
7
8
|
|
|
8
9
|
from maps4fs.generator.component.base.component import Component
|
|
9
10
|
from maps4fs.generator.settings import Parameters
|
|
@@ -237,3 +238,35 @@ class ImageComponent(Component):
|
|
|
237
238
|
edge_mask = cv2.subtract(bigger_mask, smaller_mask)
|
|
238
239
|
|
|
239
240
|
return self.blur_by_mask(data, edge_mask)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def convert_png_to_dds(input_png_path: str, output_dds_path: str):
|
|
244
|
+
"""Convert a PNG file to DDS format using PIL
|
|
245
|
+
|
|
246
|
+
Arguments:
|
|
247
|
+
input_png_path (str): Path to input PNG file
|
|
248
|
+
output_dds_path (str): Path for output DDS file
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
FileNotFoundError: If the input PNG file does not exist.
|
|
252
|
+
RuntimeError: If the DDS conversion fails.
|
|
253
|
+
"""
|
|
254
|
+
if not os.path.exists(input_png_path):
|
|
255
|
+
raise FileNotFoundError(f"Input PNG file not found: {input_png_path}")
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
|
259
|
+
|
|
260
|
+
with Image.open(input_png_path) as img:
|
|
261
|
+
# Convert to RGB if needed (DDS works better with RGB)
|
|
262
|
+
if img.mode == "RGBA":
|
|
263
|
+
# Create RGB version on white background
|
|
264
|
+
rgb_img = Image.new("RGB", img.size, (255, 255, 255))
|
|
265
|
+
rgb_img.paste(img, mask=img.split()[-1]) # Use alpha as mask
|
|
266
|
+
img = rgb_img
|
|
267
|
+
elif img.mode != "RGB":
|
|
268
|
+
img = img.convert("RGB")
|
|
269
|
+
|
|
270
|
+
img.save(output_dds_path, format="DDS")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
raise RuntimeError(f"DDS conversion failed: {e}")
|