maps4fs 2.1.0__py3-none-any.whl → 2.1.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.
@@ -265,5 +265,5 @@ class MeshComponent(Component):
265
265
  trimesh.Trimesh: A new mesh with inverted faces.
266
266
  """
267
267
  mesh_copy = mesh.copy()
268
- mesh_copy.faces = mesh_copy.faces[:, ::-1]
268
+ mesh_copy.faces = mesh_copy.faces[:, ::-1] # type: ignore
269
269
  return mesh_copy
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import os
7
7
  from random import choice, randint, uniform
8
- from typing import Generator
8
+ from typing import Any, Generator
9
9
  from xml.etree import ElementTree as ET
10
10
 
11
11
  import cv2
@@ -48,6 +48,9 @@ class I3d(XMLComponent):
48
48
  attribute. If the game does not support I3D files, the attribute is set to None."""
49
49
  self.xml_path = self.game.i3d_file_path(self.map_directory)
50
50
 
51
+ self.forest_info: dict[str, Any] = {}
52
+ self.field_info: dict[str, Any] = {}
53
+
51
54
  def process(self) -> None:
52
55
  """Updates the map I3D file and creates splines in a separate I3D file."""
53
56
  self.update_height_scale()
@@ -267,6 +270,8 @@ class I3d(XMLComponent):
267
270
 
268
271
  node_id = NODE_ID_STARTING_VALUE
269
272
  field_id = 1
273
+ added_fields = skipped_fields = 0
274
+ skipped_field_ids: list[int] = []
270
275
 
271
276
  for field in tqdm(fields, desc="Adding fields", unit="field"):
272
277
  try:
@@ -279,6 +284,7 @@ class I3d(XMLComponent):
279
284
  field_id,
280
285
  e,
281
286
  )
287
+ skipped_fields += 1
282
288
  continue
283
289
 
284
290
  field_ccs = [self.top_left_coordinates_to_center(point) for point in fitted_field]
@@ -297,6 +303,11 @@ class I3d(XMLComponent):
297
303
 
298
304
  node_id += 1
299
305
  field_id += 1
306
+ added_fields += 1
307
+
308
+ self.field_info["added_fields"] = added_fields
309
+ self.field_info["skipped_fields"] = skipped_fields
310
+ self.field_info["skipped_field_ids"] = skipped_field_ids
300
311
 
301
312
  self.save_tree(tree)
302
313
 
@@ -504,6 +515,8 @@ class I3d(XMLComponent):
504
515
 
505
516
  node_id = TREE_NODE_ID_STARTING_VALUE
506
517
 
518
+ tree_count = 0
519
+
507
520
  for forest_layer in forest_layers:
508
521
  weights_directory = self.game.weights_dir_path(self.map_directory)
509
522
  forest_image_path = forest_layer.get_preview_or_path(weights_directory)
@@ -542,12 +555,17 @@ class I3d(XMLComponent):
542
555
  )
543
556
 
544
557
  forest_image = cv2.imread(forest_image_path, cv2.IMREAD_UNCHANGED)
545
- for x, y in self.non_empty_pixels(
546
- forest_image, step=self.map.i3d_settings.forest_density # type: ignore
547
- ):
558
+
559
+ step = self.get_step_by_limit(
560
+ forest_image, # type: ignore
561
+ self.map.i3d_settings.tree_limit,
562
+ self.map.i3d_settings.forest_density,
563
+ )
564
+
565
+ for x, y in self.non_empty_pixels(forest_image, step=step): # type: ignore
548
566
  shifted_x, shifted_y = self.randomize_coordinates(
549
567
  (x, y),
550
- self.map.i3d_settings.forest_density,
568
+ step,
551
569
  self.map.i3d_settings.trees_relative_shift,
552
570
  )
553
571
 
@@ -573,9 +591,16 @@ class I3d(XMLComponent):
573
591
  }
574
592
  trees_node.append(self.create_element("ReferenceNode", data))
575
593
 
594
+ tree_count += 1
595
+
576
596
  scene_node.append(trees_node)
577
597
  self.save_tree(tree)
578
598
 
599
+ self.forest_info["tree_count"] = tree_count
600
+ self.forest_info["tree_limit"] = self.map.i3d_settings.tree_limit
601
+ self.forest_info["initial_step"] = self.map.i3d_settings.forest_density
602
+ self.forest_info["actual_step"] = step
603
+
579
604
  self.assets.forests = self.xml_path
580
605
 
581
606
  @staticmethod
@@ -606,19 +631,61 @@ class I3d(XMLComponent):
606
631
  image: np.ndarray, step: int = 1
607
632
  ) -> Generator[tuple[int, int], None, None]:
608
633
  """Receives numpy array, which represents single-channeled image of uint8 type.
609
- Yield coordinates of non-empty pixels (pixels with value greater than 0).
634
+ Yield coordinates of non-empty pixels (pixels with value greater than 0), sampling about 1/step of them.
610
635
 
611
636
  Arguments:
612
637
  image (np.ndarray): The image to get non-empty pixels from.
613
- step (int, optional): The step to iterate through the image. Defaults to 1.
638
+ step (int, optional): The step to sample non-empty pixels. Defaults to 1.
614
639
 
615
640
  Yields:
616
641
  tuple[int, int]: The coordinates of non-empty pixels.
617
642
  """
618
- for y, row in enumerate(image[::step]):
619
- for x, value in enumerate(row[::step]):
643
+ count = 0
644
+ for y, row in enumerate(image):
645
+ for x, value in enumerate(row):
620
646
  if value > 0:
621
- yield x * step, y * step
647
+ if count % step == 0:
648
+ yield x, y
649
+ count += 1
650
+
651
+ @staticmethod
652
+ def non_empty_pixels_count(image: np.ndarray) -> int:
653
+ """Counts the number of non-empty pixels in the image.
654
+
655
+ Arguments:
656
+ image (np.ndarray): The image to count non-empty pixels in.
657
+
658
+ Returns:
659
+ int: The number of non-empty pixels in the image.
660
+ """
661
+ result = np.count_nonzero(image > 0)
662
+ return result
663
+
664
+ def get_step_by_limit(
665
+ self, image: np.ndarray, limit: int, current_step: int | None = None
666
+ ) -> int:
667
+ """Calculates the step size for iterating through the image based on the limit based
668
+ on the number of non-empty pixels in the image.
669
+
670
+ Arguments:
671
+ image (np.ndarray): The image to calculate the step size for.
672
+ limit (int): The maximum number of non-empty pixels to process.
673
+ current_step (int | None, optional): The current step size. If provided, the method
674
+ will return the maximum of the recommended step and the current step.
675
+
676
+ Returns:
677
+ int: The recommended step size for iterating through the image.
678
+ """
679
+ available_tree_count = self.non_empty_pixels_count(image)
680
+ self.forest_info["available_tree_count"] = available_tree_count
681
+ if limit <= 0 or available_tree_count <= limit:
682
+ recommended_step = 1
683
+ else:
684
+ recommended_step = int(available_tree_count / limit)
685
+
686
+ self.forest_info["step_by_limit"] = recommended_step
687
+
688
+ return recommended_step if not current_step else max(recommended_step, current_step)
622
689
 
623
690
  def get_not_resized_dem(self) -> np.ndarray | None:
624
691
  """Reads the not resized DEM image from the background component.
@@ -638,3 +705,16 @@ class I3d(XMLComponent):
638
705
  not_resized_dem = cv2.imread(background_component.not_resized_path, cv2.IMREAD_UNCHANGED)
639
706
 
640
707
  return not_resized_dem
708
+
709
+ def info_sequence(self) -> dict[str, dict[str, str | float | int]]:
710
+ """Returns information about the component.
711
+
712
+ Returns:
713
+ dict[str, dict[str, str | float | int]]: Information about the component.
714
+ """
715
+ data = {
716
+ "Forests": self.forest_info,
717
+ "Fields": self.field_info,
718
+ }
719
+
720
+ return data
@@ -203,6 +203,7 @@ class I3DSettings(SettingsModel):
203
203
  Attributes:
204
204
  add_trees (bool): add trees to the map.
205
205
  forest_density (int): density of the forest (distance between trees).
206
+ tree_limit (int): maximum number of trees to be added to the map.
206
207
  trees_relative_shift (int): relative shift of the trees.
207
208
  spline_density (int): the number of extra points that will be added between each two
208
209
  existing points.
@@ -212,6 +213,7 @@ class I3DSettings(SettingsModel):
212
213
 
213
214
  add_trees: bool = True
214
215
  forest_density: int = 10
216
+ tree_limit: int = 0
215
217
  trees_relative_shift: int = 20
216
218
 
217
219
  spline_density: int = 2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Generate map templates for Farming Simulator from real places.
5
5
  Author-email: iwatkot <iwatkot@gmail.com>
6
6
  License: Apache License 2.0
@@ -650,6 +650,8 @@ You can also apply some advanced settings to the map generation process.<br>
650
650
 
651
651
  - Forest density - the density of the forest in meters. The lower the value, the lower the distance between the trees, which makes the forest denser. Note, that low values will lead to enormous number of trees, which may cause the Giants Editor to crash or lead to performance issues. By default, it's set to 10.
652
652
 
653
+ - Tree limit - this value will be used to adjust the forest density value. For example, if it's possible to place 100000 trees from OSM data, and the forest density is set to 10, the expected number of trees on map will be 10000. At the same time, if you set the tree limit to 5000, the forest density will be adjusted to 20, which means that the distance between the trees will be doubled. This value is useful to prevent the Giants Editor from crashing due to too many trees on the map. By default, it's set to 0, which means that it's disabled and will use the forest density value as is. Note, that it will not lead to the exact number of trees, but will adjust the forest density value to fit the tree limit so the resulting number of trees will be more or less equal to the tree limit value.
654
+
653
655
  - Trees relative shift - represents the maximum possible shift of the tree from it's original position in percents of the forest density value. The higher the value, the more the trees will be shifted from their original positions. Warning: higher values can lead to overlapping trees.
654
656
 
655
657
  - Splines density - number of points, which will be added (interpolate) between each pair of existing points. The higher the value, the denser the spline will be. It can smooth the splines, but high values can in opposite make the splines look unnatural.
@@ -4,24 +4,24 @@ maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk
4
4
  maps4fs/generator/game.py,sha256=Nl8xqEwW-E04l2D3e9ff7kkzPcolr1ndnXxQJlLMBDM,12627
5
5
  maps4fs/generator/map.py,sha256=x_NjUwKkDnh2cC61QpTfX88mFHAkQG1sxVwZckoV0mE,16357
6
6
  maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
7
- maps4fs/generator/settings.py,sha256=FIE02u2gWwo1WC3rUIZmQziC994dhDH38HflLrXT_DM,7578
7
+ maps4fs/generator/settings.py,sha256=OXgCjpmcqvTENRcKKYcEhES3GD3KPpvrbeJDv8g25yY,7676
8
8
  maps4fs/generator/statistics.py,sha256=aynS3zbAtiwnU_YLKHPTiiaKW98_suvQUhy1SGBA6mc,2448
9
9
  maps4fs/generator/component/__init__.py,sha256=s01yVVVi8R2xxNvflu2D6wTd9I_g73AMM2x7vAC7GX4,490
10
10
  maps4fs/generator/component/background.py,sha256=wqTgndTfRIGlLMpMXqJMw_6eVdH1JtMei46ZIXlT9X4,29480
11
11
  maps4fs/generator/component/config.py,sha256=uL76h9UwyhZKZmbxz0mBmWtEPN6qYay4epTEqqtej60,8601
12
12
  maps4fs/generator/component/dem.py,sha256=SH_2Zu5O4dhWtZeOkCwzDF4RU04XhTdpGFYaRYJkdjc,11905
13
13
  maps4fs/generator/component/grle.py,sha256=8K32pC_ar9CR6p0EhCe2X--wEoIxFzJCPcN9ydHQ1LE,19747
14
- maps4fs/generator/component/i3d.py,sha256=B0-YTeiIkon2v7OLFNMEw38ouuuu4G54WbYIQm6I5a8,23798
14
+ maps4fs/generator/component/i3d.py,sha256=L-QAbr3Z7Ye5N0BeS_qvY9bqYxYs0eVnRCGWp77etrE,26693
15
15
  maps4fs/generator/component/layer.py,sha256=-br4gAGcGeBNb3ldch9XFEK0lhXqb1IbArhFB5Owu54,6186
16
16
  maps4fs/generator/component/satellite.py,sha256=OsxoNOCgkUtRzL7Geuqubsf6uoKXAIN8jQvrJ7IFeAI,4958
17
17
  maps4fs/generator/component/texture.py,sha256=krtvOS0hH8BTzfxd2jsTo3bIJYRkIVbaz6FGhWY8L1o,33921
18
18
  maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
19
19
  maps4fs/generator/component/base/component.py,sha256=Vgmdsn1ZC37EwWi4Va4uYVt0RnFLiARTtZ-R5GTSrrM,22877
20
20
  maps4fs/generator/component/base/component_image.py,sha256=2NYJgCU8deHl7O2FYFYk38WKZVJygFoc2gjBXwH6vjM,5970
21
- maps4fs/generator/component/base/component_mesh.py,sha256=lZuF-9hzDYodL4Ub4RgvFjsozxTPZJrOaLQmlNTrZ7I,9074
21
+ maps4fs/generator/component/base/component_mesh.py,sha256=_thzgjJDroMn-9SBsBmAWizcSsnV9U5445SD18Tx1kc,9090
22
22
  maps4fs/generator/component/base/component_xml.py,sha256=MT-VhU2dEckLFxAgmxg6V3gnv11di_94Qq6atfpOLdc,5342
23
- maps4fs-2.1.0.dist-info/licenses/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
- maps4fs-2.1.0.dist-info/METADATA,sha256=-_6ei79gfvcNStmIBB3iu8zlf4Y39EOZWp1pvvIQdwI,44135
25
- maps4fs-2.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- maps4fs-2.1.0.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
- maps4fs-2.1.0.dist-info/RECORD,,
23
+ maps4fs-2.1.1.dist-info/licenses/LICENSE.md,sha256=pTKD_oUexcn-yccFCTrMeLkZy0ifLRa-VNcDLqLZaIw,10749
24
+ maps4fs-2.1.1.dist-info/METADATA,sha256=uHY1Ap5uahWTaFPIAOulIBz754lJ7Ga4S4RJ6ZOAUpI,44934
25
+ maps4fs-2.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ maps4fs-2.1.1.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
27
+ maps4fs-2.1.1.dist-info/RECORD,,