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

@@ -823,6 +823,10 @@ class Background(MeshComponent, ImageComponent):
823
823
 
824
824
  if self.map.background_settings.flatten_water:
825
825
  try:
826
+ # Check if there are any water pixels (255) in the water resources image.
827
+ if not np.any(water_resources_image == 255):
828
+ self.logger.warning("No water pixels found in water resources image.")
829
+ return
826
830
  mask = water_resources_image == 255
827
831
  flatten_to = int(np.mean(dem_image[mask]) - subtract_by) # type: ignore
828
832
  self.flatten_water_to = flatten_to # type: ignore
@@ -862,13 +866,14 @@ class Background(MeshComponent, ImageComponent):
862
866
  """
863
867
  self.logger.debug("Starting line-based water generation...")
864
868
  water_polygons = self.get_infolayer_data(Parameters.BACKGROUND, Parameters.WATER)
865
- self.logger.debug(
866
- "Found %s water polygons in background info layer.", len(water_polygons) # type: ignore
867
- )
868
869
  if not water_polygons:
869
870
  self.logger.warning("No water polygons found in background info layer.")
870
871
  return
871
872
 
873
+ self.logger.debug(
874
+ "Found %s water polygons in background info layer.", len(water_polygons) # type: ignore
875
+ )
876
+
872
877
  polygons: list[shapely.Polygon] = []
873
878
  for polygon_points in water_polygons:
874
879
  if not polygon_points or len(polygon_points) < 2:
@@ -14,8 +14,9 @@ from maps4fs.generator.settings import Parameters
14
14
  from maps4fs.generator.utils import get_region_by_coordinates
15
15
 
16
16
  BUILDINGS_STARTING_NODE_ID = 10000
17
- TOLERANCE_FACTOR = 0.3 # 30% size tolerance
18
17
  DEFAULT_HEIGHT = 200
18
+ AUTO_REGION = "auto"
19
+ ALL_REGIONS = "all"
19
20
 
20
21
 
21
22
  AREA_TYPES = {
@@ -45,16 +46,25 @@ class BuildingEntry(NamedTuple):
45
46
  class BuildingEntryCollection:
46
47
  """Collection of building entries with efficient lookup capabilities."""
47
48
 
48
- def __init__(self, building_entries: list[BuildingEntry], region: str):
49
+ def __init__(
50
+ self, building_entries: list[BuildingEntry], region: str, ignore_region: bool = False
51
+ ):
49
52
  """Initialize the collection with a list of building entries for a specific region.
50
53
 
51
54
  Arguments:
52
55
  building_entries (list[BuildingEntry]): List of building entries to manage
53
56
  region (str): The region for this collection (filters entries to this region only)
57
+ ignore_region (bool): If True, ignore region filtering and use all entries
54
58
  """
55
59
  self.region = region
56
- # Filter entries to only include the specified region
57
- self.entries = [entry for entry in building_entries if region in entry.regions]
60
+ self.ignore_region = ignore_region
61
+
62
+ # Filter entries based on ignore_region flag
63
+ if ignore_region:
64
+ self.entries = building_entries # Use all entries regardless of region
65
+ else:
66
+ self.entries = [entry for entry in building_entries if region in entry.regions]
67
+
58
68
  # Create indices for faster lookup
59
69
  self._create_indices()
60
70
 
@@ -77,7 +87,7 @@ class BuildingEntryCollection:
77
87
  tolerance: float = 0.3,
78
88
  ) -> BuildingEntry | None:
79
89
  """Find the best matching building entry based on criteria.
80
- All entries are already filtered by region during initialization.
90
+ Entries are filtered by region during initialization unless ignore_region is True.
81
91
 
82
92
  Arguments:
83
93
  category (str): Required building category
@@ -88,7 +98,7 @@ class BuildingEntryCollection:
88
98
  Returns:
89
99
  BuildingEntry | None: Best matching entry or None if no suitable match found
