maps4fs 2.9.2__py3-none-any.whl → 2.9.37__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/component/background.py +18 -3
- maps4fs/generator/component/building.py +706 -0
- maps4fs/generator/component/grle.py +57 -0
- maps4fs/generator/component/i3d.py +59 -5
- maps4fs/generator/component/layer.py +19 -0
- maps4fs/generator/component/road.py +648 -0
- maps4fs/generator/component/texture.py +16 -4
- maps4fs/generator/config.py +79 -11
- maps4fs/generator/game.py +64 -3
- maps4fs/generator/map.py +3 -0
- maps4fs/generator/settings.py +18 -1
- maps4fs/generator/utils.py +15 -1
- {maps4fs-2.9.2.dist-info → maps4fs-2.9.37.dist-info}/METADATA +6 -4
- maps4fs-2.9.37.dist-info/RECORD +32 -0
- maps4fs-2.9.37.dist-info/licenses/LICENSE.md +416 -0
- maps4fs-2.9.2.dist-info/RECORD +0 -30
- maps4fs-2.9.2.dist-info/licenses/LICENSE.md +0 -651
- {maps4fs-2.9.2.dist-info → maps4fs-2.9.37.dist-info}/WHEEL +0 -0
- {maps4fs-2.9.2.dist-info → maps4fs-2.9.37.dist-info}/top_level.txt +0 -0
|
@@ -168,6 +168,14 @@ class Texture(ImageComponent):
|
|
|
168
168
|
"""
|
|
169
169
|
return [layer for layer in self.layers if layer.indoor]
|
|
170
170
|
|
|
171
|
+
def get_building_category_layers(self) -> list[Layer]:
|
|
172
|
+
"""Returns layers which have building category defined.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
list[Layer]: List of layers which have building category defined.
|
|
176
|
+
"""
|
|
177
|
+
return [layer for layer in self.layers if layer.building_category is not None]
|
|
178
|
+
|
|
171
179
|
def process(self) -> None:
|
|
172
180
|
"""Processes the data to generate textures."""
|
|
173
181
|
self._prepare_weights()
|
|
@@ -469,8 +477,11 @@ class Texture(ImageComponent):
|
|
|
469
477
|
self._draw_layer(layer, info_layer_data, layer_image) # type: ignore
|
|
470
478
|
self._add_roads(layer, info_layer_data)
|
|
471
479
|
|
|
472
|
-
|
|
473
|
-
|
|
480
|
+
if not layer.external:
|
|
481
|
+
output_image = cv2.bitwise_and(layer_image, mask) # type: ignore
|
|
482
|
+
cumulative_image = cv2.bitwise_or(cumulative_image, output_image) # type: ignore
|
|
483
|
+
else:
|
|
484
|
+
output_image = layer_image # type: ignore
|
|
474
485
|
|
|
475
486
|
cv2.imwrite(layer_path, output_image)
|
|
476
487
|
self.logger.debug("Texture %s saved.", layer_path)
|
|
@@ -550,6 +561,7 @@ class Texture(ImageComponent):
|
|
|
550
561
|
"points": linestring,
|
|
551
562
|
"tags": str(layer.tags),
|
|
552
563
|
"width": layer.width,
|
|
564
|
+
"road_texture": layer.road_texture,
|
|
553
565
|
}
|
|
554
566
|
info_layer_data[f"{layer.info_layer}_polylines"].append(linestring_entry) # type: ignore
|
|
555
567
|
|
|
@@ -803,7 +815,7 @@ class Texture(ImageComponent):
|
|
|
803
815
|
is_fieds = info_layer == "fields"
|
|
804
816
|
|
|
805
817
|
ox_settings.use_cache = self.map.texture_settings.use_cache
|
|
806
|
-
ox_settings.requests_timeout =
|
|
818
|
+
ox_settings.requests_timeout = 10
|
|
807
819
|
|
|
808
820
|
objects = self.fetch_osm_data(tags)
|
|
809
821
|
if objects is None or objects.empty:
|
|
@@ -834,7 +846,7 @@ class Texture(ImageComponent):
|
|
|
834
846
|
else:
|
|
835
847
|
objects = ox.features_from_bbox(bbox=self.new_bbox, tags=tags)
|
|
836
848
|
except Exception as e:
|
|
837
|
-
self.logger.
|
|
849
|
+
self.logger.warning("Error fetching objects for tags: %s. Error: %s.", tags, e)
|
|
838
850
|
return None
|
|
839
851
|
|
|
840
852
|
return objects
|
maps4fs/generator/config.py
CHANGED
|
@@ -40,7 +40,7 @@ logger.info(
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
TEMPLATES_STRUCTURE = {
|
|
43
|
-
"fs25": ["texture_schemas", "tree_schemas", "map_templates"],
|
|
43
|
+
"fs25": ["texture_schemas", "tree_schemas", "buildings_schemas", "map_templates"],
|
|
44
44
|
"fs22": ["texture_schemas", "map_templates"],
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -51,11 +51,19 @@ def ensure_templates():
|
|
|
51
51
|
If MFS_TEMPLATES_DIR is empty or doesn't exist, clone the maps4fsdata
|
|
52
52
|
repository and run the preparation script to populate it.
|
|
53
53
|
"""
|
|
54
|
-
|
|
55
54
|
# Check if templates directory exists and has content
|
|
56
|
-
if os.path.exists(MFS_TEMPLATES_DIR) and os.listdir(MFS_TEMPLATES_DIR):
|
|
57
|
-
logger.info("Templates directory already exists
|
|
58
|
-
|
|
55
|
+
if os.path.exists(MFS_TEMPLATES_DIR): # and os.listdir(MFS_TEMPLATES_DIR):
|
|
56
|
+
logger.info("Templates directory already exists: %s", MFS_TEMPLATES_DIR)
|
|
57
|
+
|
|
58
|
+
files = [
|
|
59
|
+
entry
|
|
60
|
+
for entry in os.listdir(MFS_TEMPLATES_DIR)
|
|
61
|
+
if os.path.isfile(os.path.join(MFS_TEMPLATES_DIR, entry))
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
if files:
|
|
65
|
+
logger.info("Templates directory contains files and will not be modified.")
|
|
66
|
+
return
|
|
59
67
|
|
|
60
68
|
logger.info("Templates directory is empty or missing, preparing data...")
|
|
61
69
|
|
|
@@ -82,15 +90,31 @@ def ensure_templates():
|
|
|
82
90
|
text=True,
|
|
83
91
|
)
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
if os.name == "nt":
|
|
94
|
+
logger.info("Detected Windows OS, running PowerShell preparation script...")
|
|
95
|
+
prep_script = os.path.join(clone_dir, "prepare_data.ps1")
|
|
96
|
+
for_subprocess = [
|
|
97
|
+
"powershell",
|
|
98
|
+
"-ExecutionPolicy",
|
|
99
|
+
"Bypass",
|
|
100
|
+
"-File",
|
|
101
|
+
"prepare_data.ps1",
|
|
102
|
+
]
|
|
103
|
+
else:
|
|
104
|
+
logger.info("Detected non-Windows OS, running bash preparation script...")
|
|
105
|
+
prep_script = os.path.join(clone_dir, "prepare_data.sh")
|
|
106
|
+
for_subprocess = ["./prepare_data.sh"]
|
|
107
|
+
|
|
87
108
|
if os.path.exists(prep_script):
|
|
88
|
-
|
|
109
|
+
try:
|
|
110
|
+
os.chmod(prep_script, 0o755)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.warning("Could not set execute permissions on script: %s", str(e))
|
|
89
113
|
|
|
90
114
|
logger.info("Running data preparation script...")
|
|
91
115
|
# Run the preparation script from the cloned directory
|
|
92
116
|
subprocess.run(
|
|
93
|
-
|
|
117
|
+
for_subprocess, cwd=clone_dir, check=True, capture_output=True, text=True
|
|
94
118
|
)
|
|
95
119
|
|
|
96
120
|
# Copy the generated data directory to templates directory
|
|
@@ -140,6 +164,27 @@ def ensure_template_subdirs() -> None:
|
|
|
140
164
|
logger.info("Templates directory is ready at: %s", MFS_TEMPLATES_DIR)
|
|
141
165
|
|
|
142
166
|
|
|
167
|
+
def reload_templates() -> None:
|
|
168
|
+
"""Reload templates by removing existing files and re-preparing them.
|
|
169
|
+
Does not affect nested directories containing user data.
|
|
170
|
+
If needed, the files should be removed manually.
|
|
171
|
+
"""
|
|
172
|
+
logger.info("Reloading templates...")
|
|
173
|
+
# Remove files from the templates directory.
|
|
174
|
+
# But do not remove nested directories, because they contain user data.
|
|
175
|
+
# Only remove files in the top-level templates directory.
|
|
176
|
+
for item in os.listdir(MFS_TEMPLATES_DIR):
|
|
177
|
+
item_path = os.path.join(MFS_TEMPLATES_DIR, item)
|
|
178
|
+
if os.path.isfile(item_path):
|
|
179
|
+
try:
|
|
180
|
+
os.remove(item_path)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.warning("Could not remove file %s: %s", item_path, str(e))
|
|
183
|
+
ensure_templates()
|
|
184
|
+
ensure_template_subdirs()
|
|
185
|
+
logger.info("Templates reloaded successfully.")
|
|
186
|
+
|
|
187
|
+
|
|
143
188
|
ensure_templates()
|
|
144
189
|
ensure_template_subdirs()
|
|
145
190
|
|
|
@@ -160,8 +205,31 @@ SAT_CACHE_DIR = os.path.join(MFS_CACHE_DIR, "sat")
|
|
|
160
205
|
|
|
161
206
|
osmnx_cache = os.path.join(MFS_CACHE_DIR, "osmnx")
|
|
162
207
|
osmnx_data = os.path.join(MFS_CACHE_DIR, "odata")
|
|
163
|
-
|
|
164
|
-
|
|
208
|
+
|
|
209
|
+
CACHE_DIRS = [DTM_CACHE_DIR, SAT_CACHE_DIR, osmnx_cache, osmnx_data]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def create_cache_dirs() -> None:
|
|
213
|
+
"""Create cache directories if they do not exist."""
|
|
214
|
+
logger.info("Ensuring cache directories exist...")
|
|
215
|
+
for cache_dir in CACHE_DIRS:
|
|
216
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
217
|
+
logger.debug("Cache directory ensured: %s", cache_dir)
|
|
218
|
+
logger.info("All cache directories are ready.")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def clean_cache() -> None:
|
|
222
|
+
"""Clean all cache directories by removing and recreating them."""
|
|
223
|
+
logger.info("Cleaning cache directories...")
|
|
224
|
+
for cache_dir in CACHE_DIRS:
|
|
225
|
+
if os.path.exists(cache_dir):
|
|
226
|
+
shutil.rmtree(cache_dir)
|
|
227
|
+
logger.debug("Removed cache directory: %s", cache_dir)
|
|
228
|
+
create_cache_dirs()
|
|
229
|
+
logger.info("Cache directories cleaned and recreated.")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
create_cache_dirs()
|
|
165
233
|
|
|
166
234
|
|
|
167
235
|
ox_settings.cache_folder = osmnx_cache
|
maps4fs/generator/game.py
CHANGED
|
@@ -5,12 +5,15 @@ template file and specific settings for map generation."""
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
from typing import Callable
|
|
8
9
|
|
|
9
10
|
import maps4fs.generator.config as mfscfg
|
|
10
11
|
from maps4fs.generator.component.background import Background
|
|
12
|
+
from maps4fs.generator.component.building import Building
|
|
11
13
|
from maps4fs.generator.component.config import Config
|
|
12
14
|
from maps4fs.generator.component.grle import GRLE
|
|
13
15
|
from maps4fs.generator.component.i3d import I3d
|
|
16
|
+
from maps4fs.generator.component.road import Road
|
|
14
17
|
from maps4fs.generator.component.satellite import Satellite
|
|
15
18
|
from maps4fs.generator.component.texture import Texture
|
|
16
19
|
|
|
@@ -37,6 +40,7 @@ class Game:
|
|
|
37
40
|
_texture_schema_file: str | None = None
|
|
38
41
|
_grle_schema_file: str | None = None
|
|
39
42
|
_tree_schema_file: str | None = None
|
|
43
|
+
_buildings_schema_file: str | None = None
|
|
40
44
|
_i3d_processing: bool = True
|
|
41
45
|
_plants_processing: bool = True
|
|
42
46
|
_environment_processing: bool = True
|
|
@@ -45,7 +49,7 @@ class Game:
|
|
|
45
49
|
_mesh_processing: bool = True
|
|
46
50
|
|
|
47
51
|
# Order matters! Some components depend on others.
|
|
48
|
-
components = [Satellite, Texture, Background, GRLE, I3d, Config]
|
|
52
|
+
components = [Satellite, Texture, Background, GRLE, I3d, Config, Road, Building]
|
|
49
53
|
|
|
50
54
|
def __init__(self, map_template_path: str | None = None):
|
|
51
55
|
if map_template_path:
|
|
@@ -72,6 +76,11 @@ class Game:
|
|
|
72
76
|
else:
|
|
73
77
|
self._tree_schema = os.path.join(mfscfg.MFS_TEMPLATES_DIR, self._tree_schema_file) # type: ignore
|
|
74
78
|
|
|
79
|
+
if not self._buildings_schema_file:
|
|
80
|
+
self._buildings_schema_file = None
|
|
81
|
+
else:
|
|
82
|
+
self._buildings_schema_file = os.path.join(mfscfg.MFS_TEMPLATES_DIR, self._buildings_schema_file) # type: ignore
|
|
83
|
+
|
|
75
84
|
def set_components_by_names(self, component_names: list[str]) -> None:
|
|
76
85
|
"""Sets the components used for map generation by their names.
|
|
77
86
|
|
|
@@ -159,6 +168,19 @@ class Game:
|
|
|
159
168
|
raise ValueError("Tree layers schema path not set.")
|
|
160
169
|
return self._tree_schema
|
|
161
170
|
|
|
171
|
+
@property
|
|
172
|
+
def buildings_schema(self) -> str:
|
|
173
|
+
"""Returns the path to the buildings layers schema file.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
ValueError: If the buildings layers schema path is not set.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
str: The path to the buildings layers schema file."""
|
|
180
|
+
if not self._buildings_schema_file:
|
|
181
|
+
raise ValueError("Buildings layers schema path not set.")
|
|
182
|
+
return self._buildings_schema_file
|
|
183
|
+
|
|
162
184
|
def dem_file_path(self, map_directory: str) -> str:
|
|
163
185
|
"""Returns the path to the DEM file.
|
|
164
186
|
|
|
@@ -166,8 +188,7 @@ class Game:
|
|
|
166
188
|
map_directory (str): The path to the map directory.
|
|
167
189
|
|
|
168
190
|
Returns:
|
|
169
|
-
str: The path to the DEM file.
|
|
170
|
-
"""
|
|
191
|
+
str: The path to the DEM file."""
|
|
171
192
|
raise NotImplementedError
|
|
172
193
|
|
|
173
194
|
def weights_dir_path(self, map_directory: str) -> str:
|
|
@@ -341,6 +362,45 @@ class Game:
|
|
|
341
362
|
bool: True if the mesh should be processed, False otherwise."""
|
|
342
363
|
return self._mesh_processing
|
|
343
364
|
|
|
365
|
+
def validate_template(self, map_directory: str) -> None:
|
|
366
|
+
"""Validates that all required files exist in the map template directory.
|
|
367
|
+
|
|
368
|
+
Arguments:
|
|
369
|
+
map_directory (str): The path to the map directory.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
FileNotFoundError: If any required files are missing from the template.
|
|
373
|
+
"""
|
|
374
|
+
all_files = []
|
|
375
|
+
for root, _, files in os.walk(map_directory):
|
|
376
|
+
for file in files:
|
|
377
|
+
all_files.append(os.path.join(root, file))
|
|
378
|
+
|
|
379
|
+
missing_files = []
|
|
380
|
+
for func in self.required_file_methods():
|
|
381
|
+
try:
|
|
382
|
+
required_filepath = func(map_directory)
|
|
383
|
+
except NotImplementedError:
|
|
384
|
+
continue
|
|
385
|
+
if required_filepath not in all_files:
|
|
386
|
+
missing_files.append(required_filepath)
|
|
387
|
+
if missing_files:
|
|
388
|
+
raise FileNotFoundError(f"The following files are not found: {missing_files}.")
|
|
389
|
+
|
|
390
|
+
def required_file_methods(self) -> list[Callable[[str], str]]:
|
|
391
|
+
"""Returns a list of methods that return paths to required files for map generation.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
list[Callable[[str], str]]: List of methods that take a map directory path
|
|
395
|
+
and return file paths that are required for the map template.
|
|
396
|
+
"""
|
|
397
|
+
return [
|
|
398
|
+
self.map_xml_path,
|
|
399
|
+
self.i3d_file_path,
|
|
400
|
+
self.get_environment_xml_path,
|
|
401
|
+
self.get_farmlands_xml_path,
|
|
402
|
+
]
|
|
403
|
+
|
|
344
404
|
|
|
345
405
|
class FS22(Game):
|
|
346
406
|
"""Class used to define the game version FS22."""
|
|
@@ -396,6 +456,7 @@ class FS25(Game):
|
|
|
396
456
|
_texture_schema_file = "fs25-texture-schema.json"
|
|
397
457
|
_grle_schema_file = "fs25-grle-schema.json"
|
|
398
458
|
_tree_schema_file = "fs25-tree-schema.json"
|
|
459
|
+
_buildings_schema_file = "fs25-buildings-schema.json"
|
|
399
460
|
|
|
400
461
|
def dem_file_path(self, map_directory: str) -> str:
|
|
401
462
|
"""Returns the path to the DEM file.
|
maps4fs/generator/map.py
CHANGED
|
@@ -105,6 +105,7 @@ class Map:
|
|
|
105
105
|
self.i3d_settings = generation_settings.i3d_settings
|
|
106
106
|
self.texture_settings = generation_settings.texture_settings
|
|
107
107
|
self.satellite_settings = generation_settings.satellite_settings
|
|
108
|
+
self.building_settings = generation_settings.building_settings
|
|
108
109
|
self.process_settings()
|
|
109
110
|
|
|
110
111
|
self.logger = logger if logger else Logger()
|
|
@@ -123,11 +124,13 @@ class Map:
|
|
|
123
124
|
os.makedirs(self.map_directory, exist_ok=True)
|
|
124
125
|
self.texture_custom_schema = kwargs.get("texture_custom_schema", None)
|
|
125
126
|
self.tree_custom_schema = kwargs.get("tree_custom_schema", None)
|
|
127
|
+
self.buildings_custom_schema = kwargs.get("buildings_custom_schema", None)
|
|
126
128
|
|
|
127
129
|
json_data = {
|
|
128
130
|
"generation_settings.json": generation_settings_json,
|
|
129
131
|
"texture_custom_schema.json": self.texture_custom_schema,
|
|
130
132
|
"tree_custom_schema.json": self.tree_custom_schema,
|
|
133
|
+
"buildings_custom_schema.json": self.buildings_custom_schema,
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
for filename, data in json_data.items():
|
maps4fs/generator/settings.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Literal, NamedTuple
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, ConfigDict
|
|
10
10
|
|
|
@@ -279,6 +279,21 @@ class SatelliteSettings(SettingsModel):
|
|
|
279
279
|
zoom_level: int = 16
|
|
280
280
|
|
|
281
281
|
|
|
282
|
+
class BuildingSettings(SettingsModel):
|
|
283
|
+
"""Represents the advanced settings for building component.
|
|
284
|
+
|
|
285
|
+
Attributes:
|
|
286
|
+
generate_buildings (bool): generate buildings on the map.
|
|
287
|
+
region (Literal["auto", "all", "EU", "US"]): region for the buildings.
|
|
288
|
+
tolerance_factor (float): tolerance factor representing allowed dimension difference
|
|
289
|
+
between OSM building footprint and the building model footprint.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
generate_buildings: bool = True
|
|
293
|
+
region: Literal["auto", "all", "EU", "US"] = "auto"
|
|
294
|
+
tolerance_factor: float = 0.3
|
|
295
|
+
|
|
296
|
+
|
|
282
297
|
class GenerationSettings(BaseModel):
|
|
283
298
|
"""Represents the settings for the map generation process."""
|
|
284
299
|
|
|
@@ -288,6 +303,7 @@ class GenerationSettings(BaseModel):
|
|
|
288
303
|
i3d_settings: I3DSettings = I3DSettings()
|
|
289
304
|
texture_settings: TextureSettings = TextureSettings()
|
|
290
305
|
satellite_settings: SatelliteSettings = SatelliteSettings()
|
|
306
|
+
building_settings: BuildingSettings = BuildingSettings()
|
|
291
307
|
|
|
292
308
|
def to_json(self) -> dict[str, Any]:
|
|
293
309
|
"""Convert the GenerationSettings instance to JSON format.
|
|
@@ -302,6 +318,7 @@ class GenerationSettings(BaseModel):
|
|
|
302
318
|
"I3DSettings": self.i3d_settings.model_dump(),
|
|
303
319
|
"TextureSettings": self.texture_settings.model_dump(),
|
|
304
320
|
"SatelliteSettings": self.satellite_settings.model_dump(),
|
|
321
|
+
"BuildingSettings": self.building_settings.model_dump(),
|
|
305
322
|
}
|
|
306
323
|
|
|
307
324
|
@classmethod
|
maps4fs/generator/utils.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import shutil
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Literal
|
|
8
8
|
from xml.etree import ElementTree as ET
|
|
9
9
|
|
|
10
10
|
import osmnx as ox
|
|
@@ -122,6 +122,20 @@ def get_country_by_coordinates(coordinates: tuple[float, float]) -> str:
|
|
|
122
122
|
return "Unknown"
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def get_region_by_coordinates(coordinates: tuple[float, float]) -> Literal["EU", "US"]:
|
|
126
|
+
"""Get region (EU or US) by coordinates.
|
|
127
|
+
|
|
128
|
+
Arguments:
|
|
129
|
+
coordinates (tuple[float, float]): Latitude and longitude.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Literal["EU", "US"]: Region code.
|
|
133
|
+
"""
|
|
134
|
+
country = get_country_by_coordinates(coordinates)
|
|
135
|
+
# If country is not US, assume EU for simplicity.
|
|
136
|
+
return "US" if country == "United States" else "EU"
|
|
137
|
+
|
|
138
|
+
|
|
125
139
|
def get_timestamp() -> str:
|
|
126
140
|
"""Get current underscore-separated timestamp.
|
|
127
141
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maps4fs
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.37
|
|
4
4
|
Summary: Generate map templates for Farming Simulator from real places.
|
|
5
5
|
Author-email: iwatkot <iwatkot@gmail.com>
|
|
6
|
-
License:
|
|
6
|
+
License-Expression: CC-BY-NC-4.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/iwatkot/maps4fs
|
|
8
8
|
Project-URL: Repository, https://github.com/iwatkot/maps4fs
|
|
9
9
|
Keywords: farmingsimulator,fs,farmingsimulator22,farmingsimulator25,fs22,fs25
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
13
12
|
Classifier: Operating System :: OS Independent
|
|
14
13
|
Description-Content-Type: text/markdown
|
|
15
14
|
License-File: LICENSE.md
|
|
@@ -76,6 +75,7 @@ Dynamic: license-file
|
|
|
76
75
|
🚜 **Farming Simulator 22 & 25** - Generate maps for both game versions<br>
|
|
77
76
|
🗺️ **Flexible Map Sizes** - 2x2, 4x4, 8x8, 16x16 km + custom sizes<br>
|
|
78
77
|
✂️ **Map Scaling & Rotation** - Perfect positioning and sizing control<br>
|
|
78
|
+
🏘️ **Adding buildings** - Automatic building placement system<br>
|
|
79
79
|
|
|
80
80
|
🌍 **Real-World Foundation** - Built from OpenStreetMap and satellite data<br>
|
|
81
81
|
🏞️ **Accurate Terrain** - SRTM elevation data with custom DTM support<br>
|
|
@@ -86,7 +86,9 @@ Dynamic: license-file
|
|
|
86
86
|
🌲 **Natural Forests** - Tree placement with customizable density<br>
|
|
87
87
|
🌊 **Water Systems** - Rivers, lakes, and water planes<br>
|
|
88
88
|
🌿 **Decorative Foliage** - Realistic vegetation and grass areas<br>
|
|
89
|
+
🏘️ **Intelligent Building Placement** - Automatic building placement in appropriate areas<br>
|
|
89
90
|
|
|
91
|
+
🚧 **3D Road Generation** - Automatic road mesh creation with custom textures<br>
|
|
90
92
|
🚧 **Complete Spline Networks** - Roads and infrastructure<br>
|
|
91
93
|
🔷 **Background Terrain** - 3D *.obj files for surrounding landscape<br>
|
|
92
94
|
📦 **Giants Editor Ready** - Import and start building immediately<br>
|
|
@@ -96,7 +98,7 @@ Dynamic: license-file
|
|
|
96
98
|
📚 **Complete Documentation** - [Detailed guides](https://maps4fs.gitbook.io/docs) and video tutorials<br>
|
|
97
99
|
|
|
98
100
|
<p align="center">
|
|
99
|
-
<img src="https://github.com/iwatkot/
|
|
101
|
+
<img src="https://github.com/iwatkot/maps4fs/releases/download/2.9.34/mfsg.gif"><br>
|
|
100
102
|
<i>Example of map generated with Maps4FS with no manual edits.</i>
|
|
101
103
|
</p>
|
|
102
104
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
maps4fs/__init__.py,sha256=5ixsCA5vgcIV0OrF9EJBm91Mmc_KfMiDRM-QyifMAvo,386
|
|
2
|
+
maps4fs/logger.py,sha256=aZAa9glzgvH6ySVDLelSPTwHfWZtpGK5YBl-ufNUsPg,801
|
|
3
|
+
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
|
4
|
+
maps4fs/generator/config.py,sha256=o_zRT8Nauy1djELsC867yD7GTs1fwz1OtZ2jE4w_7Ts,9797
|
|
5
|
+
maps4fs/generator/game.py,sha256=B7-a9gEBIOKAmC2eAAnEx65Ghx6U-edvNvU4a3WmjIw,18513
|
|
6
|
+
maps4fs/generator/map.py,sha256=kkyMETKteFhnWRgmcR8gjdNBQy4roQzcdlFw1nE5chE,16116
|
|
7
|
+
maps4fs/generator/monitor.py,sha256=Yrc7rClpmJK53SRzrOYZNBlwJmb5l6TkW-laFbyBEno,3524
|
|
8
|
+
maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
|
|
9
|
+
maps4fs/generator/settings.py,sha256=jrrIILNRtIpj7hpLrQqLTIagTY8tdJlLZDEN1M4n3Yc,14116
|
|
10
|
+
maps4fs/generator/statistics.py,sha256=ol0MTiehcCbQFfyYA7cKU-M4_cjiLCktnGbid4GYABU,2641
|
|
11
|
+
maps4fs/generator/utils.py,sha256=qaHmS5I30OhDwd213bbctlplQQlX-qkHugyszXGmh0U,5587
|
|
12
|
+
maps4fs/generator/component/__init__.py,sha256=s01yVVVi8R2xxNvflu2D6wTd9I_g73AMM2x7vAC7GX4,490
|
|
13
|
+
maps4fs/generator/component/background.py,sha256=3nzrIENqHVJiZzICqtMdgD-QbGS9125IeTCvO60y7jI,50155
|
|
14
|
+
maps4fs/generator/component/building.py,sha256=VoLjj6mDT-4kVfwxXP-lD0r4vJVYszyWZtciVFwdkIk,27402
|
|
15
|
+
maps4fs/generator/component/config.py,sha256=tI2RQaGIqBgJIi9KjYfMZZ8AWg_YVUm6KKsBHGV241g,31285
|
|
16
|
+
maps4fs/generator/component/dem.py,sha256=vMVJtU2jAS-2lfB9JsqodZsrUvY1h5xr3Dh5qk6txwk,11895
|
|
17
|
+
maps4fs/generator/component/grle.py,sha256=FAcGmG7yq0icOElRoO4QMsVisZMsNrLhfNSWvGKnOHg,29899
|
|
18
|
+
maps4fs/generator/component/i3d.py,sha256=idOixc2UF6RSvQud9GCWmMuTcY1qKVuzijUHakSzWks,28952
|
|
19
|
+
maps4fs/generator/component/layer.py,sha256=-pnKPlZFmsU-OmvG7EX-Nb55eETY0NTiYnnkCIRJuvY,7731
|
|
20
|
+
maps4fs/generator/component/road.py,sha256=YHX3-GcRXEyy9UG5KpUwC75n0FEIysn9PJnC7-tiwkw,27850
|
|
21
|
+
maps4fs/generator/component/satellite.py,sha256=1bPqd8JqAPqU0tEI9m-iuljMW9hXqlaCIxvq7kdpMY0,5219
|
|
22
|
+
maps4fs/generator/component/texture.py,sha256=46eG1EUWqDEppdxkimgu_gCBcNf2KqvZXfkak3GO8EI,38584
|
|
23
|
+
maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
|
24
|
+
maps4fs/generator/component/base/component.py,sha256=-7H3donrH19f0_rivNyI3fgLsiZkntXfGywEx4tOnM4,23924
|
|
25
|
+
maps4fs/generator/component/base/component_image.py,sha256=GXFkEFARNRkWkDiGSjvU4WX6f_8s6R1t2ZYqZflv1jk,9626
|
|
26
|
+
maps4fs/generator/component/base/component_mesh.py,sha256=2wGe_-wAZVRljMKzzVJ8jdzIETWg7LjxGj8A3inH5eI,25550
|
|
27
|
+
maps4fs/generator/component/base/component_xml.py,sha256=MT-VhU2dEckLFxAgmxg6V3gnv11di_94Qq6atfpOLdc,5342
|
|
28
|
+
maps4fs-2.9.37.dist-info/licenses/LICENSE.md,sha256=LzOB2xtN1AlHJi4hqoEsBlYLfmfRyXCPC2417miYoBc,19579
|
|
29
|
+
maps4fs-2.9.37.dist-info/METADATA,sha256=rJbXUF1gfOOgila4vS8faBTPMkZSKX0bxCxotgQl940,10204
|
|
30
|
+
maps4fs-2.9.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
31
|
+
maps4fs-2.9.37.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
|
32
|
+
maps4fs-2.9.37.dist-info/RECORD,,
|