voxcity 0.5.14__py3-none-any.whl → 0.5.16__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 voxcity might be problematic. Click here for more details.

@@ -7,29 +7,75 @@ import matplotlib.pyplot as plt
7
7
 
8
8
  def create_voxel_mesh(voxel_array, class_id, meshsize=1.0, building_id_grid=None, mesh_type=None):
9
9
  """
10
- Create a mesh from voxels preserving sharp edges, scaled by meshsize.
11
-
10
+ Create a 3D mesh from voxels preserving sharp edges, scaled by meshsize.
11
+
12
+ This function converts a 3D voxel array into a triangulated mesh, where each voxel
13
+ face is converted into two triangles. The function preserves sharp edges between
14
+ different classes and handles special cases for buildings.
15
+
12
16
  Parameters
13
17
  ----------
14
18
  voxel_array : np.ndarray (3D)
15
- The voxel array of shape (X, Y, Z).
19
+ The voxel array of shape (X, Y, Z) where each cell contains a class ID.
20
+ - 0: typically represents void/air
21
+ - -2: typically represents trees
22
+ - -3: typically represents buildings
23
+ Other values can represent different classes as defined by the application.
24
+
16
25
  class_id : int
17
- The ID of the class to extract.
18
- meshsize : float
19
- The real-world size of each voxel in meters, for x, y, and z.
26
+ The ID of the class to extract. Only voxels with this ID will be included
27
+ in the output mesh.
28
+
29
+ meshsize : float, default=1.0
30
+ The real-world size of each voxel in meters, applied uniformly to x, y, and z
31
+ dimensions. Used to scale the output mesh to real-world coordinates.
32
+
20
33
  building_id_grid : np.ndarray (2D), optional
21
- 2D grid of building IDs, shape (X, Y). Used when class_id=-3 (buildings).
34
+ 2D grid of building IDs with shape (X, Y). Only used when class_id=-3 (buildings).
35
+ Each cell contains a unique identifier for the building at that location.
36
+ This allows tracking which faces belong to which building.
37
+
22
38
  mesh_type : str, optional
23
- Type of mesh to create:
24
- - None (default): create meshes for boundaries between different classes
25
- - 'building_solar': only create meshes for boundaries between buildings (-3)
26
- and void (0) or trees (-2)
39
+ Type of mesh to create, controlling which faces are included:
40
+ - None (default): create faces at boundaries between different classes
41
+ - 'building_solar': only create faces at boundaries between buildings (-3)
42
+ and either void (0) or trees (-2). Useful for solar analysis
43
+ where only exposed surfaces matter.
27
44
 
28
45
  Returns
29
46
  -------
30
47
  mesh : trimesh.Trimesh or None
31
- The resulting mesh for the given class_id (or None if no voxels).
32
- If class_id=-3, mesh.metadata['building_id'] contains building IDs.
48
+ The resulting triangulated mesh for the given class_id. Returns None if no
49
+ voxels of the specified class are found.
50
+
51
+ The mesh includes:
52
+ - vertices: 3D coordinates of each vertex
53
+ - faces: triangles defined by vertex indices
54
+ - face_normals: normal vectors for each face
55
+ - metadata: If class_id=-3, includes 'building_id' mapping faces to buildings
56
+
57
+ Examples
58
+ --------
59
+ Basic usage for a simple voxel array:
60
+ >>> voxels = np.zeros((10, 10, 10))
61
+ >>> voxels[4:7, 4:7, 0:5] = 1 # Create a simple column
62
+ >>> mesh = create_voxel_mesh(voxels, class_id=1, meshsize=0.5)
63
+
64
+ Creating a building mesh with IDs:
65
+ >>> building_ids = np.zeros((10, 10))
66
+ >>> building_ids[4:7, 4:7] = 1 # Mark building #1
67
+ >>> mesh = create_voxel_mesh(voxels, class_id=-3,
68
+ ... building_id_grid=building_ids,
69
+ ... meshsize=1.0)
70
+
71
+ Notes
72
+ -----
73
+ - The function creates faces only at boundaries between different classes or at
74
+ the edges of the voxel array.
75
+ - Each face is split into two triangles for compatibility with graphics engines.
76
+ - Face normals are computed to ensure correct lighting and rendering.
77
+ - For buildings (class_id=-3), building IDs are tracked to maintain building identity.
78
+ - The mesh preserves sharp edges, which is important for architectural visualization.
33
79
  """