90
100
  """
91
- # Start with buildings of the required category (already filtered by region)
101
+ # Start with buildings of the required category (filtered by region unless ignore_region is True)
92
102
  candidates = self.by_category.get(category, [])
93
103
  if not candidates:
94
104
  return None
@@ -107,6 +117,91 @@ class BuildingEntryCollection:
107
117
  scored_candidates.sort(key=lambda x: x[0], reverse=True)
108
118
  return scored_candidates[0][1]
109
119
 
120
+ def find_best_match_with_orientation(
121
+ self,
122
+ category: str,
123
+ width: float | None = None,
124
+ depth: float | None = None,
125
+ tolerance: float = 0.3,
126
+ ) -> tuple[BuildingEntry | None, bool]:
127
+ """Find the best matching building entry and determine if rotation is needed.
128
+
129
+ Arguments:
130
+ category (str): Required building category
131
+ width (float | None): Desired width (optional)
132
+ depth (float | None): Desired depth (optional)
133
+ tolerance (float): Size tolerance factor (0.3 = 30% tolerance)
134
+
135
+ Returns:
136
+ tuple[BuildingEntry | None, bool]: Best matching entry and whether it needs 90° rotation
137
+ """
138
+ # Start with buildings of the required category
139
+ candidates = self.by_category.get(category, [])
140
+ if not candidates:
141
+ return None, False
142
+
143
+ # Score each candidate and track orientation
144
+ scored_candidates = []
145
+ for entry in candidates:
146
+ score, needs_rotation = self._calculate_match_score_with_orientation(
147
+ entry, category, width, depth, tolerance
148
+ )
149
+ if score > 0: # Only consider viable matches
150
+ scored_candidates.append((score, entry, needs_rotation))
151
+
152
+ if not scored_candidates:
153
+ return None, False
154
+
155
+ # Return the highest scoring match with its orientation info
156
+ scored_candidates.sort(key=lambda x: x[0], reverse=True)
157
+ _, best_entry, needs_rotation = scored_candidates[0]
158
+ return best_entry, needs_rotation
159
+
160
+ def _calculate_match_score_with_orientation(
161
+ self,
162
+ entry: BuildingEntry,
163
+ category: str,
164
+ width: float | None,
165
+ depth: float | None,
166
+ tolerance: float,
167
+ ) -> tuple[float, bool]:
168
+ """Calculate a match score and determine orientation for a building entry.
169
+
170
+ Returns:
171
+ tuple[float, bool]: Match score and whether 90° rotation is needed
172
+ """
173
+ score = 0.0
174
+
175
+ # Category match (required) - base score
176
+ if category in entry.categories:
177
+ score = 100.0
178
+ else:
179
+ return 0.0, False # Category mismatch = no match
180
+
181
+ # Size matching (if dimensions are provided)
182
+ if width is not None and depth is not None:
183
+ # Calculate how well the dimensions match (considering both orientations)
184
+ size_score1 = self._calculate_size_match(
185
+ entry.width, entry.depth, width, depth, tolerance
186
+ )
187
+ size_score2 = self._calculate_size_match(
188
+ entry.width, entry.depth, depth, width, tolerance
189
+ )
190
+
191
+ # Determine which orientation is better
192
+ if size_score1 >= size_score2:
193
+ # Original orientation is better
194
+ if size_score1 > 0:
195
+ score += size_score1 * 80.0
196
+ return score, False
197
+ return 0.0, False
198
+ if size_score2 > 0:
199
+ score += size_score2 * 80.0
200
+ return score, True
201
+ return 0.0, False
202
+
203
+ return score, False
204
+
110
205
  def _calculate_match_score(
111
206
  self,
112
207
  entry: BuildingEntry,
@@ -116,7 +211,7 @@ class BuildingEntryCollection:
116
211
  tolerance: float,
117
212
  ) -> float:
118
213
  """Calculate a match score for a building entry.
119
- Region is already matched during initialization.
214
+ Region is matched during initialization unless ignore_region is True.
120
215
 
121
216
  Returns:
122
217
  float: Match score (higher is better, 0 means no match)
