maps4fs 0.9.2__py3-none-any.whl → 0.9.4__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(
@@ -86,11 +95,13 @@ class Background(Component):
86
95
  for tile in self.tiles:
87
96
  north, south, east, west = tile.bbox
88
97
  epsg3857_string = tile.get_epsg3857_string()
98
+ epsg3857_string_with_margin = tile.get_epsg3857_string(add_margin=True)
89
99
 
90
100
  tile_entry = {
91
101
  "center_latitude": tile.coordinates[0],
92
102
  "center_longitude": tile.coordinates[1],
93
103
  "epsg3857_string": epsg3857_string,
104
+ "epsg3857_string_with_margin": epsg3857_string_with_margin,
94
105
  "height": tile.map_height,
95
106
  "width": tile.map_width,
96
107
  "north": north,
@@ -106,10 +117,16 @@ class Background(Component):
106
117
  def qgis_sequence(self) -> None:
107
118
  """Generates QGIS scripts for creating bounding box layers and rasterizing them."""
108
119
  qgis_layers = [
109
- (f"Background_bbox_{tile.code}", *tile.get_espg3857_bbox()) for tile in self.tiles
120
+ (f"Background_{tile.code}", *tile.get_espg3857_bbox()) for tile in self.tiles
110
121
  ]
122
+ qgis_layers_with_margin = [
123
+ (f"Background_{tile.code}_margin", *tile.get_espg3857_bbox(add_margin=True))
124
+ for tile in self.tiles
125
+ ]
126
+
127
+ layers = qgis_layers + qgis_layers_with_margin
111
128
 
112
- self.create_qgis_scripts(qgis_layers) # type: ignore
129
+ self.create_qgis_scripts(layers)
113
130
 
114
131
  def generate_obj_files(self) -> None:
115
132
  """Iterates over all tiles and generates 3D obj files based on DEM data.
@@ -129,29 +146,49 @@ class Background(Component):
129
146
  self.logger.debug("Generating obj file for tile %s in path: %s", tile.code, save_path)
130
147
 
131
148
  dem_data = cv2.imread(tile.dem_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
132
- self.plane_from_np(dem_data, save_path)
149
+ self.plane_from_np(tile.code, dem_data, save_path)
133
150
 
134
151
  # pylint: disable=too-many-locals
135
- def plane_from_np(self, dem_data: np.ndarray, save_path: str) -> None:
152
+ @timeit
153
+ def plane_from_np(self, tile_code: str, dem_data: np.ndarray, save_path: str) -> None:
136
154
  """Generates a 3D obj file based on DEM data.
137
155
 
138
156
  Arguments:
157
+ tile_code (str) -- The code of the tile.
139
158
  dem_data (np.ndarray) -- The DEM data as a numpy array.
140
159
  save_path (str) -- The path where the obj file will be saved.
141
160
  """
161
+ if tile_code == PATH_FULL_NAME:
162
+ resize_factor = FULL_RESIZE_FACTOR
163
+ simplify_factor = FULL_SIMPLIFY_FACTOR
164
+ self.logger.info("Generating a full map obj file")
165
+ else:
166
+ resize_factor = RESIZE_FACTOR
167
+ simplify_factor = SIMPLIFY_FACTOR
142
168
  dem_data = cv2.resize( # pylint: disable=no-member
143
- dem_data, (0, 0), fx=RESIZE_FACTOR, fy=RESIZE_FACTOR
169
+ dem_data, (0, 0), fx=resize_factor, fy=resize_factor
144
170
  )
145
171
  self.logger.debug(
146
- "DEM data resized to shape: %s with factor: %s", dem_data.shape, RESIZE_FACTOR
172
+ "DEM data resized to shape: %s with factor: %s", dem_data.shape, resize_factor
147
173
  )
148
174
 
175
+ # Invert the height values.
176
+ dem_data = dem_data.max() - dem_data
177
+
149
178
  rows, cols = dem_data.shape
150
179
  x = np.linspace(0, cols - 1, cols)
151
180
  y = np.linspace(0, rows - 1, rows)
152
181
  x, y = np.meshgrid(x, y)
153
182
  z = dem_data
154
183
 
184
+ self.logger.info(
185
+ "Starting to generate a mesh for tile %s with shape: %s x %s. "
186
+ "This may take a while...",
187
+ tile_code,
188
+ cols,
189
+ rows,
190
+ )
191
+
155
192
  vertices = np.column_stack([x.ravel(), y.ravel(), z.ravel()])
156
193
  faces = []
157
194
 
@@ -162,15 +199,22 @@ class Background(Component):
162
199
  bottom_left = top_left + cols
163
200
  bottom_right = bottom_left + 1
164
201
 
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])
202
+ faces.append([top_left, bottom_left, bottom_right])
203
+ faces.append([top_left, bottom_right, top_right])
168
204
 
169
205
  faces = np.array(faces) # type: ignore
170
206
  mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
171
207
 
208
+ # Apply rotation: 180 degrees around Y-axis and Z-axis
209
+ rotation_matrix_y = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
210
+ rotation_matrix_z = trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
211
+ mesh.apply_transform(rotation_matrix_y)
212
+ mesh.apply_transform(rotation_matrix_z)
213
+
214
+ self.logger.info("Mesh generated with %s faces, will be simplified", len(mesh.faces))
215
+
172
216
  # Simplify the mesh to reduce the number of faces.
173
- mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // 10)
217
+ mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // simplify_factor)
174
218
  self.logger.debug("Mesh simplified to %s faces", len(mesh.faces))
175
219
 
176
220
  mesh.export(save_path)
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any
10
10
  import osmnx as ox # type: ignore
11
11
  from pyproj import Transformer
12
12
 
13
- from maps4fs.generator.qgis import get_bbox_template, get_rasterize_template
13
+ from maps4fs.generator.qgis import save_scripts
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from maps4fs.generator.game import Game
@@ -214,16 +214,17 @@ class Component:
214
214
  return west, south, east, north
215
215
 
216
216
  def get_espg3857_bbox(
217
- self, bbox: tuple[int, int, int, int] | None = None
218
- ) -> tuple[int, int, int, int]:
217
+ self, bbox: tuple[float, float, float, float] | None = None, add_margin: bool = False
218
+ ) -> tuple[float, float, float, float]:
219
219
  """Converts the bounding box to EPSG:3857.
220
220
  If the bounding box is not provided, the instance variable is used.
221
221
 
222
222
  Args:
223
- bbox (tuple[int, int, int, int], optional): The bounding box to convert.
223
+ bbox (tuple[float, float, float, float], optional): The bounding box to convert.
224
+ add_margin (bool, optional): Whether to add a margin to the bounding box.
224
225
 
225
226
  Returns:
226
- tuple[int, int, int, int]: The bounding box in EPSG:3857.
227
+ tuple[float, float, float, float]: The bounding box in EPSG:3857.
227
228
  """
228
229
  bbox = bbox or self.bbox
229
230
  north, south, east, west = bbox
@@ -231,19 +232,29 @@ class Component:
231
232
  epsg3857_north, epsg3857_west = transformer.transform(north, west)
232
233
  epsg3857_south, epsg3857_east = transformer.transform(south, east)
233
234
 
235
+ if add_margin:
236
+ MARGIN = 500 # pylint: disable=C0103
237
+ epsg3857_north = int(epsg3857_north - MARGIN)
238
+ epsg3857_south = int(epsg3857_south + MARGIN)
239
+ epsg3857_east = int(epsg3857_east - MARGIN)
240
+ epsg3857_west = int(epsg3857_west + MARGIN)
241
+
234
242
  return epsg3857_north, epsg3857_south, epsg3857_east, epsg3857_west
235
243
 
236
- def get_epsg3857_string(self, bbox: tuple[int, int, int, int] | None = None) -> str:
244
+ def get_epsg3857_string(
245
+ self, bbox: tuple[float, float, float, float] | None = None, add_margin: bool = False
246
+ ) -> str:
237
247
  """Converts the bounding box to EPSG:3857 string.
238
248
  If the bounding box is not provided, the instance variable is used.
239
249
 
240
250
  Args:
241
- bbox (tuple[int, int, int, int], optional): The bounding box to convert.
251
+ bbox (tuple[float, float, float, float], optional): The bounding box to convert.
252
+ add_margin (bool, optional): Whether to add a margin to the bounding box.
242
253
 
243
254
  Returns:
244
255
  str: The bounding box in EPSG:3857 string.
245
256
  """
246
- north, south, east, west = self.get_espg3857_bbox(bbox)
257
+ north, south, east, west = self.get_espg3857_bbox(bbox, add_margin=add_margin)
247
258
  return f"{north},{south},{east},{west} [EPSG:3857]"
248
259
 
249
260
  def create_qgis_scripts(
@@ -259,17 +270,4 @@ class Component:
259
270
  create scripts for.
260
271
  """
261
272
  class_name = self.__class__.__name__.lower()
262
-
263
- script_files = [
264
- (f"{class_name}_bbox.py", get_bbox_template),
265
- (f"{class_name}_rasterize.py", get_rasterize_template),
266
- ]
267
-
268
- for script_file, process_function in script_files:
269
- script_path = os.path.join(self.scripts_directory, script_file)
270
- script_content = process_function(qgis_layers) # type: ignore
271
-
272
- with open(script_path, "w", encoding="utf-8") as file:
273
- file.write(script_content)
274
-
275
- self.logger.info("QGIS script saved: %s", script_path)
273
+ save_scripts(qgis_layers, class_name, self.scripts_directory)
@@ -70,11 +70,13 @@ class Config(Component):
70
70
  bbox = self.get_bbox(height_distance=self.map_height, width_distance=self.map_width)
71
71
  south, west, north, east = bbox
72
72
  epsg3857_string = self.get_epsg3857_string(bbox=bbox)
73
+ epsg3857_string_with_margin = self.get_epsg3857_string(bbox=bbox, add_margin=True)
73
74
 
74
75
  self.qgis_sequence()
75
76
 
76
77
  overview_data = {
77
78
  "epsg3857_string": epsg3857_string,
79
+ "epsg3857_string_with_margin": epsg3857_string_with_margin,
78
80
  "south": south,
79
81
  "west": west,
80
82
  "north": north,
@@ -93,7 +95,11 @@ class Config(Component):
93
95
  """Generates QGIS scripts for creating bounding box layers and rasterizing them."""
94
96
  bbox = self.get_bbox(height_distance=self.map_height, width_distance=self.map_width)
95
97
  espg3857_bbox = self.get_espg3857_bbox(bbox=bbox)
98
+ espg3857_bbox_with_margin = self.get_espg3857_bbox(bbox=bbox, add_margin=True)
96
99
 
97
100
  qgis_layers = [("Overview_bbox", *espg3857_bbox)]
101
+ qgis_layers_with_margin = [("Overview_bbox_with_margin", *espg3857_bbox_with_margin)]
98
102
 
99
- self.create_qgis_scripts(qgis_layers) # type: ignore
103
+ layers = qgis_layers + qgis_layers_with_margin
104
+
105
+ self.create_qgis_scripts(layers)
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/generator/qgis.py CHANGED
@@ -1,11 +1,13 @@
1
1
  """This module contains templates for generating QGIS scripts."""
2
2
 
3
+ import os
4
+
3
5
  BBOX_TEMPLATE = """
4
6
  layers = [
5
7
  {layers}
6
8
  ]
7
9
  for layer in layers:
8
- name = layer[0]
10
+ name = "Bounding_Box_" + layer[0]
9
11
  north, south, east, west = layer[1:]
10
12
 
11
13
  # Create a rectangle geometry from the bounding box.
@@ -34,6 +36,42 @@ for layer in layers:
34
36
  layer.triggerRepaint()
35
37
  """
36
38
 
39
+ POINT_TEMPLATE = """
40
+ layers = [
41
+ {layers}
42
+ ]
43
+ for layer in layers:
44
+ name = "Points_" + layer[0]
45
+ north, south, east, west = layer[1:]
46
+
47
+ top_left = QgsPointXY(north, west)
48
+ top_right = QgsPointXY(north, east)
49
+ bottom_right = QgsPointXY(south, east)
50
+ bottom_left = QgsPointXY(south, west)
51
+
52
+ points = [top_left, top_right, bottom_right, bottom_left, top_left]
53
+
54
+ # Create a new layer
55
+ layer = QgsVectorLayer('Point?crs=EPSG:4326', name, 'memory')
56
+ provider = layer.dataProvider()
57
+
58
+ # Add fields
59
+ provider.addAttributes([QgsField("id", QVariant.Int)])
60
+ layer.updateFields()
61
+
62
+ # Create and add features for each point
63
+ for i, point in enumerate(points):
64
+ feature = QgsFeature()
65
+ feature.setGeometry(QgsGeometry.fromPointXY(point))
66
+ feature.setAttributes([i + 1])
67
+ provider.addFeature(feature)
68
+
69
+ layer.updateExtents()
70
+
71
+ # Add the layer to the project
72
+ QgsProject.instance().addMapLayer(layer)
73
+ """
74
+
37
75
  RASTERIZE_TEMPLATE = """
38
76
  import processing
39
77
 
@@ -74,17 +112,18 @@ for layer in layers:
74
112
  """
75
113
 
76
114
 
77
- def get_bbox_template(layers: list[tuple[str, float, float, float, float]]) -> str:
78
- """Returns a template for creating bounding box layers in QGIS.
115
+ def _get_template(layers: list[tuple[str, float, float, float, float]], template: str) -> str:
116
+ """Returns a template for creating layers in QGIS.
79
117
 
80
118
  Args:
81
119
  layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
82
120
  layer name and the bounding box coordinates.
121
+ template (str): The template for creating layers in QGIS.
83
122
 
84
123
  Returns:
85
- str: The template for creating bounding box layers in QGIS.
124
+ str: The template for creating layers in QGIS.
86
125
  """
87
- return BBOX_TEMPLATE.format(
126
+ return template.format(
88
127
  layers=",\n ".join(
89
128
  [
90
129
  f'("{name}", {north}, {south}, {east}, {west})'
@@ -94,6 +133,32 @@ def get_bbox_template(layers: list[tuple[str, float, float, float, float]]) -> s
94
133
  )
95
134
 
96
135
 
136
+ def get_bbox_template(layers: list[tuple[str, float, float, float, float]]) -> str:
137
+ """Returns a template for creating bounding box layers in QGIS.
138
+
139
+ Args:
140
+ layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
141
+ layer name and the bounding box coordinates.
142
+
143
+ Returns:
144
+ str: The template for creating bounding box layers in QGIS.
145
+ """
146
+ return _get_template(layers, BBOX_TEMPLATE)
147
+
148
+
149
+ def get_point_template(layers: list[tuple[str, float, float, float, float]]) -> str:
150
+ """Returns a template for creating point layers in QGIS.
151
+
152
+ Args:
153
+ layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
154
+ layer name and the bounding box coordinates.
155
+
156
+ Returns:
157
+ str: The template for creating point layers in QGIS.
158
+ """
159
+ return _get_template(layers, POINT_TEMPLATE)
160
+
161
+
97
162
  def get_rasterize_template(layers: list[tuple[str, float, float, float, float]]) -> str:
98
163
  """Returns a template for rasterizing bounding box layers in QGIS.
99
164
 
@@ -104,11 +169,28 @@ def get_rasterize_template(layers: list[tuple[str, float, float, float, float]])
104
169
  Returns:
105
170
  str: The template for rasterizing bounding box layers in QGIS.
106
171
  """
107
- return RASTERIZE_TEMPLATE.format(
108
- layers=",\n ".join(
109
- [
110
- f'("{name}", {north}, {south}, {east}, {west})'
111
- for name, north, south, east, west in layers
112
- ]
113
- )
114
- )
172
+ return _get_template(layers, RASTERIZE_TEMPLATE)
173
+
174
+
175
+ def save_scripts(
176
+ layers: list[tuple[str, float, float, float, float]], file_prefix: str, save_directory: str
177
+ ) -> None:
178
+ """Saves QGIS scripts for creating bounding box, point, and raster layers.
179
+
180
+ Args:
181
+ layers (list[tuple[str, float, float, float, float]]): A list of tuples containing the
182
+ layer name and the bounding box coordinates.
183
+ save_dir (str): The directory to save the scripts.
184
+ """
185
+ script_files = [
186
+ (f"{file_prefix}_bbox.py", get_bbox_template),
187
+ (f"{file_prefix}_rasterize.py", get_rasterize_template),
188
+ (f"{file_prefix}_point.py", get_point_template),
189
+ ]
190
+
191
+ for script_file, process_function in script_files:
192
+ script_path = os.path.join(save_directory, script_file)
193
+ script_content = process_function(layers) # type: ignore
194
+
195
+ with open(script_path, "w", encoding="utf-8") as file:
196
+ file.write(script_content)
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.4
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
@@ -23,7 +23,9 @@ Requires-Dist: imageio
23
23
  Requires-Dist: tifffile
24
24
 
25
25
  <div align="center" markdown>
26
- <img src="https://github.com/iwatkot/maps4fs/assets/118521851/ffd7f0a3-e317-4c3f-911f-2c2fb736fbfa">
26
+ <a href="https://discord.gg/Sj5QKKyE42">
27
+ <img src="https://github.com/user-attachments/assets/37043333-d6ef-4ca3-9f3c-81323d9d0b71">
28
+ </a>
27
29
 
28
30
  <p align="center">
29
31
  <a href="#Quick-Start">Quick Start</a> •
@@ -41,6 +43,7 @@ Requires-Dist: tifffile
41
43
  </p>
42
44
 
43
45
 
46
+ [![Join Discord](https://img.shields.io/badge/join-discord-blue)](https://discord.gg/Sj5QKKyE42)
44
47
  [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/iwatkot/maps4fs)](https://github.com/iwatkot/maps4fs/releases)
45
48
  [![PyPI - Version](https://img.shields.io/pypi/v/maps4fs)](https://pypi.org/project/maps4fs)
46
49
  [![Docker Pulls](https://img.shields.io/docker/pulls/iwatkot/maps4fs)](https://hub.docker.com/repository/docker/iwatkot/maps4fs/general)
@@ -360,6 +363,12 @@ Let's have a closer look at the fields:
360
363
  ## Background terrain
361
364
  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
365
  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>
366
+ 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>
367
+
368
+ ![Complete background terrain in Blender](https://github.com/user-attachments/assets/7266b8f1-bfa2-4c14-a740-1c84b1030a66)
369
+
370
+ ➡️ *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.*
371
+
363
372
  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
373
 
365
374
  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:
@@ -0,0 +1,19 @@
1
+ maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
+ maps4fs/logger.py,sha256=8oZzAKJllilYrVp452LX0zx-dNFwpS6UngbTrI6KfwA,2148
3
+ maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
+ maps4fs/generator/background.py,sha256=6mN2xRg5XEqsLaXPUqdNGgDR1Ats60fBK93WlDeB8EI,13002
5
+ maps4fs/generator/component.py,sha256=ZEDjChPnvqAsgnBu2f2YBOlwGOlfax4VaAYBcJerLIQ,10684
6
+ maps4fs/generator/config.py,sha256=ZO5BWDU-S3p0-ndKDSFa8Oin3YcYy0iH8B4lqEA_07Q,4275
7
+ maps4fs/generator/dem.py,sha256=p7THncPUdYWtNR2HrTXe0bCuJ8psUV8rRpYPJkzL2gA,15220
8
+ maps4fs/generator/game.py,sha256=94HjPNOyy6XSSOnBp-uxrkBglKyC-X3ULIrrucfdlKw,6825
9
+ maps4fs/generator/i3d.py,sha256=UuQiQRusPQ2f6PvGKxJ_UZeXsx3uG15efmy8Ysnm3F8,3597
10
+ maps4fs/generator/map.py,sha256=v8OOLmhAkgqq64tQgEDbV6DmbgOVm3NXJBDDy0nJf60,4226
11
+ maps4fs/generator/path_steps.py,sha256=yeN6hmOD8O2LMozUf4HcQMIy8WJ5sHF5pGQT_s2FfOw,3147
12
+ maps4fs/generator/qgis.py,sha256=vhV-EwZKK-p1qEV-2H_yckuktx8ezlCMnmJJfUt1HGI,6091
13
+ maps4fs/generator/texture.py,sha256=CwaXZfAG4e3D3YR7yVR2MC677EHpbUWTboCS3G6jkhk,17723
14
+ maps4fs/generator/tile.py,sha256=3vmfjQiPiiUZKPuIo5tg2rOKkgcP1NRVkMGK-Vo10-A,2138
15
+ maps4fs-0.9.4.dist-info/LICENSE.md,sha256=-JY0v7p3dwXze61EbYiK7YEJ2aKrjaFZ8y2xYEOrmRY,1068
16
+ maps4fs-0.9.4.dist-info/METADATA,sha256=WVUjB4PmrCnuWZNIVurx3_1XPqS2oBEwWdVaU22uX2Y,24130
17
+ maps4fs-0.9.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
18
+ maps4fs-0.9.4.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
19
+ maps4fs-0.9.4.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
2
- maps4fs/logger.py,sha256=CneeHxQywjNUJXqQrUUSeiDxu95FfrfyK_Si1v0gMZ8,1477
3
- maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=2BUgn12t-FgijPTY7hvwTaYQrVzYdnZqhoz76KSBcKs,11216
5
- maps4fs/generator/component.py,sha256=tLjjbrFn3v4CrUrUOgH9s8NuJhmQcBs74ShefkNg1VE,10584
6
- maps4fs/generator/config.py,sha256=JL7leQv8C06JQOXIbgQ-jve2re7cNsx8vKa8dfbnxPM,3896
7
- maps4fs/generator/dem.py,sha256=p7THncPUdYWtNR2HrTXe0bCuJ8psUV8rRpYPJkzL2gA,15220
8
- maps4fs/generator/game.py,sha256=94HjPNOyy6XSSOnBp-uxrkBglKyC-X3ULIrrucfdlKw,6825
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
12
- maps4fs/generator/qgis.py,sha256=k19miPEFyOdu_ogBFHmFVfYQ-IgF38ufJZ5DM5NSHBQ,3400
13
- maps4fs/generator/texture.py,sha256=CwaXZfAG4e3D3YR7yVR2MC677EHpbUWTboCS3G6jkhk,17723
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,,