maps4fs 1.6.4__py3-none-any.whl → 1.6.6__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.

Potentially problematic release.


This version of maps4fs might be problematic. Click here for more details.

@@ -58,6 +58,10 @@ class Background(Component):
58
58
  os.makedirs(self.water_directory, exist_ok=True)
59
59
 
60
60
  self.output_path = os.path.join(self.background_directory, f"{FULL_NAME}.png")
61
+ if self.map.custom_background_path:
62
+ self.check_custom_background(self.map.custom_background_path)
63
+ shutil.copyfile(self.map.custom_background_path, self.output_path)
64
+
61
65
  self.not_substracted_path = os.path.join(self.background_directory, "not_substracted.png")
62
66
  self.not_resized_path = os.path.join(self.background_directory, "not_resized.png")
63
67
 
@@ -75,6 +79,28 @@ class Background(Component):
75
79
  self.dem.set_output_resolution((self.rotated_size, self.rotated_size))
76
80
  self.dem.set_dem_path(self.output_path)
77
81
 
82
+ def check_custom_background(self, image_path: str) -> None:
83
+ """Checks if the custom background image meets the requirements.
84
+
85
+ Arguments:
86
+ image_path (str): The path to the custom background image.
87
+
88
+ Raises:
89
+ ValueError: If the custom background image does not meet the requirements.
90
+ """
91
+ image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member
92
+ if image.shape[0] != image.shape[1]:
93
+ raise ValueError("The custom background image must be a square.")
94
+
95
+ if image.shape[0] != self.map_size + DEFAULT_DISTANCE * 2:
96
+ raise ValueError("The custom background image must have the size of the map + 4096.")
97
+
98
+ if len(image.shape) != 2:
99
+ raise ValueError("The custom background image must be a grayscale image.")
100
+
101
+ if image.dtype != np.uint16:
102
+ raise ValueError("The custom background image must be a 16-bit grayscale image.")
103
+
78
104
  def is_preview(self, name: str) -> bool:
79
105
  """Checks if the DEM is a preview.
80
106
 
@@ -91,7 +117,9 @@ class Background(Component):
91
117
  as a result the DEM files will be saved, then based on them the obj files will be
92
118
  generated."""
93
119
  self.create_background_textures()
94
- self.dem.process()
120
+
121
+ if not self.map.custom_background_path:
122
+ self.dem.process()
95
123
 
96
124
  shutil.copyfile(self.dem.dem_path, self.not_substracted_path)
97
125
  self.cutout(self.dem.dem_path, save_path=self.not_resized_path)
maps4fs/generator/map.py CHANGED
@@ -89,6 +89,17 @@ class Map:
89
89
 
90
90
  self.dem_settings = dem_settings
91
91
  self.logger.info("DEM settings: %s", dem_settings)
92
+ if self.dem_settings.water_depth > 0:
93
+ # Make sure that the plateau value is >= water_depth
94
+ self.dem_settings.plateau = max(
95
+ self.dem_settings.plateau, self.dem_settings.water_depth
96
+ )
97
+ self.logger.info(
98
+ "Plateau value was set to %s to be >= water_depth value %s",
99
+ self.dem_settings.plateau,
100
+ self.dem_settings.water_depth,
101
+ )
102
+
92
103
  self.background_settings = background_settings
93
104
  self.logger.info("Background settings: %s", background_settings)
94
105
  self.grle_settings = grle_settings
@@ -140,6 +151,11 @@ class Map:
140
151
  json.dump(self.tree_custom_schema, file, indent=4)
141
152
  self.logger.debug("Tree custom schema saved to %s", save_path)
142
153
 
154
+ self.custom_background_path = kwargs.get("custom_background_path", None)
155
+ if self.custom_background_path:
156
+ save_path = os.path.join(self.map_directory, "custom_background.png")
157
+ shutil.copyfile(self.custom_background_path, save_path)
158
+
143
159
  try:
144
160
  shutil.unpack_archive(game.template_path, self.map_directory)
145
161
  self.logger.debug("Map template unpacked to %s", self.map_directory)
@@ -23,6 +23,10 @@ class SharedSettings(BaseModel):
23
23
  class SettingsModel(BaseModel):
24
24
  """Base class for settings models. It provides methods to convert settings to and from JSON."""
25
25
 
26
+ model_config = ConfigDict(
27
+ frozen=False,
28
+ )
29
+
26
30
  @classmethod
27
31
  def all_settings_to_json(cls) -> dict[str, dict[str, Any]]:
