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.
@@ -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
- tile_coordinates = path_step.get_destination(origin)
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
- def plane_from_np(self, dem_data: np.ndarray, save_path: str) -> None:
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=RESIZE_FACTOR, fy=RESIZE_FACTOR
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, RESIZE_FACTOR
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
- # Invert the order of vertices to flip the normals
166
- faces.append([top_left, bottom_right, bottom_left])
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) // 10)
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(__name__, to_stdout=True, to_file=False)
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
 
@@ -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 typing import Literal
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__(name)
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.2
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
+ ![Complete background terrain in Blender](https://github.com/user-attachments/assets/7266b8f1-bfa2-4c14-a740-1c84b1030a66)
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=CneeHxQywjNUJXqQrUUSeiDxu95FfrfyK_Si1v0gMZ8,1477
2
+ maps4fs/logger.py,sha256=8oZzAKJllilYrVp452LX0zx-dNFwpS6UngbTrI6KfwA,2148
3
3
  maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=2BUgn12t-FgijPTY7hvwTaYQrVzYdnZqhoz76KSBcKs,11216
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=BS8ylTCRlHRmbImg73wnHMpBB2ODckVyVZq5KJhCMfM,4236
11
- maps4fs/generator/path_steps.py,sha256=rKslIiG9mriCVL9_0i8Oet2p0DITrpBWi0pECpvuyUM,2707
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.2.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
16
- maps4fs-0.9.2.dist-info/METADATA,sha256=J9SVncLGsj5Le0QQu-mfSyrsQJc0EkO7tdDs1HFd8vQ,23427
17
- maps4fs-0.9.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
18
- maps4fs-0.9.2.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
19
- maps4fs-0.9.2.dist-info/RECORD,,
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,,