@@ -175,11 +270,11 @@ class BuildingEntryCollection:
175
270
  return (width_ratio + depth_ratio) / 2.0
176
271
 
177
272
  def get_available_categories(self) -> list[str]:
178
- """Get list of available building categories for this region."""
273
+ """Get list of available building categories for this collection."""
179
274
  return list(self.by_category.keys())
180
275
 
181
276
  def filter_by_category(self, category: str) -> list[BuildingEntry]:
182
- """Get all buildings of a specific category (already filtered by region)."""
277
+ """Get all buildings of a specific category (filtered by region unless ignore_region is True)."""
183
278
  return self.by_category.get(category, [])
184
279
 
185
280
 
@@ -310,17 +405,40 @@ class Building(I3d):
310
405
  building = BuildingEntry(**building_entry)
311
406
  building_entries.append(building)
312
407
 
313
- region = get_region_by_coordinates(self.coordinates)
408
+ ignore_region = False
409
+ region = ""
314
410
 
315
- self.buildings_collection = BuildingEntryCollection(building_entries, region)
316
- self.logger.info(
317
- "Buildings collection created with %d buildings for region '%s'.",
318
- len(self.buildings_collection.entries),
319
- region,
320
- )
411
+ if self.map.building_settings.region == AUTO_REGION:
412
+ region = get_region_by_coordinates(self.coordinates)
413
+ elif self.map.building_settings.region == ALL_REGIONS:
414
+ ignore_region = True
415
+ region = "all" # Set a default region name for logging
416
+ else:
417
+ region = self.map.building_settings.region
418
+
419
+ self.buildings_collection = BuildingEntryCollection(building_entries, region, ignore_region)
420
+
421
+ if ignore_region:
422
+ self.logger.info(
423
+ "Buildings collection created with %d buildings ignoring region restrictions.",
424
+ len(self.buildings_collection.entries),
425
+ )
426
+ else:
427
+ self.logger.info(
428
+ "Buildings collection created with %d buildings for region '%s'.",
429
+ len(self.buildings_collection.entries),
430
+ region,
431
+ )
321
432
 
322
- # pylint: disable=too-many-return-statements
323
433
  def process(self) -> None:
434
+ """Process and place buildings on the map."""
435
+ try:
436
+ self.add_buildings()
437
+ except Exception as e:
438
+ self.logger.warning("An error occurred during buildings processing: %s", e)
439
+
440
+ # pylint: disable=too-many-return-statements
441
+ def add_buildings(self) -> None:
324
442
  """Process and place buildings on the map based on the buildings map image and schema."""
325
443
  if not hasattr(self, "buildings_map_path") or not os.path.isfile(self.buildings_map_path):
326
444
  self.logger.warning(
@@ -376,7 +494,7 @@ class Building(I3d):
376
494
  file_id_counter = BUILDINGS_STARTING_NODE_ID
377
495
  node_id_counter = BUILDINGS_STARTING_NODE_ID + 1000
378
496
 
379
- not_resized_dem = self.get_not_resized_dem(with_foundations=True)
497
+ not_resized_dem = self.get_not_resized_dem_with_foundations(allow_fallback=True)
380
498
  if not_resized_dem is None:
381
499
  self.logger.warning("Not resized DEM not found.")
382
500
  return
@@ -414,17 +532,21 @@ class Building(I3d):
414
532
  rotation_angle,
415
533
  )
416
534
 
