maps4fs 0.9.97__py3-none-any.whl → 0.9.99__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maps4fs/generator/component.py +10 -0
- maps4fs/generator/game.py +2 -1
- maps4fs/generator/grle.py +0 -9
- maps4fs/generator/i3d.py +277 -6
- maps4fs/generator/texture.py +31 -0
- {maps4fs-0.9.97.dist-info → maps4fs-0.9.99.dist-info}/METADATA +4 -1
- {maps4fs-0.9.97.dist-info → maps4fs-0.9.99.dist-info}/RECORD +10 -10
- {maps4fs-0.9.97.dist-info → maps4fs-0.9.99.dist-info}/LICENSE.md +0 -0
- {maps4fs-0.9.97.dist-info → maps4fs-0.9.99.dist-info}/WHEEL +0 -0
- {maps4fs-0.9.97.dist-info → maps4fs-0.9.99.dist-info}/top_level.txt +0 -0
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.
|
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/grle.py
CHANGED
@@ -57,15 +57,6 @@ class GRLE(Component):
|
|
57
57
|
width = int(self.map_width * info_layer["width_multiplier"])
|
58
58
|
data_type = info_layer["data_type"]
|
59
59
|
|
60
|
-
self.logger.debug(
|
61
|
-
"Creating InfoLayer PNG file %s with dimensions %sx%s, %s channels, "
|
62
|
-
"and data type %s.",
|
63
|
-
file_path,
|
64
|
-
height,
|
65
|
-
width,
|
66
|
-
data_type,
|
67
|
-
)
|
68
|
-
|
69
60
|
# Create the InfoLayer PNG file with zeros.
|
70
61
|
info_layer_data = np.zeros((height, width), dtype=data_type)
|
71
62
|
print(info_layer_data.shape)
|
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
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
5
5
|
import json
|
6
6
|
import os
|
7
7
|
import warnings
|
8
|
+
from collections import defaultdict
|
8
9
|
from typing import Any, Callable, Generator, Optional
|
9
10
|
|
10
11
|
import cv2
|
@@ -41,6 +42,9 @@ class Texture(Component):
|
|
41
42
|
tags (dict[str, str | list[str]]): Dictionary of tags to search for.
|
42
43
|
width (int | None): Width of the polygon in meters (only for LineString).
|
43
44
|
color (tuple[int, int, int]): Color of the layer in BGR format.
|
45
|
+
exclude_weight (bool): Flag to exclude weight from the texture.
|
46
|
+
priority (int | None): Priority of the layer.
|
47
|
+
info_layer (str | None): Name of the corresnponding info layer.
|
44
48
|
|
45
49
|
Attributes:
|
46
50
|
name (str): Name of the layer.
|
@@ -58,6 +62,7 @@ class Texture(Component):
|
|
58
62
|
color: tuple[int, int, int] | list[int] | None = None,
|
59
63
|
exclude_weight: bool = False,
|
60
64
|
priority: int | None = None,
|
65
|
+
info_layer: str | None = None,
|
61
66
|
):
|
62
67
|
self.name = name
|
63
68
|
self.count = count
|
@@ -66,6 +71,7 @@ class Texture(Component):
|
|
66
71
|
self.color = color if color else (255, 255, 255)
|
67
72
|
self.exclude_weight = exclude_weight
|
68
73
|
self.priority = priority
|
74
|
+
self.info_layer = info_layer
|
69
75
|
|
70
76
|
def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
|
71
77
|
"""Returns dictionary with layer data.
|
@@ -80,6 +86,7 @@ class Texture(Component):
|
|
80
86
|
"color": list(self.color),
|
81
87
|
"exclude_weight": self.exclude_weight,
|
82
88
|
"priority": self.priority,
|
89
|
+
"info_layer": self.info_layer,
|
83
90
|
}
|
84
91
|
|
85
92
|
data = {k: v for k, v in data.items() if v is not None}
|
@@ -178,6 +185,9 @@ class Texture(Component):
|
|
178
185
|
self.info_save_path = os.path.join(self.map_directory, "generation_info.json")
|
179
186
|
self.logger.debug("Generation info save path: %s.", self.info_save_path)
|
180
187
|
|
188
|
+
self.info_layer_path = os.path.join(self.info_layers_directory, "textures.json")
|
189
|
+
self.logger.debug("Info layer path: %s.", self.info_layer_path)
|
190
|
+
|
181
191
|
def get_base_layer(self) -> Layer | None:
|
182
192
|
"""Returns base layer.
|
183
193
|
|
@@ -297,6 +307,10 @@ class Texture(Component):
|
|
297
307
|
|
298
308
|
cumulative_image = None
|
299
309
|
|
310
|
+
# Dictionary to store info layer data.
|
311
|
+
# Key is a layer.info_layer, value is a list of polygon points as tuples (x, y).
|
312
|
+
info_layer_data = defaultdict(list)
|
313
|
+
|
300
314
|
for layer in layers:
|
301
315
|
if not layer.tags:
|
302
316
|
self.logger.debug("Layer %s has no tags, there's nothing to draw.", layer.name)
|
@@ -317,6 +331,8 @@ class Texture(Component):
|
|
317
331
|
mask = cv2.bitwise_not(cumulative_image)
|
318
332
|
|
319
333
|
for polygon in self.polygons(layer.tags, layer.width): # type: ignore
|
334
|
+
if layer.info_layer:
|
335
|
+
info_layer_data[layer.info_layer].append(self.np_to_polygon_points(polygon))
|
320
336
|
cv2.fillPoly(layer_image, [polygon], color=255) # type: ignore
|
321
337
|
|
322
338
|
output_image = cv2.bitwise_and(layer_image, mask)
|
@@ -326,6 +342,10 @@ class Texture(Component):
|
|
326
342
|
cv2.imwrite(layer_path, output_image)
|
327
343
|
self.logger.debug("Texture %s saved.", layer_path)
|
328
344
|
|
345
|
+
# Save info layer data.
|
346
|
+
with open(self.info_layer_path, "w", encoding="utf-8") as f:
|
347
|
+
json.dump(info_layer_data, f, ensure_ascii=False, indent=4)
|
348
|
+
|
329
349
|
if cumulative_image is not None:
|
330
350
|
self.draw_base_layer(cumulative_image)
|
331
351
|
|
@@ -428,6 +448,17 @@ class Texture(Component):
|
|
428
448
|
"""
|
429
449
|
return int(self.map_height * (1 - (y - self.minimum_y) / (self.maximum_y - self.minimum_y)))
|
430
450
|
|
451
|
+
def np_to_polygon_points(self, np_array: np.ndarray) -> list[tuple[int, int]]:
|
452
|
+
"""Converts numpy array of polygon points to list of tuples.
|
453
|
+
|
454
|
+
Arguments:
|
455
|
+
np_array (np.ndarray): Numpy array of polygon points.
|
456
|
+
|
457
|
+
Returns:
|
458
|
+
list[tuple[int, int]]: List of tuples.
|
459
|
+
"""
|
460
|
+
return [(int(x), int(y)) for x, y in np_array.reshape(-1, 2)]
|
461
|
+
|
431
462
|
# pylint: disable=W0613
|
432
463
|
def _to_np(self, geometry: shapely.geometry.polygon.Polygon, *args) -> np.ndarray:
|
433
464
|
"""Converts Polygon geometry to numpy array of polygon points.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: maps4fs
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.99
|
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
|
@@ -68,6 +68,7 @@ Requires-Dist: pympler
|
|
68
68
|
🔷 Generates *.obj files for background terrain based on the real-world height map<br>
|
69
69
|
📄 Generates scripts to download high-resolution satellite images from [QGIS](https://qgis.org/download/) in one click<br>
|
70
70
|
🧰 Modder Toolbox to help you with various of tasks 🆕<br>
|
71
|
+
🌾 Automatically generates fields 🆕<br>
|
71
72
|
|
72
73
|
<p align="center">
|
73
74
|
<img src="https://github.com/user-attachments/assets/cf8f5752-9c69-4018-bead-290f59ba6976"><br>
|
@@ -76,6 +77,8 @@ Requires-Dist: pympler
|
|
76
77
|
🛰️ Realistic background terrain objects with satellite images.<br><br>
|
77
78
|
<img src="https://github.com/user-attachments/assets/80e5923c-22c7-4dc0-8906-680902511f3a"><br>
|
78
79
|
🗒️ True-to-life blueprints for fast and precise modding.<br><br>
|
80
|
+
<img width="480" src="https://github.com/user-attachments/assets/1a8802d2-6a3b-4bfa-af2b-7c09478e199b"><br>
|
81
|
+
🌾 Field generation with one click.<br><br>
|
79
82
|
<img src="https://github.com/user-attachments/assets/cce45575-c917-4a1b-bdc0-6368e32ccdff"><br>
|
80
83
|
📏 Almost any possible map sizes.
|
81
84
|
</p>
|
@@ -2,21 +2,21 @@ maps4fs/__init__.py,sha256=da4jmND2Ths9AffnkAKgzLHNkvKFOc_l21gJisPXqWY,155
|
|
2
2
|
maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
|
3
3
|
maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
4
4
|
maps4fs/generator/background.py,sha256=h6zyqMc51eOD8_Jbe8eP9DJFy27-XItzIOVEThQKFdk,14011
|
5
|
-
maps4fs/generator/component.py,sha256=
|
5
|
+
maps4fs/generator/component.py,sha256=gtb_p_cHfRgOQwCLCxmQPNObuipqvmGaGAct7KUxAtI,11053
|
6
6
|
maps4fs/generator/config.py,sha256=qomeSaHNgSYvRyPbGkyDjgAxU1jUMND8wESuy9uXcV4,4280
|
7
7
|
maps4fs/generator/dem.py,sha256=aU0syr7pu81hd6oVn1rjQoDsDAYJro-58i1STTUz3jw,17167
|
8
|
-
maps4fs/generator/game.py,sha256=
|
9
|
-
maps4fs/generator/grle.py,sha256=
|
10
|
-
maps4fs/generator/i3d.py,sha256=
|
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
11
|
maps4fs/generator/map.py,sha256=1EJfq5928xbV7v9vBBuoC3VpD5gC1SMDBTQ7geH6yaA,4678
|
12
12
|
maps4fs/generator/path_steps.py,sha256=yeN6hmOD8O2LMozUf4HcQMIy8WJ5sHF5pGQT_s2FfOw,3147
|
13
13
|
maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
|
14
|
-
maps4fs/generator/texture.py,sha256=
|
14
|
+
maps4fs/generator/texture.py,sha256=uSuqJyZom861_BO-3aUqU8_OkroExmN25PsRBjdddkw,23799
|
15
15
|
maps4fs/generator/tile.py,sha256=6zUpDidPcPRTGzQvYSNw-0Pj-if2WiakU2qb-40vyOk,2151
|
16
16
|
maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
|
17
17
|
maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
|
18
|
-
maps4fs-0.9.
|
19
|
-
maps4fs-0.9.
|
20
|
-
maps4fs-0.9.
|
21
|
-
maps4fs-0.9.
|
22
|
-
maps4fs-0.9.
|
18
|
+
maps4fs-0.9.99.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
|
19
|
+
maps4fs-0.9.99.dist-info/METADATA,sha256=e23P4VVbfJ9L9OvW0k1j9ppNIRDGAMUBW623o1n9-zM,25393
|
20
|
+
maps4fs-0.9.99.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
21
|
+
maps4fs-0.9.99.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
|
22
|
+
maps4fs-0.9.99.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|