34
80
  # Find voxels of the current class
35
81
  voxel_coords = np.argwhere(voxel_array == class_id)
@@ -152,32 +198,78 @@ def create_sim_surface_mesh(sim_grid, dem_grid,
152
198
  cmap_name='viridis',
153
199
  vmin=None, vmax=None):
154
200
  """
155
- Create a planar surface mesh from sim_grid located at dem_grid + z_offset.
156
- Skips any cells in sim_grid that are NaN, and flips both sim_grid and dem_grid
157
- (up-down) to match voxel_array orientation. Applies meshsize scaling in x,y.
158
-
201
+ Create a colored planar surface mesh from simulation data, positioned above a DEM.
202
+
203
+ This function generates a 3D visualization mesh for 2D simulation results (like
204
+ Green View Index, solar radiation, etc.). The mesh is positioned above the Digital
205
+ Elevation Model (DEM) by a specified offset, and colored according to the simulation
206
+ values using a matplotlib colormap.
207
+
159
208
  Parameters
160
209
  ----------
161
210
  sim_grid : 2D np.ndarray
162
- 2D array of simulation values (e.g., Green View Index).
211
+ 2D array of simulation values (e.g., Green View Index, solar radiation).
212
+ NaN values in this grid will be skipped in the output mesh.
213
+ The grid should be oriented with north at the top.
214
+
163
215
  dem_grid : 2D np.ndarray
164
- 2D array of ground elevations (same shape as sim_grid).
165
- meshsize : float
166
- Size of each cell in meters (same in x and y).
167
- z_offset : float
168
- Additional offset added to dem_grid for placing the mesh.
169
- cmap_name : str
170
- Matplotlib colormap name. Default is 'viridis'.
171
- vmin : float or None
172
- Minimum value for color mapping. If None, use min of sim_grid (non-NaN).
173
- vmax : float or None
174
- Maximum value for color mapping. If None, use max of sim_grid (non-NaN).
175
-
216
+ 2D array of ground elevations in meters. Must have the same shape as sim_grid.
217
+ Used to position the visualization mesh at the correct height above terrain.
218
+
219
+ meshsize : float, default=1.0
220
+ Size of each cell in meters. Applied uniformly to x and y dimensions.
221
+ Determines the resolution of the output mesh.
222
+
223
+ z_offset : float, default=1.5
224
+ Additional height offset in meters added to dem_grid elevations.
225
+ Used to position the visualization above ground level for better visibility.
226
+
227
+ cmap_name : str, default='viridis'
228
+ Matplotlib colormap name used for coloring the mesh based on sim_grid values.
229
+ Common options:
230
+ - 'viridis': Default, perceptually uniform, colorblind-friendly
231
+ - 'RdYlBu': Red-Yellow-Blue, good for diverging data
232
+ - 'jet': Rainbow colormap (not recommended for scientific visualization)
233
+
234
+ vmin : float, optional
235
+ Minimum value for color mapping. If None, uses min of sim_grid (excluding NaN).
236
+ Used to control the range of the colormap.
237
+
238
+ vmax : float, optional
239
+ Maximum value for color mapping. If None, uses max of sim_grid (excluding NaN).
240
+ Used to control the range of the colormap.
241
+
176
242
  Returns
177
243
  -------
178
- trimesh.Trimesh or None
179
- A single mesh containing one square face per non-NaN cell.
180
- Returns None if there are no valid cells.
244
+ mesh : trimesh.Trimesh or None
245
+ A single mesh containing one colored square face (two triangles) per non-NaN cell.
246
+ Returns None if there are no valid (non-NaN) cells in sim_grid.
247
+
248
+ The mesh includes:
249
+ - vertices: 3D coordinates of each vertex
250
+ - faces: triangles defined by vertex indices
251
+ - face_colors: RGBA colors for each face based on sim_grid values
252
+ - visual: trimesh.visual.ColorVisuals object storing the face colors
253
+
254
+ Examples
255
+ --------
256
+ Basic usage with Green View Index data:
257
+ >>> gvi = np.array([[0.5, 0.6], [0.4, 0.8]]) # GVI values
258
+ >>> dem = np.array([[10.0, 10.2], [9.8, 10.1]]) # Ground heights
259
+ >>> mesh = create_sim_surface_mesh(gvi, dem, meshsize=1.0, z_offset=1.5)
260
+
261
+ Custom color range and colormap:
262
+ >>> mesh = create_sim_surface_mesh(gvi, dem,
263
+ ... cmap_name='RdYlBu',
264
+ ... vmin=0.0, vmax=1.0)
265
+
266
+ Notes
267
+ -----
268
+ - The function automatically creates a matplotlib colorbar figure for visualization
269
+ - Both input grids are flipped vertically to match the voxel_array orientation
270
+ - Each grid cell is converted to two triangles for compatibility with 3D engines
271
+ - The mesh is positioned at dem_grid + z_offset to float above the terrain
272
+ - Face colors are interpolated from the colormap based on sim_grid values
181
273
  """