417
- # 3. Find the best matching building from the collection (region already filtered)
418
- best_match = self.buildings_collection.find_best_match(
535
+ # 3. Find the best matching building from the collection and determine orientation
536
+ best_match, needs_rotation = self.buildings_collection.find_best_match_with_orientation(
419
537
  category=category,
420
538
  width=width,
421
539
  depth=depth,
422
- tolerance=TOLERANCE_FACTOR,
540
+ tolerance=self.map.building_settings.tolerance_factor,
423
541
  )
424
542
 
425
543
  if best_match:
426
544
  self.logger.debug(
427
- f"Best building match: {best_match.name} ({best_match.width}x{best_match.depth})"
545
+ "Best building match: %s: %d x %d, needs_rotation: %s",
546
+ best_match.name,
547
+ best_match.width,
548
+ best_match.depth,
549
+ needs_rotation,
428
550
  )
429
551
 
430
552
  # Get world coordinates
@@ -470,11 +592,21 @@ class Building(I3d):
470
592
  else:
471
593
  file_id = used_building_files[best_match.file]
472
594
 
595
+ # Adjust rotation if the building needs to be rotated 90 degrees
596
+ final_rotation = rotation_angle
597
+ if needs_rotation:
598
+ final_rotation = (rotation_angle + 90.0) % 360.0
599
+ self.logger.debug(
600
+ "Building needs 90° rotation: original=%.1f°, final=%.1f°",
601
+ rotation_angle,
602
+ final_rotation,
603
+ )
604
+
473
605
  # Create building instance in the buildings group
474
606
  building_node = ET.SubElement(buildings_group, "ReferenceNode")
475
607
  building_node.set("name", f"{best_match.name}_{node_id_counter}")
476
608
  building_node.set("translation", f"{x_center:.3f} {z:.3f} {y_center:.3f}")
477
- building_node.set("rotation", f"0 {rotation_angle:.3f} 0")
609
+ building_node.set("rotation", f"0 {final_rotation:.3f} 0")
478
610
  # building_node.set(
479
611
  # "scale", f"{scale_width:.4f} 1.0 {scale_depth:.4f}"
480
612
  # )
@@ -485,7 +617,11 @@ class Building(I3d):
485
617
 
486
618
  else:
487
619
  self.logger.debug(
488
- f"No suitable building found for category '{category}' with dimensions {width:.2f}x{depth:.2f}"
620
+ "No suitable building found for category '%s' with dimensions %.2fx%.2f",
621
+ category,
622
+ width,
623
+ depth,
624
+ needs_rotation,
489
625
  )
490
626
  continue
491
627
 
@@ -712,7 +712,7 @@ class I3d(XMLComponent, ImageComponent):
712
712
  else background_component.not_resized_path
713
713
  )
714
714
 
715
- if not dem_path:
715
+ if not dem_path or not os.path.isfile(dem_path):
716
716
  self.logger.warning("Not resized DEM path not found.")
717
717
  return None
718
718
 
@@ -720,6 +720,28 @@ class I3d(XMLComponent, ImageComponent):
720
720
 
721
721
  return not_resized_dem
722
722
 
723
+ def get_not_resized_dem_with_foundations(
724
+ self, allow_fallback: bool = False
725
+ ) -> np.ndarray | None:
726
+ """Gets the not resized DEM with foundations. If the DEM with foundations is not found
727
+ and allow_fallback is True, the method returns the not resized DEM without foundations.
728
+
729
+ Arguments:
730
+ allow_fallback (bool, optional): Whether to allow fallback to DEM without
731
+ foundations. Defaults to False.
732
+
733
+ Returns:
734
+ np.ndarray | None: The not resized DEM image or None if the image could not be read.
735
+ """
736
+ dem_with_foundations = self.get_not_resized_dem(with_foundations=True)
737
+
738
+ if dem_with_foundations is not None:
739
+ return dem_with_foundations
740
+ self.logger.warning("Not resized DEM with foundations not found.")
741
+ if allow_fallback:
742
+ return self.get_not_resized_dem(with_foundations=False)
743
+ return None
744
+
723
745
  def info_sequence(self) -> dict[str, dict[str, str | float | int]]:
724
746
  """Returns information about the component.
725
747
 
@@ -814,7 +814,7 @@ class Texture(ImageComponent):
814
814
  is_fieds = info_layer == "fields"
815
815
 
816
816
  ox_settings.use_cache = self.map.texture_settings.use_cache
817
- ox_settings.requests_timeout = 30
817
+ ox_settings.requests_timeout = 10
818
818
 
819
819
  objects = self.fetch_osm_data(tags)
820
820
  if objects is None or objects.empty:
@@ -845,7 +845,7 @@ class Texture(ImageComponent):
845
845
  else:
846
846
  objects = ox.features_from_bbox(bbox=self.new_bbox, tags=tags)
847
847
  except Exception as e:
848
- self.logger.debug("Error fetching objects for tags: %s. Error: %s.", tags, e)
848
+ self.logger.warning("Error fetching objects for tags: %s. Error: %s.", tags, e)
849
849
  return None
850
850
 
851
851
  return objects
@@ -205,8 +205,31 @@ SAT_CACHE_DIR = os.path.join(MFS_CACHE_DIR, "sat")
205
205
 
206
206
  osmnx_cache = os.path.join(MFS_CACHE_DIR, "osmnx")
207
207
  osmnx_data = os.path.join(MFS_CACHE_DIR, "odata")
208
- os.makedirs(osmnx_cache, exist_ok=True)
209
- os.makedirs(osmnx_data, exist_ok=True)
208
+
209
+ CACHE_DIRS = [DTM_CACHE_DIR, SAT_CACHE_DIR, osmnx_cache, osmnx_data]
210
+
211
+
212
+ def create_cache_dirs() -> None:
213
+ """Create cache directories if they do not exist."""
214
+ logger.info("Ensuring cache directories exist...")
215
+ for cache_dir in CACHE_DIRS:
216
+ os.makedirs(cache_dir, exist_ok=True)
217
+ logger.debug("Cache directory ensured: %s", cache_dir)
218
+ logger.info("All cache directories are ready.")
219
+
220
+
221
+ def clean_cache() -> None:
222
+ """Clean all cache directories by removing and recreating them."""
223
+ logger.info("Cleaning cache directories...")
224
+ for cache_dir in CACHE_DIRS:
225
+ if os.path.exists(cache_dir):
226
+ shutil.rmtree(cache_dir)
227
+ logger.debug("Removed cache directory: %s", cache_dir)
228
+ create_cache_dirs()
229
+ logger.info("Cache directories cleaned and recreated.")
230
+
231
+
232
+ create_cache_dirs()
210
233
 
211
234
 
212
235
  ox_settings.cache_folder = osmnx_cache
maps4fs/generator/map.py CHANGED
@@ -105,6 +105,7 @@ class Map:
105
105
  self.i3d_settings = generation_settings.i3d_settings
106
106
  self.texture_settings = generation_settings.texture_settings
107
107
  self.satellite_settings = generation_settings.satellite_settings
108
+ self.building_settings = generation_settings.building_settings
108
109
  self.process_settings()
109
110
 
110
111
  self.logger = logger if logger else Logger()
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from datetime import datetime
7
- from typing import TYPE_CHECKING, Any, NamedTuple
7
+ from typing import TYPE_CHECKING, Any, Literal, NamedTuple
8
8
 
9
9
  from pydantic import BaseModel, ConfigDict
10
10
 
@@ -279,6 +279,21 @@ class SatelliteSettings(SettingsModel):
279
279
  zoom_level: int = 16
280
280
 
281
281
 
282
+ class BuildingSettings(SettingsModel):
283
+ """Represents the advanced settings for building component.
284
+
285
+ Attributes:
286
+ generate_buildings (bool): generate buildings on the map.
287
+ region (Literal["auto", "all", "EU", "US"]): region for the buildings.
288
+ tolerance_factor (float): tolerance factor representing allowed dimension difference
289
+ between OSM building footprint and the building model footprint.
290
+ """
291
+
292
+ generate_buildings: bool = True
293
+ region: Literal["auto", "all", "EU", "US"] = "auto"
294
+ tolerance_factor: float = 0.3
295
+
296
+
282
297
  class GenerationSettings(BaseModel):
283
298
  """Represents the settings for the map generation process."""
284
299
 
@@ -288,6 +303,7 @@ class GenerationSettings(BaseModel):
288
303
  i3d_settings: I3DSettings = I3DSettings()
289
304
  texture_settings: TextureSettings = TextureSettings()
290
305
  satellite_settings: SatelliteSettings = SatelliteSettings()
306
+ building_settings: BuildingSettings = BuildingSettings()
291
307
 
292
308
  def to_json(self) -> dict[str, Any]:
293
309
  """Convert the GenerationSettings instance to JSON format.