28
32
  """Get all settings of the current class and its subclasses as a dictionary.
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import os
7
7
  import re
8
+ import shutil
8
9
  from collections import defaultdict
9
10
  from typing import Any, Callable, Generator, Optional
10
11
 
@@ -69,6 +70,7 @@ class Texture(Component):
69
70
  usage: str | None = None,
70
71
  background: bool = False,
71
72
  invisible: bool = False,
73
+ procedural: list[str] | None = None,
72
74
  ):
73
75
  self.name = name
74
76
  self.count = count
@@ -81,6 +83,7 @@ class Texture(Component):
81
83
  self.usage = usage
82
84
  self.background = background
83
85
  self.invisible = invisible
86
+ self.procedural = procedural
84
87
 
85
88
  def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
86
89
  """Returns dictionary with layer data.
@@ -99,6 +102,7 @@ class Texture(Component):
99
102
  "usage": self.usage,
100
103
  "background": self.background,
101
104
  "invisible": self.invisible,
105
+ "procedural": self.procedural,
102
106
  }
103
107
 
104
108
  data = {k: v for k, v in data.items() if v is not None}
@@ -212,6 +216,10 @@ class Texture(Component):
212
216
 
213
217
  self._weights_dir = self.game.weights_dir_path(self.map_directory)
214
218
  self.logger.debug("Weights directory: %s.", self._weights_dir)
219
+ self.procedural_dir = os.path.join(self._weights_dir, "masks")
220
+ os.makedirs(self.procedural_dir, exist_ok=True)
221
+ self.logger.debug("Procedural directory: %s.", self.procedural_dir)
222
+
215
223
  self.info_save_path = os.path.join(self.map_directory, "generation_info.json")
216
224
  self.logger.debug("Generation info save path: %s.", self.info_save_path)
217
225
 
@@ -251,11 +259,56 @@ class Texture(Component):
251
259
  return layer
252
260
  return None
253
261
 
254
- def process(self):
262
+ def process(self) -> None:
263
+ """Processes the data to generate textures."""
255
264
  self._prepare_weights()
256
265
  self._read_parameters()
257
266
  self.draw()
258
267
  self.rotate_textures()
268
+ self.copy_procedural()
269
+
270
+ def copy_procedural(self) -> None:
271
+ """Copies some of the textures to use them as mask for procedural generation.
272
+ Creates an empty blockmask if it does not exist."""
273
+ blockmask_path = os.path.join(self.procedural_dir, "BLOCKMASK.png")
274
+ if not os.path.isfile(blockmask_path):
275
+ self.logger.debug("BLOCKMASK.png not found, creating an empty file.")
276
+ img = np.zeros((self.map_size, self.map_size), dtype=np.uint8)
277
+ cv2.imwrite(blockmask_path, img) # pylint: disable=no-member
278
+
279
+ pg_layers_by_type = defaultdict(list)
280
+ for layer in self.layers:
281
+ if layer.procedural:
282
+ # Get path to the original file.
283
+ texture_path = layer.get_preview_or_path(self._weights_dir)
284
+ for procedural_layer_name in layer.procedural:
285
+ pg_layers_by_type[procedural_layer_name].append(texture_path)
286
+
287
+ if not pg_layers_by_type:
288
+ self.logger.debug("No procedural layers found.")
289
+ return
290
+
291
+ for procedural_layer_name, texture_paths in pg_layers_by_type.items():
292
+ procedural_save_path = os.path.join(self.procedural_dir, f"{procedural_layer_name}.png")
293
+ if len(texture_paths) > 1:
294
+ # If there are more than one texture, merge them.
295
+ merged_texture = np.zeros((self.map_size, self.map_size), dtype=np.uint8)
296
+ for texture_path in texture_paths:
297
+ # pylint: disable=E1101
298
+ texture = cv2.imread(texture_path, cv2.IMREAD_UNCHANGED)
299
+ merged_texture[texture == 255] = 255
300
+ cv2.imwrite(procedural_save_path, merged_texture) # pylint: disable=no-member
301
+ self.logger.debug(
302
+ "Procedural file %s merged from %s textures.",
303
+ procedural_save_path,
304
+ len(texture_paths),
305
+ )
306
+ elif len(texture_paths) == 1:
307
+ # Otherwise, copy the texture.
308
+ shutil.copyfile(texture_paths[0], procedural_save_path)
309
+ self.logger.debug(
310
+ "Procedural file %s copied from %s.", procedural_save_path, texture_paths[0]
311
+ )
259
312
 
260
313
  def rotate_textures(self) -> None:
261
314
  """Rotates textures of the layers which have tags."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maps4fs
3
- Version: 1.6.4
3
+ Version: 1.6.6
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
@@ -131,6 +131,7 @@ docker run -d -p 8501:8501 --name maps4fs iwatkot/maps4fs
131
131
  ```
132
132
  And open [http://localhost:8501](http://localhost:8501) in your browser.<br>
133
133
  If you don't know how to use Docker, navigate to the [Docker version](#option-2-docker-version), it's really simple.<br>
134
+ Check out the [Docker FAQ](docs/FAQ_docker.md) if you have any questions.<br>
134
135
 
135
136
  ### 🤯 For developers
136
137
  **Option 3:** Python package. Install the package using the following command:
@@ -185,6 +186,7 @@ Using it is easy and doesn't require any guides. Enjoy!
185
186
  🗺️ Supported map sizes: 2x2, 4x4, 8x8, 16x16 km and any custom size.
186
187
  ⚙️ Advanced settings: enabled.
187
188
  🖼️ Texture dissolving: enabled.
189
+ Check out the [Docker FAQ](docs/FAQ_docker.md) if you have any questions.<br>
188
190
  You can launch the project with minimalistic UI in your browser using Docker. Follow these steps:
189
191
 
190
192
  1. Install [Docker](https://docs.docker.com/get-docker/) for your OS.
@@ -421,6 +423,7 @@ Let's have a closer look at the fields:
421
423
  - `background` - set it to True for the textures, which should have impact on the Background Terrain, by default it's used to subtract the water depth from the DEM and background terrain.
422
424
  - `info_layer` - if the layer is saving some data in JSON format, this section will describe it's name in the JSON file. Used to find the needed JSON data, for example for fields it will be `fields` and as a value - list of polygon coordinates.
423
425
  - `invisible` - set it to True for the textures, which should not be drawn in the files, but only to save the data in the JSON file (related to the previous field).
426
+ - `procedural` - is a list of corresponding files, that will be used for a procedural generation. For example: `"procedural": ["PG_meadow", "PG_acres"]` - means that the texture will be used for two procedural generation files: `masks/PG_meadow.png` and `masks/PG_acres.png`. Note, that the one procuderal name can be applied to multiple textures, in this case they will be merged into one mask.
424
427
 
425
428
  ## Background terrain
426
429
  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 that can (and should) be textured to look fine.<br>
@@ -472,7 +475,7 @@ You can also apply some advanced settings to the map generation process.<br>
472
475
 
473
476
  ### DEM Advanced settings
474
477
 
475
- - Multiplier: the height of the map is multiplied by this value. So the DEM map is just a 16-bit grayscale image, which means that the maximum available value there is 65535, while the actual difference between the deepest and the highest point on Earth is about 20 km. Just note that this setting mostly does not matter, because you can always adjust it in the Giants Editor, learn more about the DEM file and the heightScale parameter in [docs](docs/dem.md). By default, it's set to 1.
478
+ - Multiplier: the height of the map is multiplied by this value. So the DEM map is just a 16-bit grayscale image, which means that the maximum available value there is 65535, while the actual difference between the deepest and the highest point on Earth is about 20 km. Just note that this setting mostly does not matter, because you can always adjust it in the Giants Editor, learn more about the DEM file and the heightScale parameter in [docs](docs/dem.md). To match the in-game heights with SRTM Data provider, the recommended value is 255 (if easy mode is disabled), but depending on the place, you will need to play with both multiplier and the height scale in Giants Editor to find the best values.
476
479
 
477
480
  - Blur radius: the radius of the Gaussian blur filter applied to the DEM map. By default, it's set to 21. This filter just makes the DEM map smoother, so the height transitions will be more natural. You can set it to 1 to disable the filter, but it will result in a Minecraft-like map.
478
481
 
@@ -538,6 +541,8 @@ The tool also supports the expert settings. Do not use them until you read the d
538
541
 
539
542
  - Show schemas - you'll be able to edit or define your own texture or tree schemas. It's useful if you want to add some custom textures or trees to the map. Refer to the [Texture schema](#texture-schema) section to learn more about the schema structure. Any incorrect value here will lead to the completely broken map.
540
543
 
544
+ - Upload custom background image - if you have an image, which represents the map and background terrain you can use it for generation. Note, that the image should meet the following requirements: 1:1 aspect ratio, size = map size + 2048 * 2, it should be uint16 (unsigned 16-bit integer) grayscale (single channel) image. The image should be in the PNG format. If any of the requirements are not met, the tool raises an error. If you're using rotation, the image should already be rotated.
545
+
541
546
  ## Resources
542
547
  In this section, you'll find a list of the resources that you need to create a map for the Farming Simulator.<br>
543
548
  To create a basic map, you only need the Giants Editor. But if you want to create a background terrain - the world around the map, so it won't look like it's floating in the void - you also need Blender and the Blender Exporter Plugins. To create realistic textures for the background terrain, the QGIS is required to obtain high-resolution satellite images.<br>
@@ -1,18 +1,18 @@
1
1
  maps4fs/__init__.py,sha256=WbT36EzJ_74GN0RUUrLIYECdSdtRiZaxKl17KUt7pjA,492
2
2
  maps4fs/logger.py,sha256=B-NEYpMjPAAqlV4VpfTi6nbBFnEABVtQOaYe6nMpidg,1489
3
3
  maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/background.py,sha256=JZNSiPZzA6_Kihu83qdCbsJc9bGeeSBY0SP6jTtdvU4,24595
4
+ maps4fs/generator/background.py,sha256=K2HSYENPtJNSlG9M3XGkvUjtYyyI3oWwLMNYPpY3IBQ,25824
5
5
  maps4fs/generator/component.py,sha256=GrTI7803gOQFhqocdjFG-wh0HOkC6HWoyKr8pR2Xp28,20409
6
6
  maps4fs/generator/config.py,sha256=0QmK052B8bxyHVhg3jzCORLfOBMMmqVfhhbqXKf6OMk,4383
7
7
  maps4fs/generator/dem.py,sha256=20gx0dzX0LyO6ipvDitst-BwGfcKogFqgQf9Q2qMH5U,10933
8
8
  maps4fs/generator/game.py,sha256=QHgVnyGYvEnfwGZ84-u-dpbCRr3UeVVqBbrwr5WG8dE,7992
9
9
  maps4fs/generator/grle.py,sha256=rU84Q1PBHr-V-JzdHJ7BXLHC_LztGw6Z1FS2w_1HIF0,17848
10
10
  maps4fs/generator/i3d.py,sha256=D1QBHCFygTkml6GZmLRlUagWEVQb0tNeyZ-MAY-Uf0Q,24945
11
- maps4fs/generator/map.py,sha256=8CYUs7NNVoQBvABqtoKtnbj280JuvseNORDCsebkQ_c,9357
11
+ maps4fs/generator/map.py,sha256=P8wHrCLhLcv2W5zJmMGjpM1TAMR8c7yVFzm_n-5ZTHQ,10084
12
12
  maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
13
13
  maps4fs/generator/satellite.py,sha256=_7RcuNmR1mjxEJWMDsjnzKUIqWxnGUn50XtjB7HmSPg,3661
14
- maps4fs/generator/settings.py,sha256=zPdewt348ulparOAlWo-TmfyF77bm1567fcliL6YP6s,4951
15
- maps4fs/generator/texture.py,sha256=C3gjAd5yAF0uw7QP3hU2In9iHAXg-oXzYAuBVWd3FD8,31135
14
+ maps4fs/generator/settings.py,sha256=TGavqPG-sULpB7_SeOzFZ5mWZUq8d2sHfVu5aBbmx5E,5011
15
+ maps4fs/generator/texture.py,sha256=jWorYVOzBI-plzy_mh42O2NZuB13M5yDaCOfrpX6Dck,33836
16
16
  maps4fs/generator/dtm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  maps4fs/generator/dtm/dtm.py,sha256=azy-RWsc5PgenKDtgG0lrddMwWEw1hYzdng9V8zphMk,9167
18
18
  maps4fs/generator/dtm/srtm.py,sha256=2-pX6bWrJX6gr8IM7ueX6mm_PW7_UQ58MtdzDHae2OQ,9030
@@ -20,8 +20,8 @@ maps4fs/generator/dtm/usgs.py,sha256=ZTi10RNDA3EBrsVg2ZoYrdN4uqiG1Jvk7FzdcKdgNkU
20
20
  maps4fs/toolbox/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
21
21
  maps4fs/toolbox/background.py,sha256=9BXWNqs_n3HgqDiPztWylgYk_QM4YgBpe6_ZNQAWtSc,2154
22
22
  maps4fs/toolbox/dem.py,sha256=z9IPFNmYbjiigb3t02ZenI3Mo8odd19c5MZbjDEovTo,3525
23
- maps4fs-1.6.4.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
- maps4fs-1.6.4.dist-info/METADATA,sha256=huMhHVyVy7sduTij2eOj0GAAVxSCgUSX4unrmkEeW-s,36425
25
- maps4fs-1.6.4.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
26
- maps4fs-1.6.4.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
- maps4fs-1.6.4.dist-info/RECORD,,
23
+ maps4fs-1.6.6.dist-info/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
+ maps4fs-1.6.6.dist-info/METADATA,sha256=REJVmAJBVXkg5_Gk-3BpU4JbEol1pewc4_AZeWsb6fw,37687
25
+ maps4fs-1.6.6.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
26
+ maps4fs-1.6.6.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
+ maps4fs-1.6.6.dist-info/RECORD,,