maps4fs 0.9.98__py3-none-any.whl → 1.0.1__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 +15 -113
- maps4fs/generator/component.py +12 -2
- maps4fs/generator/dem.py +1 -4
- maps4fs/generator/game.py +2 -1
- maps4fs/generator/i3d.py +277 -6
- maps4fs/generator/texture.py +35 -6
- maps4fs/generator/tile.py +3 -3
- maps4fs/toolbox/background.py +63 -0
- {maps4fs-0.9.98.dist-info → maps4fs-1.0.1.dist-info}/METADATA +17 -6
- maps4fs-1.0.1.dist-info/RECORD +23 -0
- maps4fs-0.9.98.dist-info/RECORD +0 -22
- {maps4fs-0.9.98.dist-info → maps4fs-1.0.1.dist-info}/LICENSE.md +0 -0
- {maps4fs-0.9.98.dist-info → maps4fs-1.0.1.dist-info}/WHEEL +0 -0
- {maps4fs-0.9.98.dist-info → maps4fs-1.0.1.dist-info}/top_level.txt +0 -0
maps4fs/generator/background.py
CHANGED
@@ -15,7 +15,7 @@ from maps4fs.generator.dem import (
|
|
15
15
|
DEFAULT_MULTIPLIER,
|
16
16
|
DEFAULT_PLATEAU,
|
17
17
|
)
|
18
|
-
from maps4fs.generator.path_steps import
|
18
|
+
from maps4fs.generator.path_steps import PATH_FULL_NAME, get_steps
|
19
19
|
from maps4fs.generator.tile import Tile
|
20
20
|
|
21
21
|
RESIZE_FACTOR = 1 / 4
|
@@ -242,119 +242,21 @@ class Background(Component):
|
|
242
242
|
|
243
243
|
self.stl_preview_path = preview_path # pylint: disable=attribute-defined-outside-init
|
244
244
|
|
245
|
+
# pylint: disable=no-member
|
245
246
|
def previews(self) -> list[str]:
|
246
|
-
"""
|
247
|
-
NOTE: The map itself is not included in the preview, so it will be empty.
|
247
|
+
"""Returns the path to the image of full tile and the path to the STL preview file.
|
248
248
|
|
249
249
|
Returns:
|
250
|
-
list[str] -- A list of paths to the
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
image = np.zeros((image_height, image_width), np.uint16) # pylint: disable=no-member
|
259
|
-
self.logger.debug("Empty image created: %s", image.shape)
|
260
|
-
|
261
|
-
for tile in self.tiles:
|
262
|
-
# pylint: disable=no-member
|
263
|
-
if tile.code == PATH_FULL_NAME:
|
264
|
-
continue
|
265
|
-
tile_image = cv2.imread(tile.dem_path, cv2.IMREAD_UNCHANGED)
|
266
|
-
|
267
|
-
self.logger.debug(
|
268
|
-
"Tile %s image shape: %s, dtype: %s, max: %s, min: %s",
|
269
|
-
tile.code,
|
270
|
-
tile_image.shape,
|
271
|
-
tile_image.dtype,
|
272
|
-
tile_image.max(),
|
273
|
-
tile_image.min(),
|
274
|
-
)
|
275
|
-
|
276
|
-
tile_height, tile_width = tile_image.shape
|
277
|
-
self.logger.debug("Tile %s size: %s x %s", tile.code, tile_width, tile_height)
|
278
|
-
|
279
|
-
# Calculate the position based on the tile code
|
280
|
-
if tile.code == "N":
|
281
|
-
x = DEFAULT_DISTANCE
|
282
|
-
y = 0
|
283
|
-
elif tile.code == "NE":
|
284
|
-
x = self.map_width + DEFAULT_DISTANCE
|
285
|
-
y = 0
|
286
|
-
elif tile.code == "E":
|
287
|
-
x = self.map_width + DEFAULT_DISTANCE
|
288
|
-
y = DEFAULT_DISTANCE
|
289
|
-
elif tile.code == "SE":
|
290
|
-
x = self.map_width + DEFAULT_DISTANCE
|
291
|
-
y = self.map_height + DEFAULT_DISTANCE
|
292
|
-
elif tile.code == "S":
|
293
|
-
x = DEFAULT_DISTANCE
|
294
|
-
y = self.map_height + DEFAULT_DISTANCE
|
295
|
-
elif tile.code == "SW":
|
296
|
-
x = 0
|
297
|
-
y = self.map_height + DEFAULT_DISTANCE
|
298
|
-
elif tile.code == "W":
|
299
|
-
x = 0
|
300
|
-
y = DEFAULT_DISTANCE
|
301
|
-
elif tile.code == "NW":
|
302
|
-
x = 0
|
303
|
-
y = 0
|
304
|
-
|
305
|
-
# pylint: disable=possibly-used-before-assignment
|
306
|
-
x2 = x + tile_width
|
307
|
-
y2 = y + tile_height
|
308
|
-
|
309
|
-
self.logger.debug(
|
310
|
-
"Tile %s position. X from %s to %s, Y from %s to %s", tile.code, x, x2, y, y2
|
250
|
+
list[str] -- A list of paths to the previews.
|
251
|
+
"""
|
252
|
+
full_tile = next((tile for tile in self.tiles if tile.code == PATH_FULL_NAME), None)
|
253
|
+
if full_tile:
|
254
|
+
preview_path = os.path.join(self.previews_directory, "background_dem.png")
|
255
|
+
full_tile_image = cv2.imread(full_tile.dem_path, cv2.IMREAD_UNCHANGED)
|
256
|
+
full_tile_image = cv2.normalize( # type: ignore
|
257
|
+
full_tile_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U
|
311
258
|
)
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
# Save image to the map directory.
|
317
|
-
preview_path = os.path.join(self.previews_directory, "background_dem.png")
|
318
|
-
|
319
|
-
# pylint: disable=no-member
|
320
|
-
image = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U) # type: ignore
|
321
|
-
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) # type: ignore
|
322
|
-
cv2.imwrite(preview_path, image)
|
323
|
-
|
324
|
-
return [preview_path, self.stl_preview_path]
|
325
|
-
|
326
|
-
|
327
|
-
# Creates tiles around the map.
|
328
|
-
# The one on corners 2048x2048, on sides and in the middle map_size x 2048.
|
329
|
-
# So 2048 is a distance FROM the edge of the map, but the other size depends on the map size.
|
330
|
-
# But for corner tiles it's always 2048.
|
331
|
-
|
332
|
-
# In the beginning we have coordinates of the central point of the map and it's size.
|
333
|
-
# We need to calculate the coordinates of central points all 8 tiles around the map.
|
334
|
-
|
335
|
-
# Latitude is a vertical line, Longitude is a horizontal line.
|
336
|
-
|
337
|
-
# 2048
|
338
|
-
# | |
|
339
|
-
# ____________________|_________|___
|
340
|
-
# | | | |
|
341
|
-
# | NW | N | NE | 2048
|
342
|
-
# |_________|_________|_________|___
|
343
|
-
# | | | |
|
344
|
-
# | W | C | E |
|
345
|
-
# |_________|_________|_________|
|
346
|
-
# | | | |
|
347
|
-
# | SW | S | SE |
|
348
|
-
# |_________|_________|_________|
|
349
|
-
#
|
350
|
-
# N = C map_height / 2 + 1024; N_width = map_width; N_height = 2048
|
351
|
-
# NW = N - map_width / 2 - 1024; NW_width = 2048; NW_height = 2048
|
352
|
-
# and so on...
|
353
|
-
|
354
|
-
# lat, lon = 45.28565000315636, 20.237121355049904
|
355
|
-
# dst = 1024
|
356
|
-
|
357
|
-
# # N
|
358
|
-
# destination = distance(meters=dst).destination((lat, lon), 0)
|
359
|
-
# lat, lon = destination.latitude, destination.longitude
|
360
|
-
# print(lat, lon)
|
259
|
+
full_tile_image = cv2.cvtColor(full_tile_image, cv2.COLOR_GRAY2BGR)
|
260
|
+
cv2.imwrite(preview_path, full_tile_image)
|
261
|
+
return [preview_path, self.stl_preview_path]
|
262
|
+
return [self.stl_preview_path]
|
maps4fs/generator/component.py
CHANGED
@@ -50,6 +50,7 @@ class Component:
|
|
50
50
|
|
51
51
|
os.makedirs(self.previews_directory, exist_ok=True)
|
52
52
|
os.makedirs(self.scripts_directory, exist_ok=True)
|
53
|
+
os.makedirs(self.info_layers_directory, exist_ok=True)
|
53
54
|
|
54
55
|
self.save_bbox()
|
55
56
|
self.preprocess()
|
@@ -87,6 +88,15 @@ class Component:
|
|
87
88
|
"""
|
88
89
|
return os.path.join(self.map_directory, "previews")
|
89
90
|
|
91
|
+
@property
|
92
|
+
def info_layers_directory(self) -> str:
|
93
|
+
"""The directory where the info layers are stored.
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
str: The directory where the info layers are stored.
|
97
|
+
"""
|
98
|
+
return os.path.join(self.map_directory, "info_layers")
|
99
|
+
|
90
100
|
@property
|
91
101
|
def scripts_directory(self) -> str:
|
92
102
|
"""The directory where the scripts are stored.
|
@@ -174,10 +184,10 @@ class Component:
|
|
174
184
|
height_distance = height_distance or int(self.map_height / 2)
|
175
185
|
width_distance = width_distance or int(self.map_width / 2)
|
176
186
|
|
177
|
-
|
187
|
+
west, south, _, _ = ox.utils_geo.bbox_from_point( # type: ignore
|
178
188
|
coordinates, dist=height_distance, project_utm=project_utm
|
179
189
|
)
|
180
|
-
_, _, east,
|
190
|
+
_, _, east, north = ox.utils_geo.bbox_from_point( # type: ignore
|
181
191
|
coordinates, dist=width_distance, project_utm=project_utm
|
182
192
|
)
|
183
193
|
bbox = north, south, east, west
|
maps4fs/generator/dem.py
CHANGED
@@ -319,7 +319,7 @@ class DEM(Component):
|
|
319
319
|
def _save_empty_dem(self, dem_output_resolution: tuple[int, int]) -> None:
|
320
320
|
"""Saves empty DEM file filled with zeros."""
|
321
321
|
dem_data = np.zeros(dem_output_resolution, dtype="uint16")
|
322
|
-
cv2.imwrite(self._dem_path, dem_data)
|
322
|
+
cv2.imwrite(self._dem_path, dem_data)
|
323
323
|
self.logger.warning("DEM data filled with zeros and saved to %s.", self._dem_path)
|
324
324
|
|
325
325
|
def grayscale_preview(self) -> str:
|
@@ -329,7 +329,6 @@ class DEM(Component):
|
|
329
329
|
Returns:
|
330
330
|
str: Path to the preview image.
|
331
331
|
"""
|
332
|
-
# rgb_dem_path = self._dem_path.replace(".png", "_grayscale.png")
|
333
332
|
grayscale_dem_path = os.path.join(self.previews_directory, "dem_grayscale.png")
|
334
333
|
|
335
334
|
self.logger.debug("Creating grayscale preview of DEM data in %s.", grayscale_dem_path)
|
@@ -346,8 +345,6 @@ class DEM(Component):
|
|
346
345
|
Returns:
|
347
346
|
list[str]: List with a single path to the DEM file
|
348
347
|
"""
|
349
|
-
|
350
|
-
# colored_dem_path = self._dem_path.replace(".png", "_colored.png")
|
351
348
|
colored_dem_path = os.path.join(self.previews_directory, "dem_colored.png")
|
352
349
|
|
353
350
|
self.logger.debug("Creating colored preview of DEM data in %s.", colored_dem_path)
|
maps4fs/generator/game.py
CHANGED
@@ -38,7 +38,8 @@ class Game:
|
|
38
38
|
_texture_schema: str | None = None
|
39
39
|
_grle_schema: str | None = None
|
40
40
|
|
41
|
-
|
41
|
+
# Order matters! Some components depend on others.
|
42
|
+
components = [Texture, I3d, DEM, Config, GRLE, Background]
|
42
43
|
|
43
44
|
def __init__(self, map_template_path: str | None = None):
|
44
45
|
if map_template_path:
|
maps4fs/generator/i3d.py
CHANGED
@@ -2,14 +2,18 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
import json
|
5
6
|
import os
|
6
7
|
from xml.etree import ElementTree as ET
|
7
8
|
|
9
|
+
from shapely.geometry import Polygon, box # type: ignore
|
10
|
+
|
8
11
|
from maps4fs.generator.component import Component
|
9
12
|
|
10
13
|
DEFAULT_HEIGHT_SCALE = 2000
|
11
14
|
DEFAULT_MAX_LOD_DISTANCE = 10000
|
12
15
|
DEFAULT_MAX_LOD_OCCLUDER_DISTANCE = 10000
|
16
|
+
NODE_ID_STARTING_VALUE = 500
|
13
17
|
|
14
18
|
|
15
19
|
# pylint: disable=R0903
|
@@ -40,17 +44,25 @@ class I3d(Component):
|
|
40
44
|
def process(self) -> None:
|
41
45
|
"""Updates the map I3D file with the default settings."""
|
42
46
|
self._update_i3d_file()
|
47
|
+
self._add_fields()
|
43
48
|
|
44
|
-
def
|
45
|
-
"""
|
49
|
+
def _get_tree(self) -> ET.ElementTree | None:
|
50
|
+
"""Returns the ElementTree instance of the map I3D file."""
|
46
51
|
if not self._map_i3d_path:
|
47
52
|
self.logger.info("I3D is not obtained, skipping the update.")
|
48
|
-
return
|
53
|
+
return None
|
49
54
|
if not os.path.isfile(self._map_i3d_path):
|
50
55
|
self.logger.warning("I3D file not found: %s.", self._map_i3d_path)
|
51
|
-
return
|
56
|
+
return None
|
57
|
+
|
58
|
+
return ET.parse(self._map_i3d_path)
|
52
59
|
|
53
|
-
|
60
|
+
def _update_i3d_file(self) -> None:
|
61
|
+
"""Updates the map I3D file with the default settings."""
|
62
|
+
|
63
|
+
tree = self._get_tree()
|
64
|
+
if tree is None:
|
65
|
+
return
|
54
66
|
|
55
67
|
self.logger.debug("Map I3D file loaded from: %s.", self._map_i3d_path)
|
56
68
|
|
@@ -76,7 +88,7 @@ class I3d(Component):
|
|
76
88
|
|
77
89
|
self.logger.debug("TerrainTransformGroup element updated in I3D file.")
|
78
90
|
|
79
|
-
tree.write(self._map_i3d_path)
|
91
|
+
tree.write(self._map_i3d_path) # type: ignore
|
80
92
|
self.logger.debug("Map I3D file saved to: %s.", self._map_i3d_path)
|
81
93
|
|
82
94
|
def previews(self) -> list[str]:
|
@@ -87,3 +99,262 @@ class I3d(Component):
|
|
87
99
|
list[str]: An empty list.
|
88
100
|
"""
|
89
101
|
return []
|
102
|
+
|
103
|
+
# pylint: disable=R0914, R0915
|
104
|
+
def _add_fields(self) -> None:
|
105
|
+
"""Adds fields to the map I3D file."""
|
106
|
+
tree = self._get_tree()
|
107
|
+
if tree is None:
|
108
|
+
return
|
109
|
+
|
110
|
+
textures_info_layer_path = os.path.join(self.info_layers_directory, "textures.json")
|
111
|
+
if not os.path.isfile(textures_info_layer_path):
|
112
|
+
self.logger.warning("Textures info layer not found: %s.", textures_info_layer_path)
|
113
|
+
return
|
114
|
+
|
115
|
+
with open(textures_info_layer_path, "r", encoding="utf-8") as textures_info_layer_file:
|
116
|
+
textures_info_layer = json.load(textures_info_layer_file)
|
117
|
+
|
118
|
+
fields: list[tuple[int, int]] | None = textures_info_layer.get("fields")
|
119
|
+
if not fields:
|
120
|
+
self.logger.warning("Fields data not found in textures info layer.")
|
121
|
+
return
|
122
|
+
|
123
|
+
self.logger.debug("Found %s fields in textures info layer.", len(fields))
|
124
|
+
|
125
|
+
root = tree.getroot()
|
126
|
+
gameplay_node = root.find(".//TransformGroup[@name='gameplay']")
|
127
|
+
if gameplay_node is not None:
|
128
|
+
self.logger.debug("Found the gameplay node.")
|
129
|
+
|
130
|
+
fields_node = gameplay_node.find(".//TransformGroup[@name='fields']")
|
131
|
+
user_attributes_node = root.find(".//UserAttributes")
|
132
|
+
|
133
|
+
if fields_node is not None:
|
134
|
+
node_id = NODE_ID_STARTING_VALUE
|
135
|
+
|
136
|
+
for field_id, field in enumerate(fields, start=1):
|
137
|
+
# Convert the top-left coordinates to the center coordinates system.
|
138
|
+
try:
|
139
|
+
fitted_field = self.fit_polygon_into_bounds(field) # type: ignore
|
140
|
+
except ValueError as e:
|
141
|
+
self.logger.warning(
|
142
|
+
"Field %s could not be fitted into the map bounds.", field_id
|
143
|
+
)
|
144
|
+
self.logger.debug("Error: %s", e)
|
145
|
+
continue
|
146
|
+
field_ccs = [
|
147
|
+
self.top_left_coordinates_to_center(point) for point in fitted_field
|
148
|
+
]
|
149
|
+
|
150
|
+
# Creating the main field node.
|
151
|
+
field_node = ET.Element("TransformGroup")
|
152
|
+
field_node.set("name", f"field{field_id}")
|
153
|
+
cx, cy = self.get_polygon_center(field_ccs)
|
154
|
+
field_node.set("translation", f"{cx} 0 {cy}")
|
155
|
+
field_node.set("nodeId", str(node_id))
|
156
|
+
|
157
|
+
# Adding UserAttributes to the field node.
|
158
|
+
user_attribute_node = self.create_user_attribute_node(node_id)
|
159
|
+
user_attributes_node.append(user_attribute_node) # type: ignore
|
160
|
+
|
161
|
+
node_id += 1
|
162
|
+
|
163
|
+
# Creating the polygon points node, which contains the points of the field.
|
164
|
+
polygon_points_node = ET.Element("TransformGroup")
|
165
|
+
polygon_points_node.set("name", "polygonPoints")
|
166
|
+
polygon_points_node.set("nodeId", str(node_id))
|
167
|
+
node_id += 1
|
168
|
+
|
169
|
+
for point_id, point in enumerate(field_ccs, start=1):
|
170
|
+
rx, ry = self.absolute_to_relative(point, (cx, cy))
|
171
|
+
|
172
|
+
node_id += 1
|
173
|
+
point_node = ET.Element("TransformGroup")
|
174
|
+
point_node.set("name", f"point{point_id}")
|
175
|
+
point_node.set("translation", f"{rx} 0 {ry}")
|
176
|
+
point_node.set("nodeId", str(node_id))
|
177
|
+
|
178
|
+
polygon_points_node.append(point_node)
|
179
|
+
|
180
|
+
field_node.append(polygon_points_node)
|
181
|
+
|
182
|
+
# Adding the name indicator node to the field node.
|
183
|
+
name_indicator_node, node_id = self.get_name_indicator_node(node_id, field_id)
|
184
|
+
field_node.append(name_indicator_node)
|
185
|
+
|
186
|
+
# Adding the teleport indicator node to the field node.
|
187
|
+
teleport_indicator_node, node_id = self.get_teleport_indicator_node(node_id)
|
188
|
+
field_node.append(teleport_indicator_node)
|
189
|
+
|
190
|
+
# Adding the field node to the fields node.
|
191
|
+
fields_node.append(field_node)
|
192
|
+
self.logger.debug("Field %s added to the I3D file.", field_id)
|
193
|
+
|
194
|
+
node_id += 1
|
195
|
+
|
196
|
+
tree.write(self._map_i3d_path) # type: ignore
|
197
|
+
self.logger.debug("Map I3D file saved to: %s.", self._map_i3d_path)
|
198
|
+
|
199
|
+
def get_name_indicator_node(self, node_id: int, field_id: int) -> tuple[ET.Element, int]:
|
200
|
+
"""Creates a name indicator node with given node ID and field ID.
|
201
|
+
|
202
|
+
Arguments:
|
203
|
+
node_id (int): The node ID of the name indicator node.
|
204
|
+
field_id (int): The ID of the field.
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
tuple[ET.Element, int]: The name indicator node and the updated node ID.
|
208
|
+
"""
|
209
|
+
node_id += 1
|
210
|
+
name_indicator_node = ET.Element("TransformGroup")
|
211
|
+
name_indicator_node.set("name", "nameIndicator")
|
212
|
+
name_indicator_node.set("nodeId", str(node_id))
|
213
|
+
|
214
|
+
node_id += 1
|
215
|
+
note_node = ET.Element("Note")
|
216
|
+
note_node.set("name", "Note")
|
217
|
+
note_node.set("nodeId", str(node_id))
|
218
|
+
note_node.set("text", f"field{field_id}
0.00 ha")
|
219
|
+
note_node.set("color", "4278190080")
|
220
|
+
note_node.set("fixedSize", "true")
|
221
|
+
|
222
|
+
name_indicator_node.append(note_node)
|
223
|
+
|
224
|
+
return name_indicator_node, node_id
|
225
|
+
|
226
|
+
def get_teleport_indicator_node(self, node_id: int) -> tuple[ET.Element, int]:
|
227
|
+
"""Creates a teleport indicator node with given node ID.
|
228
|
+
|
229
|
+
Arguments:
|
230
|
+
node_id (int): The node ID of the teleport indicator node.
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
tuple[ET.Element, int]: The teleport indicator node and the updated node ID.
|
234
|
+
"""
|
235
|
+
node_id += 1
|
236
|
+
teleport_indicator_node = ET.Element("TransformGroup")
|
237
|
+
teleport_indicator_node.set("name", "teleportIndicator")
|
238
|
+
teleport_indicator_node.set("nodeId", str(node_id))
|
239
|
+
|
240
|
+
return teleport_indicator_node, node_id
|
241
|
+
|
242
|
+
def get_polygon_center(self, polygon_points: list[tuple[int, int]]) -> tuple[int, int]:
|
243
|
+
"""Calculates the center of a polygon defined by a list of points.
|
244
|
+
|
245
|
+
Arguments:
|
246
|
+
polygon_points (list[tuple[int, int]]): The points of the polygon.
|
247
|
+
|
248
|
+
Returns:
|
249
|
+
tuple[int, int]: The center of the polygon.
|
250
|
+
"""
|
251
|
+
polygon = Polygon(polygon_points)
|
252
|
+
center = polygon.centroid
|
253
|
+
return int(center.x), int(center.y)
|
254
|
+
|
255
|
+
def absolute_to_relative(
|
256
|
+
self, point: tuple[int, int], center: tuple[int, int]
|
257
|
+
) -> tuple[int, int]:
|
258
|
+
"""Converts a pair of absolute coordinates to relative coordinates.
|
259
|
+
|
260
|
+
Arguments:
|
261
|
+
point (tuple[int, int]): The absolute coordinates.
|
262
|
+
center (tuple[int, int]): The center coordinates.
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
tuple[int, int]: The relative coordinates.
|
266
|
+
"""
|
267
|
+
cx, cy = center
|
268
|
+
x, y = point
|
269
|
+
return x - cx, y - cy
|
270
|
+
|
271
|
+
def top_left_coordinates_to_center(self, top_left: tuple[int, int]) -> tuple[int, int]:
|
272
|
+
"""Converts a pair of coordinates from the top-left system to the center system.
|
273
|
+
In top-left system, the origin (0, 0) is in the top-left corner of the map, while in the
|
274
|
+
center system, the origin is in the center of the map.
|
275
|
+
|
276
|
+
Arguments:
|
277
|
+
top_left (tuple[int, int]): The coordinates in the top-left system.
|
278
|
+
|
279
|
+
Returns:
|
280
|
+
tuple[int, int]: The coordinates in the center system.
|
281
|
+
"""
|
282
|
+
x, y = top_left
|
283
|
+
cs_x = x - self.map_width // 2
|
284
|
+
cs_y = y - self.map_height // 2
|
285
|
+
|
286
|
+
return cs_x, cs_y
|
287
|
+
|
288
|
+
def fit_polygon_into_bounds(
|
289
|
+
self, polygon_points: list[tuple[int, int]]
|
290
|
+
) -> list[tuple[int, int]]:
|
291
|
+
"""Fits a polygon into the bounds of the map.
|
292
|
+
|
293
|
+
Arguments:
|
294
|
+
polygon_points (list[tuple[int, int]]): The points of the polygon.
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
list[tuple[int, int]]: The points of the polygon fitted into the map bounds.
|
298
|
+
"""
|
299
|
+
min_x = min_y = 0
|
300
|
+
max_x, max_y = self.map_width, self.map_height
|
301
|
+
|
302
|
+
# Create a polygon from the given points
|
303
|
+
polygon = Polygon(polygon_points)
|
304
|
+
|
305
|
+
# Create a bounding box for the map bounds
|
306
|
+
bounds = box(min_x, min_y, max_x, max_y)
|
307
|
+
|
308
|
+
# Intersect the polygon with the bounds to fit it within the map
|
309
|
+
fitted_polygon = polygon.intersection(bounds)
|
310
|
+
|
311
|
+
if not isinstance(fitted_polygon, Polygon):
|
312
|
+
raise ValueError("The fitted polygon is not a valid polygon.")
|
313
|
+
|
314
|
+
# Return the fitted polygon points
|
315
|
+
return list(fitted_polygon.exterior.coords)
|
316
|
+
|
317
|
+
@staticmethod
|
318
|
+
def create_user_attribute_node(node_id: int) -> ET.Element:
|
319
|
+
"""Creates an XML user attribute node with given node ID.
|
320
|
+
|
321
|
+
Arguments:
|
322
|
+
node_id (int): The node ID of the user attribute node.
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
ET.Element: The created user attribute node.
|
326
|
+
"""
|
327
|
+
user_attribute_node = ET.Element("UserAttribute")
|
328
|
+
user_attribute_node.set("nodeId", str(node_id))
|
329
|
+
|
330
|
+
attributes = [
|
331
|
+
("angle", "integer", "0"),
|
332
|
+
("missionAllowed", "boolean", "true"),
|
333
|
+
("missionOnlyGrass", "boolean", "false"),
|
334
|
+
("nameIndicatorIndex", "string", "1"),
|
335
|
+
("polygonIndex", "string", "0"),
|
336
|
+
("teleportIndicatorIndex", "string", "2"),
|
337
|
+
]
|
338
|
+
|
339
|
+
for name, attr_type, value in attributes:
|
340
|
+
user_attribute_node.append(I3d.create_attribute_node(name, attr_type, value))
|
341
|
+
|
342
|
+
return user_attribute_node
|
343
|
+
|
344
|
+
@staticmethod
|
345
|
+
def create_attribute_node(name: str, attr_type: str, value: str) -> ET.Element:
|
346
|
+
"""Creates an XML attribute node with given name, type, and value.
|
347
|
+
|
348
|
+
Arguments:
|
349
|
+
name (str): The name of the attribute.
|
350
|
+
attr_type (str): The type of the attribute.
|
351
|
+
value (str): The value of the attribute.
|
352
|
+
|
353
|
+
Returns:
|
354
|
+
ET.Element: The created attribute node.
|
355
|
+
"""
|
356
|
+
attribute_node = ET.Element("Attribute")
|
357
|
+
attribute_node.set("name", name)
|
358
|
+
attribute_node.set("type", attr_type)
|
359
|
+
attribute_node.set("value", value)
|
360
|
+
return attribute_node
|
maps4fs/generator/texture.py
CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import json
|
6
6
|
import os
|
7
|
-
import
|
7
|
+
from collections import defaultdict
|
8
8
|
from typing import Any, Callable, Generator, Optional
|
9
9
|
|
10
10
|
import cv2
|
@@ -41,6 +41,9 @@ class Texture(Component):
|
|
41
41
|
tags (dict[str, str | list[str]]): Dictionary of tags to search for.
|
42
42
|
width (int | None): Width of the polygon in meters (only for LineString).
|
43
43
|
color (tuple[int, int, int]): Color of the layer in BGR format.
|
44
|
+
exclude_weight (bool): Flag to exclude weight from the texture.
|
45
|
+
priority (int | None): Priority of the layer.
|
46
|
+
info_layer (str | None): Name of the corresnponding info layer.
|
44
47
|
|
45
48
|
Attributes:
|
46
49
|
name (str): Name of the layer.
|
@@ -58,6 +61,7 @@ class Texture(Component):
|
|
58
61
|
color: tuple[int, int, int] | list[int] | None = None,
|
59
62
|
exclude_weight: bool = False,
|
60
63
|
priority: int | None = None,
|
64
|
+
info_layer: str | None = None,
|
61
65
|
):
|
62
66
|
self.name = name
|
63
67
|
self.count = count
|
@@ -66,6 +70,7 @@ class Texture(Component):
|
|
66
70
|
self.color = color if color else (255, 255, 255)
|
67
71
|
self.exclude_weight = exclude_weight
|
68
72
|
self.priority = priority
|
73
|
+
self.info_layer = info_layer
|
69
74
|
|
70
75
|
def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
|
71
76
|
"""Returns dictionary with layer data.
|
@@ -80,6 +85,7 @@ class Texture(Component):
|
|
80
85
|
"color": list(self.color),
|
81
86
|
"exclude_weight": self.exclude_weight,
|
82
87
|
"priority": self.priority,
|
88
|
+
"info_layer": self.info_layer,
|
83
89
|
}
|
84
90
|
|
85
91
|
data = {k: v for k, v in data.items() if v is not None}
|
@@ -178,6 +184,9 @@ class Texture(Component):
|
|
178
184
|
self.info_save_path = os.path.join(self.map_directory, "generation_info.json")
|
179
185
|
self.logger.debug("Generation info save path: %s.", self.info_save_path)
|
180
186
|
|
187
|
+
self.info_layer_path = os.path.join(self.info_layers_directory, "textures.json")
|
188
|
+
self.logger.debug("Info layer path: %s.", self.info_layer_path)
|
189
|
+
|
181
190
|
def get_base_layer(self) -> Layer | None:
|
182
191
|
"""Returns base layer.
|
183
192
|
|
@@ -297,6 +306,10 @@ class Texture(Component):
|
|
297
306
|
|
298
307
|
cumulative_image = None
|
299
308
|
|
309
|
+
# Dictionary to store info layer data.
|
310
|
+
# Key is a layer.info_layer, value is a list of polygon points as tuples (x, y).
|
311
|
+
info_layer_data = defaultdict(list)
|
312
|
+
|
300
313
|
for layer in layers:
|
301
314
|
if not layer.tags:
|
302
315
|
self.logger.debug("Layer %s has no tags, there's nothing to draw.", layer.name)
|
@@ -317,6 +330,8 @@ class Texture(Component):
|
|
317
330
|
mask = cv2.bitwise_not(cumulative_image)
|
318
331
|
|
319
332
|
for polygon in self.polygons(layer.tags, layer.width): # type: ignore
|
333
|
+
if layer.info_layer:
|
334
|
+
info_layer_data[layer.info_layer].append(self.np_to_polygon_points(polygon))
|
320
335
|
cv2.fillPoly(layer_image, [polygon], color=255) # type: ignore
|
321
336
|
|
322
337
|
output_image = cv2.bitwise_and(layer_image, mask)
|
@@ -326,6 +341,10 @@ class Texture(Component):
|
|
326
341
|
cv2.imwrite(layer_path, output_image)
|
327
342
|
self.logger.debug("Texture %s saved.", layer_path)
|
328
343
|
|
344
|
+
# Save info layer data.
|
345
|
+
with open(self.info_layer_path, "w", encoding="utf-8") as f:
|
346
|
+
json.dump(info_layer_data, f, ensure_ascii=False, indent=4)
|
347
|
+
|
329
348
|
if cumulative_image is not None:
|
330
349
|
self.draw_base_layer(cumulative_image)
|
331
350
|
|
@@ -428,6 +447,17 @@ class Texture(Component):
|
|
428
447
|
"""
|
429
448
|
return int(self.map_height * (1 - (y - self.minimum_y) / (self.maximum_y - self.minimum_y)))
|
430
449
|
|
450
|
+
def np_to_polygon_points(self, np_array: np.ndarray) -> list[tuple[int, int]]:
|
451
|
+
"""Converts numpy array of polygon points to list of tuples.
|
452
|
+
|
453
|
+
Arguments:
|
454
|
+
np_array (np.ndarray): Numpy array of polygon points.
|
455
|
+
|
456
|
+
Returns:
|
457
|
+
list[tuple[int, int]]: List of tuples.
|
458
|
+
"""
|
459
|
+
return [(int(x), int(y)) for x, y in np_array.reshape(-1, 2)]
|
460
|
+
|
431
461
|
# pylint: disable=W0613
|
432
462
|
def _to_np(self, geometry: shapely.geometry.polygon.Polygon, *args) -> np.ndarray:
|
433
463
|
"""Converts Polygon geometry to numpy array of polygon points.
|
@@ -509,14 +539,12 @@ class Texture(Component):
|
|
509
539
|
Generator[np.ndarray, None, None]: Numpy array of polygon points.
|
510
540
|
"""
|
511
541
|
try:
|
512
|
-
|
513
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
514
|
-
objects = ox.features_from_bbox(bbox=self.bbox, tags=tags)
|
542
|
+
objects = ox.features_from_bbox(bbox=self.new_bbox, tags=tags)
|
515
543
|
except Exception as e: # pylint: disable=W0718
|
516
544
|
self.logger.warning("Error fetching objects for tags: %s.", tags)
|
517
545
|
self.logger.warning(e)
|
518
546
|
return
|
519
|
-
objects_utm = ox.project_gdf(objects, to_latlong=False)
|
547
|
+
objects_utm = ox.projection.project_gdf(objects, to_latlong=False)
|
520
548
|
self.logger.debug("Fetched %s elements for tags: %s.", len(objects_utm), tags)
|
521
549
|
|
522
550
|
for _, obj in objects_utm.iterrows():
|
@@ -579,6 +607,7 @@ class Texture(Component):
|
|
579
607
|
merged.dtype,
|
580
608
|
)
|
581
609
|
preview_path = os.path.join(self.previews_directory, "textures_osm.png")
|
582
|
-
|
610
|
+
|
611
|
+
cv2.imwrite(preview_path, merged) # type: ignore
|
583
612
|
self.logger.info("Preview saved to %s.", preview_path)
|
584
613
|
return preview_path
|
maps4fs/generator/tile.py
CHANGED
@@ -36,10 +36,10 @@ class Tile(DEM):
|
|
36
36
|
|
37
37
|
self.logger.debug("Generating tile for code %s", self.code)
|
38
38
|
|
39
|
-
|
40
|
-
os.makedirs(
|
39
|
+
background_directory = os.path.join(self.map_directory, "background")
|
40
|
+
os.makedirs(background_directory, exist_ok=True)
|
41
41
|
|
42
|
-
self._dem_path = os.path.join(
|
42
|
+
self._dem_path = os.path.join(background_directory, f"{self.code}.png")
|
43
43
|
self.logger.debug("DEM path for tile %s is %s", self.code, self._dem_path)
|
44
44
|
|
45
45
|
def get_output_resolution(self) -> tuple[int, int]:
|
@@ -0,0 +1,63 @@
|
|
1
|
+
"""This module contains functions to work with the background terrain of the map."""
|
2
|
+
|
3
|
+
import cv2
|
4
|
+
import numpy as np
|
5
|
+
import trimesh # type: ignore
|
6
|
+
|
7
|
+
|
8
|
+
# pylint: disable=R0801, R0914
|
9
|
+
def plane_from_np(
|
10
|
+
dem_data: np.ndarray,
|
11
|
+
resize_factor: float,
|
12
|
+
simplify_factor: int,
|
13
|
+
save_path: str,
|
14
|
+
) -> None:
|
15
|
+
"""Generates a 3D obj file based on DEM data.
|
16
|
+
|
17
|
+
Arguments:
|
18
|
+
dem_data (np.ndarray) -- The DEM data as a numpy array.
|
19
|
+
resize_factor (float) -- The factor by which the DEM data will be resized. Bigger values
|
20
|
+
will result in a bigger mesh.
|
21
|
+
simplify_factor (int) -- The factor by which the mesh will be simplified. Bigger values
|
22
|
+
will result in a simpler mesh.
|
23
|
+
save_path (str) -- The path to save the obj file.
|
24
|
+
"""
|
25
|
+
dem_data = cv2.resize( # pylint: disable=no-member
|
26
|
+
dem_data, (0, 0), fx=resize_factor, fy=resize_factor
|
27
|
+
)
|
28
|
+
|
29
|
+
# Invert the height values.
|
30
|
+
dem_data = dem_data.max() - dem_data
|
31
|
+
|
32
|
+
rows, cols = dem_data.shape
|
33
|
+
x = np.linspace(0, cols - 1, cols)
|
34
|
+
y = np.linspace(0, rows - 1, rows)
|
35
|
+
x, y = np.meshgrid(x, y)
|
36
|
+
z = dem_data
|
37
|
+
|
38
|
+
vertices = np.column_stack([x.ravel(), y.ravel(), z.ravel()])
|
39
|
+
faces = []
|
40
|
+
|
41
|
+
for i in range(rows - 1):
|
42
|
+
for j in range(cols - 1):
|
43
|
+
top_left = i * cols + j
|
44
|
+
top_right = top_left + 1
|
45
|
+
bottom_left = top_left + cols
|
46
|
+
bottom_right = bottom_left + 1
|
47
|
+
|
48
|
+
faces.append([top_left, bottom_left, bottom_right])
|
49
|
+
faces.append([top_left, bottom_right, top_right])
|
50
|
+
|
51
|
+
faces = np.array(faces) # type: ignore
|
52
|
+
mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
|
53
|
+
|
54
|
+
# Apply rotation: 180 degrees around Y-axis and Z-axis
|
55
|
+
rotation_matrix_y = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
|
56
|
+
rotation_matrix_z = trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
|
57
|
+
mesh.apply_transform(rotation_matrix_y)
|
58
|
+
mesh.apply_transform(rotation_matrix_z)
|
59
|
+
|
60
|
+
# Simplify the mesh to reduce the number of faces.
|
61
|
+
mesh = mesh.simplify_quadric_decimation(face_count=len(faces) // simplify_factor)
|
62
|
+
|
63
|
+
mesh.export(save_path)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.1
|
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
|
@@ -14,7 +14,7 @@ Classifier: Operating System :: OS Independent
|
|
14
14
|
Description-Content-Type: text/markdown
|
15
15
|
License-File: LICENSE.md
|
16
16
|
Requires-Dist: opencv-python
|
17
|
-
Requires-Dist: osmnx
|
17
|
+
Requires-Dist: osmnx>=2.0.0
|
18
18
|
Requires-Dist: rasterio
|
19
19
|
Requires-Dist: folium
|
20
20
|
Requires-Dist: geopy
|
@@ -31,8 +31,10 @@ Requires-Dist: pympler
|
|
31
31
|
<p align="center">
|
32
32
|
<a href="#Quick-Start">Quick Start</a> •
|
33
33
|
<a href="#Overview">Overview</a> •
|
34
|
-
<a href="#
|
34
|
+
<a href="#Step-by-step">Create a map in 10 steps</a> •
|
35
|
+
<a href="#How-To-Run">How-To-Run</a><br>
|
35
36
|
<a href="docs/FAQ.md">FAQ</a> •
|
37
|
+
<a href="docs/map_structure.md">Map Structure</a> •
|
36
38
|
<a href="#Modder-Toolbox">Modder Toolbox</a><br>
|
37
39
|
<a href="#Supported-objects">Supported objects</a> •
|
38
40
|
<a href="#Generation-info">Generation info</a> •
|
@@ -68,6 +70,7 @@ Requires-Dist: pympler
|
|
68
70
|
🔷 Generates *.obj files for background terrain based on the real-world height map<br>
|
69
71
|
📄 Generates scripts to download high-resolution satellite images from [QGIS](https://qgis.org/download/) in one click<br>
|
70
72
|
🧰 Modder Toolbox to help you with various of tasks 🆕<br>
|
73
|
+
🌾 Automatically generates fields 🆕<br>
|
71
74
|
|
72
75
|
<p align="center">
|
73
76
|
<img src="https://github.com/user-attachments/assets/cf8f5752-9c69-4018-bead-290f59ba6976"><br>
|
@@ -76,6 +79,8 @@ Requires-Dist: pympler
|
|
76
79
|
🛰️ Realistic background terrain objects with satellite images.<br><br>
|
77
80
|
<img src="https://github.com/user-attachments/assets/80e5923c-22c7-4dc0-8906-680902511f3a"><br>
|
78
81
|
🗒️ True-to-life blueprints for fast and precise modding.<br><br>
|
82
|
+
<img width="480" src="https://github.com/user-attachments/assets/1a8802d2-6a3b-4bfa-af2b-7c09478e199b"><br>
|
83
|
+
🌾 Field generation with one click.<br><br>
|
79
84
|
<img src="https://github.com/user-attachments/assets/cce45575-c917-4a1b-bdc0-6368e32ccdff"><br>
|
80
85
|
📏 Almost any possible map sizes.
|
81
86
|
</p>
|
@@ -126,6 +131,9 @@ Parameters:
|
|
126
131
|
- coordinates: 45.15, 19.71
|
127
132
|
- size: 16 x 16 km
|
128
133
|
|
134
|
+
## Step by step
|
135
|
+
Don't know where to start? Don't worry, just follow this [step-by-step guide](docs/step_by_step.md) to create your first map in 10 simple steps.<br>
|
136
|
+
|
129
137
|
## How-To-Run
|
130
138
|
|
131
139
|
You'll find detailed instructions on how to run the project below. But if you prefer video tutorials, here's one for you:
|
@@ -202,12 +210,15 @@ The map will be saved in the `map_directory` directory.
|
|
202
210
|
## Modder Toolbox
|
203
211
|
The tool now has a Modder Toolbox, which is a set of tools to help you with various tasks. You can open the toolbox by switching to the `🧰 Modder Toolbox` tab in the StreamLit app.<br>
|
204
212
|
|
205
|
-

|
206
214
|
|
207
215
|
### Tool categories
|
208
216
|
Tools are divided into categories, which are listed below.
|
209
217
|
#### Textures and DEM
|
210
|
-
- **GeoTIFF windowing** - allows you to upload your GeoTIFF file and select the region of interest to extract it from the image.
|
218
|
+
- **GeoTIFF windowing** - allows you to upload your GeoTIFF file and select the region of interest to extract it from the image. It's useful when you have high-resolution DEM data and want to create the height map using it.
|
219
|
+
|
220
|
+
#### Background terrain
|
221
|
+
- **Convert image to obj model** - allows you to convert the image to the obj model. You can use this tool to create the background terrain for your map. It can be extremely useful if you have access to the sources of high-resolution DEM data and want to create the background terrain using it.
|
211
222
|
|
212
223
|
## Supported objects
|
213
224
|
The project is based on the [OpenStreetMap](https://www.openstreetmap.org/) data. So, refer to [this page](https://wiki.openstreetmap.org/wiki/Map_Features) to understand the list below.
|
@@ -376,7 +387,7 @@ Let's have a closer look at the fields:
|
|
376
387
|
|
377
388
|
## Background terrain
|
378
389
|
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>
|
379
|
-
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 `
|
390
|
+
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 `background` directory with corresponding names: `N.obj`, `NE.obj`, `E.obj` and so on.<br>
|
380
391
|
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>
|
381
392
|
|
382
393
|

|
@@ -0,0 +1,23 @@
|
|
1
|
+
maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
|
2
|
+
maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
|
3
|
+
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
4
|
+
maps4fs/generator/background.py,sha256=VGzKELm6W1k3NFA8akaDWlw20kcyiK68j705jDroGoM,10517
|
5
|
+
maps4fs/generator/component.py,sha256=swOocaEOP3XtZgHfgDJ0ROALWoLgCJwMq8ubl0d2WrI,11085
|
6
|
+
maps4fs/generator/config.py,sha256=qomeSaHNgSYvRyPbGkyDjgAxU1jUMND8wESuy9uXcV4,4280
|
7
|
+
maps4fs/generator/dem.py,sha256=_VbQ5M7xGCtG27EHKzKkjIxMR58ghfbu7H89txNw5gg,16987
|
8
|
+
maps4fs/generator/game.py,sha256=3Y65K-XQDKxfMRSrjsmE8qX0tQSd0aDzMUT6yGIhzXg,7450
|
9
|
+
maps4fs/generator/grle.py,sha256=kyFiGu-2aXrLVB_8GeXlGvG5ULwlSVuqU1yfpt_0br4,3134
|
10
|
+
maps4fs/generator/i3d.py,sha256=nlmI8SdzzKMOGQRvoZp7Pxo6lzm1jneoYNoL24v84zw,14281
|
11
|
+
maps4fs/generator/map.py,sha256=1EJfq5928xbV7v9vBBuoC3VpD5gC1SMDBTQ7geH6yaA,4678
|
12
|
+
maps4fs/generator/path_steps.py,sha256=yeN6hmOD8O2LMozUf4HcQMIy8WJ5sHF5pGQT_s2FfOw,3147
|
13
|
+
maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
|
14
|
+
maps4fs/generator/texture.py,sha256=FnCagiGyeCb_lIqOTHgHISZo17A_E9kHqGeUhqz_iZY,23670
|
15
|
+
maps4fs/generator/tile.py,sha256=_UZ-iHGPghUfpYcG0OboquNczpZ1FaCVYpBgNLd72eo,2160
|
16
|
+
maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
17
|
+
maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
|
18
|
+
maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
|
19
|
+
maps4fs-1.0.1.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
|
20
|
+
maps4fs-1.0.1.dist-info/METADATA,sha256=TAiNAAZfxuB1mH1TFacPAIfpfU9SPad7v7eEDfjW3Y4,26086
|
21
|
+
maps4fs-1.0.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
22
|
+
maps4fs-1.0.1.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
23
|
+
maps4fs-1.0.1.dist-info/RECORD,,
|
maps4fs-0.9.98.dist-info/RECORD
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
|
2
|
-
maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
|
3
|
-
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
4
|
-
maps4fs/generator/background.py,sha256=h6zyqMc51eOD8_Jbe8eP9DJFy27-XItzIOVEThQKFdk,14011
|
5
|
-
maps4fs/generator/component.py,sha256=ibfl2V_niXqWaFO05aaV25JbPBaupUpKBOLxKQq0Fv4,10714
|
6
|
-
maps4fs/generator/config.py,sha256=qomeSaHNgSYvRyPbGkyDjgAxU1jUMND8wESuy9uXcV4,4280
|
7
|
-
maps4fs/generator/dem.py,sha256=aU0syr7pu81hd6oVn1rjQoDsDAYJro-58i1STTUz3jw,17167
|
8
|
-
maps4fs/generator/game.py,sha256=hvVsU4l43kHYM9oZ6WBFhDiI8U3gJxzSeavAvXyh0DI,7395
|
9
|
-
maps4fs/generator/grle.py,sha256=kyFiGu-2aXrLVB_8GeXlGvG5ULwlSVuqU1yfpt_0br4,3134
|
10
|
-
maps4fs/generator/i3d.py,sha256=CSdVDQJOybeu6DQhxTxImEOSsbbXSNqAN8sunNuicM0,3602
|
11
|
-
maps4fs/generator/map.py,sha256=1EJfq5928xbV7v9vBBuoC3VpD5gC1SMDBTQ7geH6yaA,4678
|
12
|
-
maps4fs/generator/path_steps.py,sha256=yeN6hmOD8O2LMozUf4HcQMIy8WJ5sHF5pGQT_s2FfOw,3147
|
13
|
-
maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
|
14
|
-
maps4fs/generator/texture.py,sha256=9qBlL3udozp5ATg9N48naRjTQPWe_eOPgs28hG4U13g,22381
|
15
|
-
maps4fs/generator/tile.py,sha256=6zUpDidPcPRTGzQvYSNw-0Pj-if2WiakU2qb-40vyOk,2151
|
16
|
-
maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
17
|
-
maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
|
18
|
-
maps4fs-0.9.98.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
|
19
|
-
maps4fs-0.9.98.dist-info/METADATA,sha256=O0p9Zjx6_gFZHTtBurPZkMmsVcqzOUpK4uQUGfPmH_M,25194
|
20
|
-
maps4fs-0.9.98.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
21
|
-
maps4fs-0.9.98.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
22
|
-
maps4fs-0.9.98.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|