@@ -302,6 +318,7 @@ class GenerationSettings(BaseModel):
302
318
  "I3DSettings": self.i3d_settings.model_dump(),
303
319
  "TextureSettings": self.texture_settings.model_dump(),
304
320
  "SatelliteSettings": self.satellite_settings.model_dump(),
321
+ "BuildingSettings": self.building_settings.model_dump(),
305
322
  }
306
323
 
307
324
  @classmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maps4fs
3
- Version: 2.9.34
3
+ Version: 2.9.36
4
4
  Summary: Generate map templates for Farming Simulator from real places.
5
5
  Author-email: iwatkot <iwatkot@gmail.com>
6
6
  License: GNU Affero General Public License v3.0
@@ -1,31 +1,31 @@
1
1
  maps4fs/__init__.py,sha256=5ixsCA5vgcIV0OrF9EJBm91Mmc_KfMiDRM-QyifMAvo,386
2
2
  maps4fs/logger.py,sha256=aZAa9glzgvH6ySVDLelSPTwHfWZtpGK5YBl-ufNUsPg,801
3
3
  maps4fs/generator/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
4
- maps4fs/generator/config.py,sha256=gLYch5ZTmuJ4Bo9EKzwutZVpOHsTVlY90gSFLkzegmk,9035
4
+ maps4fs/generator/config.py,sha256=o_zRT8Nauy1djELsC867yD7GTs1fwz1OtZ2jE4w_7Ts,9797
5
5
  maps4fs/generator/game.py,sha256=bflRv0lxJ9-wkRvauh0k0RzIgF7zVWguygqQLcC7U-s,18457
6
- maps4fs/generator/map.py,sha256=9F3PaoK63sbhNONvHN46k62-ZFMBQy5qvu9T193gwWs,16045
6
+ maps4fs/generator/map.py,sha256=kkyMETKteFhnWRgmcR8gjdNBQy4roQzcdlFw1nE5chE,16116
7
7
  maps4fs/generator/monitor.py,sha256=Yrc7rClpmJK53SRzrOYZNBlwJmb5l6TkW-laFbyBEno,3524
8
8
  maps4fs/generator/qgis.py,sha256=Es8hLuqN_KH8lDfnJE6He2rWYbAKJ3RGPn-o87S6CPI,6116
9
- maps4fs/generator/settings.py,sha256=_QJL4ikQYLFOIB1zWqXjYvyLfoh3cr2RYb2IzsunMJg,13405
9
+ maps4fs/generator/settings.py,sha256=jrrIILNRtIpj7hpLrQqLTIagTY8tdJlLZDEN1M4n3Yc,14116
10
10
  maps4fs/generator/statistics.py,sha256=ol0MTiehcCbQFfyYA7cKU-M4_cjiLCktnGbid4GYABU,2641
11
11
  maps4fs/generator/utils.py,sha256=qaHmS5I30OhDwd213bbctlplQQlX-qkHugyszXGmh0U,5587
12
12
  maps4fs/generator/component/__init__.py,sha256=s01yVVVi8R2xxNvflu2D6wTd9I_g73AMM2x7vAC7GX4,490
13
- maps4fs/generator/component/background.py,sha256=c2bjK3DkQXvA6Gtb_hUM9m-7fIVgp2BxJp09c4ZY3_A,49434
14
- maps4fs/generator/component/building.py,sha256=Ru3AAFMPN2X_ePwM3Yoe-KDkuJySRERpvvvSxU1lJvc,22233
13
+ maps4fs/generator/component/background.py,sha256=KYtbnIqflrqiTzsibWh7atDWTeV1GgvLD0ZG463WshM,49704
14
+ maps4fs/generator/component/building.py,sha256=VoLjj6mDT-4kVfwxXP-lD0r4vJVYszyWZtciVFwdkIk,27402
15
15
  maps4fs/generator/component/config.py,sha256=tI2RQaGIqBgJIi9KjYfMZZ8AWg_YVUm6KKsBHGV241g,31285
