maps4fs 0.9.2__py3-none-any.whl → 0.9.3__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/background.py +46 -10
- maps4fs/generator/map.py +1 -1
- maps4fs/generator/path_steps.py +13 -2
- maps4fs/logger.py +27 -3
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.3.dist-info}/METADATA +7 -1
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.3.dist-info}/RECORD +9 -9
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.3.dist-info}/LICENSE.md +0 -0
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.3.dist-info}/WHEEL +0 -0
- {maps4fs-0.9.2.dist-info → maps4fs-0.9.3.dist-info}/top_level.txt +0 -0
maps4fs/generator/background.py
CHANGED
@@ -10,10 +10,14 @@ import numpy as np
|
|
10
10
|
import trimesh # type: ignore
|
11
11
|
|
12
12
|
from maps4fs.generator.component import Component
|
13
|
-
from maps4fs.generator.path_steps import DEFAULT_DISTANCE, get_steps
|
13
|
+
from maps4fs.generator.path_steps import DEFAULT_DISTANCE, PATH_FULL_NAME, get_steps
|
14
14
|
from maps4fs.generator.tile import Tile
|
15
|
+
from maps4fs.logger import timeit
|
15
16
|
|
16
17
|
RESIZE_FACTOR = 1 / 4
|
18
|
+
SIMPLIFY_FACTOR = 10
|
19
|
+
FULL_RESIZE_FACTOR = 1 / 4
|
20
|
+
FULL_SIMPLIFY_FACTOR = 20
|
17
21
|
|
18
22
|
|
19
23
|
class Background(Component):
|
@@ -37,7 +41,12 @@ class Background(Component):
|
|
37
41
|
# Getting a list of 8 tiles around the map starting from the N(North) tile.
|
38
42
|
for path_step in get_steps(self.map_height, self.map_width):
|
39
43
|
# Getting the destination coordinates for the current tile.
|
40
|
-
|
44
|
+
if path_step.angle is None:
|
45
|
+
# For the case when generating the overview map, which has the same
|
46
|
+
# center as the main map.
|
47
|
+
tile_coordinates = self.coordinates
|
48
|
+
else:
|
49
|
+
tile_coordinates = path_step.get_destination(origin)
|
41
50
|
|
42
51
|
# Create a Tile component, which is needed to save the DEM image.
|
43
52
|
tile = Tile(
|
@@ -129,29 +138,49 @@ class Background(Component):
|
|
129
138
|
self.logger.debug("Generating obj file for tile %s in path: %s", tile.code, save_path)
|
130
139
|
|
131
140
|
dem_data = cv2.imread(tile.dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
|
132
|
-
self.plane_from_np(dem_data, save_path)
|
141
|
+
self.plane_from_np(tile.code, dem_data, save_path)
|
133
142
|
|
134
143
|
# pylint: disable=too-many-locals
|
135
|
-
|
144
|
+
@timeit
|
145
|
+
def plane_from_np(self, tile_code: str, dem_data: np.ndarray, save_path: str) -> None:
|
136
146
|
"""Generates a 3D obj file based on DEM data.
|
137
147
|
|
138
148
|
Arguments:
|
149
|
+
tile_code (str) -- The code of the tile.
|
139
150
|
dem_data (np.ndarray) -- The DEM data as a numpy array.
|
140
151
|
save_path (str) -- The path where the obj file will be saved.
|
141
152
|
"""
|
153
|
+
if tile_code == PATH_FULL_NAME:
|
154
|
+
resize_factor = FULL_RESIZE_FACTOR
|
155
|
+
simplify_factor = FULL_SIMPLIFY_FACTOR
|
156
|
+
self.logger.info("Generating a full map obj file")
|
157
|
+
else:
|
158
|
+
resize_factor = RESIZE_FACTOR
|
159
|
+
simplify_factor = SIMPLIFY_FACTOR
|
142
160
|
dem_data = cv2.resize( # pylint: disable=no-member
|
143
|
-
dem_data, (0, 0), fx=
|
161
|
+
dem_data, (0, 0), fx=resize_factor, fy=resize_factor
|
144
162
|
)
|
145
163
|
self.logger.debug(
|
146
|
-
"DEM data resized to shape: %s with factor: %s", dem_data.shape,
|
164
|
+
"DEM data resized to shape: %s with factor: %s", dem_data.shape, resize_factor
|
147
165
|
)
|
148
166
|
|
167
|
+
# Invert the height values.
|
168
|
+
dem_data = dem_data.max() - dem_data
|
169
|
+
|
149
170
|
rows, cols = dem_data.shape
|
150
171
|
x = np.linspace(0, cols - 1, cols)
|
151
172
|
y = np.linspace(0, rows - 1, rows)
|
152
173
|
x, y = np.meshgrid(x, y)
|
153
174
|
z = dem_data
|
154
175
|
|
176
|
+
self.logger.info(
|
177
|
+
"Starting to generate a mesh for tile %s with shape: %s x %s. "
|
178
|
+
"This may take a while...",
|
179
|
+
tile_code,
|
180
|
+
cols,
|
181
|
+
rows,
|
182
|
+
)
|
183
|
+
|
155
184
|
vertices = np.column_stack([x.ravel(), y.ravel(), z.ravel()])
|
156
185
|
faces = []
|
157
186
|
|
@@ -162,15 +191,22 @@ class Background(Component):
|
|
162
191
|
bottom_left = top_left + cols
|
163
192
|
bottom_right = bottom_left + 1
|
164
193
|
|
165
|
-
|
166
|
-
faces.append([top_left, bottom_right,
|
167
|
-
faces.append([top_left, top_right, bottom_right])
|
194
|
+
faces.append([top_left, bottom_left, bottom_right])
|
195
|
+
faces.append([top_left, bottom_right, top_right])
|
168
196
|
|
169
197
|
faces = np.array(faces) # type: ignore
|
170
198
|
mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
|
171
199
|
|
200
|
+
# Apply rotation: 180 degrees around Y-axis and Z-axis
|
201
|
+
rotation_matrix_y = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
|
202
|
+
rotation_matrix_z = trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
|
203
|
+
mesh.apply_transform(rotation_matrix_y)
|
204
|
+
mesh.apply_transform(rotation_matrix_z)
|
205
|
+
|
206
|
+
self.logger.info("Mesh generated with %s faces, will be simplified", len(mesh.faces))
|
207
|
+
|
172
208
|
# Simplify the mesh to reduce the number of faces.
|
173
|
-
mesh = mesh.simplify_quadric_decimation(face_count=len(faces) //
|
209
|
+
mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // simplify_factor)
|
174
210
|
self.logger.debug("Mesh simplified to %s faces", len(mesh.faces))
|
175
211
|
|
176
212
|
mesh.export(save_path)
|
maps4fs/generator/map.py
CHANGED
@@ -42,7 +42,7 @@ class Map:
|
|
42
42
|
self.map_directory = map_directory
|
43
43
|
|
44
44
|
if not logger:
|
45
|
-
logger = Logger(
|
45
|
+
logger = Logger(to_stdout=True, to_file=False)
|
46
46
|
self.logger = logger
|
47
47
|
self.logger.debug("Game was set to %s", game.code)
|
48
48
|
|
maps4fs/generator/path_steps.py
CHANGED
@@ -5,6 +5,7 @@ from typing import NamedTuple
|
|
5
5
|
from geopy.distance import distance # type: ignore
|
6
6
|
|
7
7
|
DEFAULT_DISTANCE = 2048
|
8
|
+
PATH_FULL_NAME = "FULL"
|
8
9
|
|
9
10
|
|
10
11
|
class PathStep(NamedTuple):
|
@@ -13,13 +14,17 @@ class PathStep(NamedTuple):
|
|
13
14
|
Attributes:
|
14
15
|
code {str} -- Tile code (N, NE, E, SE, S, SW, W, NW).
|
15
16
|
angle {int} -- Angle in degrees (for example 0 for North, 90 for East).
|
17
|
+
If None, the step is a full map with a center at the same coordinates as the
|
18
|
+
map itself.
|
16
19
|
distance {int} -- Distance in meters from previous step.
|
20
|
+
If None, the step is a full map with a center at the same coordinates as the
|
21
|
+
map itself.
|
17
22
|
size {tuple[int, int]} -- Size of the tile in pixels (width, height).
|
18
23
|
"""
|
19
24
|
|
20
25
|
code: str
|
21
|
-
angle: int
|
22
|
-
distance: int
|
26
|
+
angle: int | None
|
27
|
+
distance: int | None
|
23
28
|
size: tuple[int, int]
|
24
29
|
|
25
30
|
def get_destination(self, origin: tuple[float, float]) -> tuple[float, float]:
|
@@ -69,4 +74,10 @@ def get_steps(map_height: int, map_width: int) -> list[PathStep]:
|
|
69
74
|
PathStep(
|
70
75
|
"NW", 0, half_height + half_default_distance, (DEFAULT_DISTANCE, DEFAULT_DISTANCE)
|
71
76
|
),
|
77
|
+
PathStep(
|
78
|
+
PATH_FULL_NAME,
|
79
|
+
None,
|
80
|
+
None,
|
81
|
+
(map_width + DEFAULT_DISTANCE * 2, map_height + DEFAULT_DISTANCE * 2),
|
82
|
+
),
|
72
83
|
]
|
maps4fs/logger.py
CHANGED
@@ -4,8 +4,11 @@ import logging
|
|
4
4
|
import os
|
5
5
|
import sys
|
6
6
|
from datetime import datetime
|
7
|
-
from
|
7
|
+
from logging import getLogger
|
8
|
+
from time import perf_counter
|
9
|
+
from typing import Any, Callable, Literal
|
8
10
|
|
11
|
+
LOGGER_NAME = "maps4fs"
|
9
12
|
log_directory = os.path.join(os.getcwd(), "logs")
|
10
13
|
os.makedirs(log_directory, exist_ok=True)
|
11
14
|
|
@@ -15,12 +18,11 @@ class Logger(logging.Logger):
|
|
15
18
|
|
16
19
|
def __init__(
|
17
20
|
self,
|
18
|
-
name: str,
|
19
21
|
level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "ERROR",
|
20
22
|
to_stdout: bool = True,
|
21
23
|
to_file: bool = True,
|
22
24
|
):
|
23
|
-
super().__init__(
|
25
|
+
super().__init__(LOGGER_NAME)
|
24
26
|
self.setLevel(level)
|
25
27
|
self.stdout_handler = logging.StreamHandler(sys.stdout)
|
26
28
|
self.file_handler = logging.FileHandler(
|
@@ -44,3 +46,25 @@ class Logger(logging.Logger):
|
|
44
46
|
today = datetime.now().strftime("%Y-%m-%d")
|
45
47
|
log_file = os.path.join(log_directory, f"{today}.txt")
|
46
48
|
return log_file
|
49
|
+
|
50
|
+
|
51
|
+
def timeit(func: Callable[..., Any]) -> Callable[..., Any]:
|
52
|
+
"""Decorator to log the time taken by a function to execute.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
func (function): The function to be timed.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
function: The timed function.
|
59
|
+
"""
|
60
|
+
|
61
|
+
def timed(*args, **kwargs):
|
62
|
+
logger = getLogger("maps4fs")
|
63
|
+
start = perf_counter()
|
64
|
+
result = func(*args, **kwargs)
|
65
|
+
end = perf_counter()
|
66
|
+
if logger is not None:
|
67
|
+
logger.info("Function %s took %s seconds to execute", func.__name__, end - start)
|
68
|
+
return result
|
69
|
+
|
70
|
+
return timed
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.3
|
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
|
@@ -360,6 +360,12 @@ Let's have a closer look at the fields:
|
|
360
360
|
## Background terrain
|
361
361
|
The tool now supports the generation of the background terrain. If you don't know what it is, here's a brief explanation. The background terrain is the world around the map. It's important to create it, because if you don't, the map will look like it's floating in the void. The background terrain is a simple plane which can (and should) be texture to look fine.<br>
|
362
362
|
So, the tool generates the background terrain in the form of the 8 tiles, which surround the map. The tiles are named as the cardinal points, e.g. "N", "NE", "E" and so on. All those tiles will be saved in the `objects/tiles` directory with corresponding names: `N.obj`, `NE.obj`, `E.obj` and so on.<br>
|
363
|
+
If you don't want to work with separate tiles, the tool also generates the `FULL.obj` file, which includes everything around the map and the map itself. It may be a convinient approach to work with one file, one texture and then just cut the map from it.<br>
|
364
|
+
|
365
|
+

|
366
|
+
|
367
|
+
➡️ *No matter which approach you choose, you still need to adjust the background terrain to connect it to the map without any gaps. But with a sinlge file it's much easier to do.*
|
368
|
+
|
363
369
|
If you're willing to create a background terrain, you will need: Blender, the Blender Exporter Plugins and the QGIS. You'll find the download links in the [Resources](#resources) section.<br>
|
364
370
|
|
365
371
|
If you're afraid of this task, please don't be. It's really simple and I've prepaired detailed step-by-step instructions for you, you'll find them in the separate README files. Here are the steps you need to follow:
|
@@ -1,19 +1,19 @@
|
|
1
1
|
maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
|
2
|
-
maps4fs/logger.py,sha256=
|
2
|
+
maps4fs/logger.py,sha256=8oZzAKJllilYrVp452LX0zx-dNFwpS6UngbTrI6KfwA,2148
|
3
3
|
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
4
|
-
maps4fs/generator/background.py,sha256=
|
4
|
+
maps4fs/generator/background.py,sha256=M43VNwiAnL4-1iUGAFwwwl5lV5EVy4luvZZmsuGO_Co,12642
|
5
5
|
maps4fs/generator/component.py,sha256=tLjjbrFn3v4CrUrUOgH9s8NuJhmQcBs74ShefkNg1VE,10584
|
6
6
|
maps4fs/generator/config.py,sha256=JL7leQv8C06JQOXIbgQ-jve2re7cNsx8vKa8dfbnxPM,3896
|
7
7
|
maps4fs/generator/dem.py,sha256=p7THncPUdYWtNR2HrTXe0bCuJ8psUV8rRpYPJkzL2gA,15220
|
8
8
|
maps4fs/generator/game.py,sha256=94HjPNOyy6XSSOnBp-uxrkBglKyC-X3ULIrrucfdlKw,6825
|
9
9
|
maps4fs/generator/i3d.py,sha256=UuQiQRusPQ2f6PvGKxJ_UZeXsx3uG15efmy8Ysnm3F8,3597
|
10
|
-
maps4fs/generator/map.py,sha256=
|
11
|
-
maps4fs/generator/path_steps.py,sha256=
|
10
|
+
maps4fs/generator/map.py,sha256=v8OOLmhAkgqq64tQgEDbV6DmbgOVm3NXJBDDy0nJf60,4226
|
11
|
+
maps4fs/generator/path_steps.py,sha256=yeN6hmOD8O2LMozUf4HcQMIy8WJ5sHF5pGQT_s2FfOw,3147
|
12
12
|
maps4fs/generator/qgis.py,sha256=k19miPEFyOdu_ogBFHmFVfYQ-IgF38ufJZ5DM5NSHBQ,3400
|
13
13
|
maps4fs/generator/texture.py,sha256=CwaXZfAG4e3D3YR7yVR2MC677EHpbUWTboCS3G6jkhk,17723
|
14
14
|
maps4fs/generator/tile.py,sha256=3vmfjQiPiiUZKPuIo5tg2rOKkgcP1NRVkMGK-Vo10-A,2138
|
15
|
-
maps4fs-0.9.
|
16
|
-
maps4fs-0.9.
|
17
|
-
maps4fs-0.9.
|
18
|
-
maps4fs-0.9.
|
19
|
-
maps4fs-0.9.
|
15
|
+
maps4fs-0.9.3.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
|
16
|
+
maps4fs-0.9.3.dist-info/METADATA,sha256=0iPJEUJXeSqgNXqhcD2BM5lqkvX7YBK2O-UFWY5HxvM,23996
|
17
|
+
maps4fs-0.9.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
18
|
+
maps4fs-0.9.3.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
19
|
+
maps4fs-0.9.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|