182
274
  # Flip arrays vertically
183
275
  sim_grid_flipped = np.flipud(sim_grid)
@@ -260,8 +352,65 @@ def create_sim_surface_mesh(sim_grid, dem_grid,
260
352
 
261
353
  def create_city_meshes(voxel_array, vox_dict, meshsize=1.0):
262
354
  """
263
- Create meshes from voxel data with sharp edges preserved.
264
- Applies meshsize for voxel scaling.
355
+ Create a collection of colored 3D meshes representing different city elements.
356
+
357
+ This function processes a voxelized city model and creates separate meshes for
358
+ different urban elements (buildings, trees, etc.), each with its own color.
359
+ The function preserves sharp edges and applies appropriate colors based on the
360
+ provided color dictionary.
361
+
362
+ Parameters
363
+ ----------
364
+ voxel_array : np.ndarray (3D)
365
+ 3D array representing the voxelized city model. Each voxel contains a class ID
366
+ that maps to an urban element type:
367
+ - 0: Void/air (automatically skipped)
368
+ - -2: Trees
369
+ - -3: Buildings
370
+ Other values can represent different urban elements as defined in vox_dict.
371
+
372
+ vox_dict : dict
373
+ Dictionary mapping class IDs to RGB colors. Each entry should be:
374
+ {class_id: [R, G, B]} where R, G, B are 0-255 integer values.
375
+ Example: {-3: [200, 200, 200], -2: [0, 255, 0]} for grey buildings and
376
+ green trees. The key 0 (air) is automatically excluded.
377
+
378
+ meshsize : float, default=1.0
379
+ Size of each voxel in meters, applied uniformly to x, y, and z dimensions.
380
+ Used to scale the output meshes to real-world coordinates.
381
+
382
+ Returns
383
+ -------
384
+ meshes : dict
385
+ Dictionary mapping class IDs to their corresponding trimesh.Trimesh objects.
386
+ Each mesh includes:
387
+ - vertices: 3D coordinates scaled by meshsize
388
+ - faces: triangulated faces preserving sharp edges
389
+ - face_colors: RGBA colors from vox_dict
390
+ - visual: trimesh.visual.ColorVisuals object storing the face colors
391
+
392
+ Classes with no voxels are automatically excluded from the output.
393
+
394
+ Examples
395
+ --------
396
+ Basic usage with buildings and trees:
397
+ >>> voxels = np.zeros((10, 10, 10))
398
+ >>> voxels[4:7, 4:7, 0:5] = -3 # Add a building
399
+ >>> voxels[2:4, 2:4, 0:3] = -2 # Add some trees
400
+ >>> colors = {
401
+ ... -3: [200, 200, 200], # Grey buildings
402
+ ... -2: [0, 255, 0] # Green trees
403
+ ... }
404
+ >>> meshes = create_city_meshes(voxels, colors, meshsize=1.0)
405
+
406
+ Notes
407
+ -----
408
+ - The function automatically skips class_id=0 (typically air/void)
409
+ - Each urban element type gets its own separate mesh for efficient rendering
410
+ - Colors are converted from RGB [0-255] to RGBA [0-1] format
411
+ - Sharp edges are preserved to maintain architectural features
412
+ - Empty classes (no voxels) are automatically excluded from the output
413
+ - Errors during mesh creation for a class are caught and reported
265
414
  """
266
415
  meshes = {}
267
416
 
@@ -296,7 +445,58 @@ def create_city_meshes(voxel_array, vox_dict, meshsize=1.0):
296
445
 
297
446
  def export_meshes(meshes, output_directory, base_filename):
298
447
  """
299
- Export meshes to OBJ (with MTL) and STL formats.
448
+ Export a collection of meshes to both OBJ (with MTL) and STL formats.
449
+
450
+ This function exports meshes in two ways:
451
+ 1. A single combined OBJ file with materials (and associated MTL file)
452
+ 2. Separate STL files for each mesh, named with their class IDs
453
+
454
+ Parameters
455
+ ----------
456
+ meshes : dict
457
+ Dictionary mapping class IDs to trimesh.Trimesh objects.
458
+ Each mesh should have:
459
+ - vertices: 3D coordinates
460
+ - faces: triangulated faces
461
+ - face_colors: RGBA colors (if using materials)
462
+
463
+ output_directory : str
464
+ Directory path where the output files will be saved.
465
+ Will be created if it doesn't exist.
466
+
467
+ base_filename : str
468
+ Base name for the output files (without extension).
469
+ Will be used to create:
470
+ - {base_filename}.obj : Combined mesh with materials
471
+ - {base_filename}.mtl : Material definitions for OBJ
472
+ - {base_filename}_{class_id}.stl : Individual STL files
473
+
474
+ Returns
475
+ -------
476
+ None
477
+ Files are written directly to the specified output directory.
478
+
479
+ Examples
480
+ --------
481
+ >>> meshes = {
482
+ ... -3: building_mesh, # Building mesh with grey color
483
+ ... -2: tree_mesh # Tree mesh with green color
484
+ ... }
485
+ >>> export_meshes(meshes, 'output/models', 'city_model')
486
+
487
+ This will create:
488
+ - output/models/city_model.obj
489
+ - output/models/city_model.mtl
490
+ - output/models/city_model_-3.stl
491
+ - output/models/city_model_-2.stl
492
+
493
+ Notes
494
+ -----
495
+ - OBJ/MTL format preserves colors and materials but is more complex
496
+ - STL format is simpler but doesn't support colors
497
+ - STL files are exported separately for each class for easier processing
498
+ - The OBJ file combines all meshes while preserving their materials
499
+ - File extensions are automatically added to the base filename
300
500
  """
301
501
  # Export combined mesh as OBJ with materials
302
502
  combined_mesh = trimesh.util.concatenate(list(meshes.values()))
@@ -309,8 +509,55 @@ def export_meshes(meshes, output_directory, base_filename):
309
509
 
310
510
  def split_vertices_manual(mesh):
311
511
  """
312
- Imitate trimesh's split_vertices() by giving each face its own copy of vertices.
313
- This ensures every face is truly disconnected, preventing smooth shading in Rhino.
512
+ Split a mesh into independent faces by duplicating shared vertices.
513
+
514
+ This function imitates trimesh's split_vertices() functionality but ensures
515
+ complete face independence by giving each face its own copy of vertices.
516
+ This is particularly useful for rendering applications where smooth shading
517
+ between faces is undesirable, such as architectural visualization in Rhino.
518
+
519
+ Parameters
520
+ ----------
521
+ mesh : trimesh.Trimesh
522
+ Input mesh to split. Should have:
523
+ - vertices: array of vertex coordinates
524
+ - faces: array of vertex indices forming triangles
525
+ - visual: Optional ColorVisuals object with face colors
526
+
527
+ Returns
528
+ -------
529
+ out_mesh : trimesh.Trimesh
530
+ New mesh where each face is completely independent, with:
531
+ - Duplicated vertices for each face
532
+ - No vertex sharing between faces
533
+ - Preserved face colors if present in input
534
+ - Each face as a separate component
535
+
536
+ Examples
537
+ --------
538
+ Basic usage:
539
+ >>> vertices = np.array([[0,0,0], [1,0,0], [1,1,0], [0,1,0]])
540
+ >>> faces = np.array([[0,1,2], [0,2,3]]) # Two triangles sharing vertices
541
+ >>> mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
542
+ >>> split_mesh = split_vertices_manual(mesh)
543
+ >>> print(f"Original vertices: {len(mesh.vertices)}") # 4 vertices
544
+ >>> print(f"Split vertices: {len(split_mesh.vertices)}") # 6 vertices
545
+
546
+ With face colors:
547
+ >>> colors = np.array([[255,0,0,255], [0,255,0,255]]) # Red and green faces
548
+ >>> mesh.visual = trimesh.visual.ColorVisuals(mesh, face_colors=colors)
549
+ >>> split_mesh = split_vertices_manual(mesh) # Colors are preserved
550
+
551
+ Notes
552
+ -----
553
+ - Each output face has exactly 3 unique vertices
554
+ - Face colors are preserved in the output mesh
555
+ - Useful for:
556
+ - Preventing smooth shading artifacts
557
+ - Ensuring face color independence
558
+ - Preparing meshes for CAD software
559
+ - Creating sharp edges in architectural models
560
+ - Memory usage increases as vertices are duplicated
314
561
  """
315
562
  new_meshes = []
316
563
 
@@ -345,23 +592,80 @@ def split_vertices_manual(mesh):
345
592
 
346
593
  def save_obj_from_colored_mesh(meshes, output_path, base_filename):
347
594
  """
348
- Save colored meshes as OBJ and MTL files.
595
+ Save a collection of colored meshes as OBJ and MTL files with material support.
596
+
597
+ This function exports colored meshes to the Wavefront OBJ format with an
598
+ accompanying MTL file for material definitions. It handles the conversion of
599
+ face colors to materials and ensures proper material assignment in the OBJ file.
600
+ The function is particularly useful for preserving color information in
601
+ architectural and urban visualization models.
349
602
 
350
603
  Parameters
351
604
  ----------
352
605
  meshes : dict
353
- Dictionary of trimesh.Trimesh objects with face colors.
606
+ Dictionary mapping class IDs to trimesh.Trimesh objects.
607
+ Each mesh should have:
608
+ - vertices: array of 3D coordinates
609
+ - faces: array of vertex indices forming triangles
610
+ - visual.face_colors: RGBA colors for each face
611
+
354
612
  output_path : str
355
613
  Directory path where to save the files.
614
+ Will be created if it doesn't exist.
615
+ Should be writable by the current user.
616
+
356
617
  base_filename : str
357
618
  Base name for the output files (without extension).
358
-
619
+ Will be used to create:
620
+ - {base_filename}.obj : The main geometry file
621
+ - {base_filename}.mtl : The material definitions file
622
+
359
623
  Returns
360
624
  -------
361
625
  tuple
362
- Paths to the saved (obj_file, mtl_file).
363
- """
626
+ (obj_path, mtl_path) : Paths to the saved OBJ and MTL files.
627
+ Both paths are absolute or relative depending on the input output_path.
364
628
 
629
+ Examples
630
+ --------
631
+ Basic usage with multiple colored meshes:
632
+ >>> building_mesh = trimesh.Trimesh(
633
+ ... vertices=[[0,0,0], [1,0,0], [1,1,0]],
634
+ ... faces=[[0,1,2]],
635
+ ... face_colors=[[200,200,200,255]] # Grey color
636
+ ... )
637
+ >>> tree_mesh = trimesh.Trimesh(
638
+ ... vertices=[[2,0,0], [3,0,0], [2.5,1,0]],
639
+ ... faces=[[0,1,2]],
640
+ ... face_colors=[[0,255,0,255]] # Green color
641
+ ... )
642
+ >>> meshes = {-3: building_mesh, -2: tree_mesh}
643
+ >>> obj_path, mtl_path = save_obj_from_colored_mesh(
644
+ ... meshes, 'output/models', 'city'
645
+ ... )
646
+
647
+ Notes
648
+ -----
649
+ - Creates unique materials for each distinct face color
650
+ - Material names are auto-generated as 'material_0', 'material_1', etc.
651
+ - Handles both RGB and RGBA colors (alpha channel supported)
652
+ - Colors are normalized from [0-255] to [0-1] range for MTL format
653
+ - Vertices are written in OBJ's 1-based indexing format
654
+ - Faces are grouped by material for efficient rendering
655
+ - The MTL file is automatically referenced in the OBJ file
656
+
657
+ File Format Details
658
+ -----------------
659
+ OBJ file structure:
660
+ - mtllib reference to MTL file
661
+ - All vertex coordinates (v)
662
+ - Face definitions (f) grouped by material (usemtl)
663
+
664
+ MTL file structure:
665
+ - newmtl: Material name
666
+ - Kd: Diffuse color (RGB)
667
+ - d: Alpha/transparency
668
+ """
365
669
  os.makedirs(output_path, exist_ok=True)
366
670
  obj_path = os.path.join(output_path, f"{base_filename}.obj")
367
671
  mtl_path = os.path.join(output_path, f"{base_filename}.mtl")