16
16
  maps4fs/generator/component/dem.py,sha256=vMVJtU2jAS-2lfB9JsqodZsrUvY1h5xr3Dh5qk6txwk,11895
17
17
  maps4fs/generator/component/grle.py,sha256=FAcGmG7yq0icOElRoO4QMsVisZMsNrLhfNSWvGKnOHg,29899
18
- maps4fs/generator/component/i3d.py,sha256=qB18jQWfWjlTiaZ4fHd16vv359hN2YHRLzGTZuJnbbU,27166
18
+ maps4fs/generator/component/i3d.py,sha256=UM1js6bJx9_deftSvij1qjTiDypY8zvyhcXa5oXkfbk,28143
19
19
  maps4fs/generator/component/layer.py,sha256=-MHnIXyJ7Xth9wOcjJCX-XkXBIYYv23lRRGbQ0XlHdU,7602
20
20
  maps4fs/generator/component/satellite.py,sha256=1bPqd8JqAPqU0tEI9m-iuljMW9hXqlaCIxvq7kdpMY0,5219
21
- maps4fs/generator/component/texture.py,sha256=pmX8KE96dJAwzhnOe_ed-1In6sOLXs8-qdJ0aPc-ePM,38526
21
+ maps4fs/generator/component/texture.py,sha256=XocNWPH1BHBcvTOJgpx8RU_tB-Mi1Kefp_lgOVIkEzY,38528
22
22
  maps4fs/generator/component/base/__init__.py,sha256=zZMLEkGzb4z0xql650gOtGSvcgX58DnJ2yN3vC2daRk,43
23
23
  maps4fs/generator/component/base/component.py,sha256=-7H3donrH19f0_rivNyI3fgLsiZkntXfGywEx4tOnM4,23924
24
24
  maps4fs/generator/component/base/component_image.py,sha256=GXFkEFARNRkWkDiGSjvU4WX6f_8s6R1t2ZYqZflv1jk,9626
25
25
  maps4fs/generator/component/base/component_mesh.py,sha256=2wGe_-wAZVRljMKzzVJ8jdzIETWg7LjxGj8A3inH5eI,25550
26
26
  maps4fs/generator/component/base/component_xml.py,sha256=MT-VhU2dEckLFxAgmxg6V3gnv11di_94Qq6atfpOLdc,5342
27
- maps4fs-2.9.34.dist-info/licenses/LICENSE.md,sha256=Ptw8AkqJ60c4tRts6yuqGP_8B0dxwOGmJsp6YJ8dKqM,34328
28
- maps4fs-2.9.34.dist-info/METADATA,sha256=N8RjSki41ECPZSKqYO5R97NgpGKcpeonB6SyWKI_m8c,10213
29
- maps4fs-2.9.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- maps4fs-2.9.34.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
31
- maps4fs-2.9.34.dist-info/RECORD,,
27
+ maps4fs-2.9.36.dist-info/licenses/LICENSE.md,sha256=Ptw8AkqJ60c4tRts6yuqGP_8B0dxwOGmJsp6YJ8dKqM,34328
28
+ maps4fs-2.9.36.dist-info/METADATA,sha256=-Fi5m_KZ584IkyeFCJnx89a1yFNq0VIPZEe6Wv3lKSQ,10213
29
+ maps4fs-2.9.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
+ maps4fs-2.9.36.dist-info/top_level.txt,sha256=Ue9DSRlejRQRCaJueB0uLcKrWwsEq9zezfv5dI5mV1M,8
31
+ maps4fs-2.9.36.dist-info/RECORD,,