voxcity 0.5.6__py3-none-any.whl → 0.5.8__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.

voxcity/exporter/obj.py CHANGED
@@ -9,6 +9,8 @@ import numpy as np
9
9
  import os
10
10
  from numba import njit, prange
11
11
  import matplotlib.pyplot as plt
12
+ import trimesh
13
+ import numpy as np
12
14
  from ..utils.visualization import get_voxel_color_map
13
15
 
14
16
  def convert_colormap_indices(original_map):
@@ -11,6 +11,7 @@ from pyproj import Geod, Transformer, CRS
11
11
  import rasterio
12
12
  from affine import Affine
13
13
  from shapely.geometry import box, Polygon, Point, MultiPolygon
14
+ import warnings
14
15
 
15
16
  from scipy.interpolate import griddata
16
17
  from shapely.errors import GEOSException
@@ -43,6 +43,7 @@ from ..geoprocessor.mesh import (
43
43
  create_city_meshes,
44
44
  export_meshes
45
45
  )
46
+ # from ..exporter.obj import save_obj_from_colored_mesh
46
47
  from .material import get_material_dict
47
48
 
48
49
  # def get_voxel_color_map():
@@ -74,6 +75,7 @@ from .material import get_material_dict
74
75
  # 13: [150, 166, 190], # 'Building (ground surface)'
75
76
  # 14: [239, 228, 176], # 'No Data (ground surface)'
76
77
  # }
78
+
77
79
  def get_voxel_color_map(color_scheme='default'):
78
80
  """
79
81
  Returns a color map for voxel visualization based on the specified color scheme.
@@ -1528,6 +1530,7 @@ def visualize_voxcity_multi_view(voxel_array, meshsize, **kwargs):
1528
1530
  vmax = kwargs.get("vmax", np.nanmax(sim_grid))
1529
1531
  projection_type = kwargs.get("projection_type", "perspective")
1530
1532
  distance_factor = kwargs.get("distance_factor", 1.0)
1533
+ save_obj = kwargs.get("save_obj", False)
1531
1534
 
1532
1535
  # Create meshes
1533
1536
  print("Creating voxel meshes...")
@@ -1579,6 +1582,13 @@ def visualize_voxcity_multi_view(voxel_array, meshsize, **kwargs):
1579
1582
  plt.axis('off')
1580
1583
  plt.show()
1581
1584
  plt.close()
1585
+
1586
+ # After creating the meshes and before visualization
1587
+ if save_obj:
1588
+ output_directory = kwargs.get('output_directory', 'output')
1589
+ output_file_name = kwargs.get('output_file_name', 'voxcity_mesh')
1590
+ obj_path, mtl_path = save_obj_from_colored_mesh(meshes, output_directory, output_file_name)
1591
+ print(f"Saved mesh files to:\n {obj_path}\n {mtl_path}")
1582
1592
 
1583
1593
  def visualize_voxcity_multi_view_with_multiple_sim_grids(voxel_array, meshsize, sim_configs, **kwargs):
1584
1594
  """
@@ -1610,7 +1620,9 @@ def visualize_voxcity_multi_view_with_multiple_sim_grids(voxel_array, meshsize,
1610
1620
  # Configure PyVista settings
1611
1621
  pv.set_plot_theme('document')
1612
1622
  pv.global_theme.background = 'white'
1613
- pv.global_theme.window_size = [1024, 768]
1623
+ window_width = kwargs.get("window_width", 1024)
1624
+ window_height = kwargs.get("window_height", 768)
1625
+ pv.global_theme.window_size = [window_width, window_height]
1614
1626
  pv.global_theme.jupyter_backend = 'static'
1615
1627
 
1616
1628
  # Parse general kwargs
@@ -1743,7 +1755,9 @@ def visualize_voxcity_with_sim_meshes(voxel_array, meshsize, custom_meshes=None,
1743
1755
  # Configure PyVista settings
1744
1756
  pv.set_plot_theme('document')
1745
1757
  pv.global_theme.background = 'white'
1746
- pv.global_theme.window_size = [1024, 768]
1758
+ window_width = kwargs.get("window_width", 1024)
1759
+ window_height = kwargs.get("window_height", 768)
1760
+ pv.global_theme.window_size = [window_width, window_height]
1747
1761
  pv.global_theme.jupyter_backend = 'static'
1748
1762
 
1749
1763
  # Parse kwargs
@@ -1764,6 +1778,7 @@ def visualize_voxcity_with_sim_meshes(voxel_array, meshsize, custom_meshes=None,
1764
1778
  colorbar_title = kwargs.get("colorbar_title", "")
1765
1779
  value_name = kwargs.get("value_name", None)
1766
1780
  nan_color = kwargs.get("nan_color", "gray")
1781
+ save_obj = kwargs.get("save_obj", False)
1767
1782
 
1768
1783
  if value_name is None:
1769
1784
  print("Set value_name")
@@ -1891,7 +1906,7 @@ def visualize_voxcity_with_sim_meshes(voxel_array, meshsize, custom_meshes=None,
1891
1906
 
1892
1907
  # Display each view separately
1893
1908
  for view_name, img_file in image_files:
1894
- plt.figure(figsize=(12, 8))
1909
+ plt.figure(figsize=(24, 16))
1895
1910
  img = plt.imread(img_file)
1896
1911
  plt.imshow(img)
1897
1912
  plt.title(view_name.replace('_', ' ').title(), pad=20)
@@ -1899,6 +1914,13 @@ def visualize_voxcity_with_sim_meshes(voxel_array, meshsize, custom_meshes=None,
1899
1914
  plt.show()
1900
1915
  plt.close()
1901
1916
 
1917
+ # After creating the meshes and before visualization
1918
+ if save_obj:
1919
+ output_directory = kwargs.get('output_directory', 'output')
1920
+ output_file_name = kwargs.get('output_file_name', 'voxcity_mesh')
1921
+ obj_path, mtl_path = save_obj_from_colored_mesh(meshes, output_directory, output_file_name)
1922
+ print(f"Saved mesh files to:\n {obj_path}\n {mtl_path}")
1923
+
1902
1924
  return image_files
1903
1925
 
1904
1926
  def visualize_building_sim_results(voxel_array, meshsize, building_sim_mesh, **kwargs):
@@ -1952,4 +1974,70 @@ def visualize_building_sim_results(voxel_array, meshsize, building_sim_mesh, **k
1952
1974
  meshsize,
1953
1975
  custom_meshes=custom_meshes,
1954
1976
  **kwargs
1955
- )
1977
+ )
1978
+
1979
+ def save_obj_from_colored_mesh(meshes, output_path, base_filename):
1980
+ """
1981
+ Save colored meshes as OBJ and MTL files.
1982
+
1983
+ Parameters
1984
+ ----------
1985
+ meshes : dict
1986
+ Dictionary of trimesh.Trimesh objects with face colors.
1987
+ output_path : str
1988
+ Directory path where to save the files.
1989
+ base_filename : str
1990
+ Base name for the output files (without extension).
1991
+
1992
+ Returns
1993
+ -------
1994
+ tuple
1995
+ Paths to the saved (obj_file, mtl_file).
1996
+ """
1997
+
1998
+ os.makedirs(output_path, exist_ok=True)
1999
+ obj_path = os.path.join(output_path, f"{base_filename}.obj")
2000
+ mtl_path = os.path.join(output_path, f"{base_filename}.mtl")
2001
+
2002
+ # Combine all meshes
2003
+ combined_mesh = trimesh.util.concatenate(list(meshes.values()))
2004
+
2005
+ # Create unique materials for each unique face color
2006
+ face_colors = combined_mesh.visual.face_colors
2007
+ unique_colors = np.unique(face_colors, axis=0)
2008
+
2009
+ # Write MTL file
2010
+ with open(mtl_path, 'w') as mtl_file:
2011
+ for i, color in enumerate(unique_colors):
2012
+ material_name = f'material_{i}'
2013
+ mtl_file.write(f'newmtl {material_name}\n')
2014
+ # Convert RGBA to RGB float values
2015
+ rgb = color[:3].astype(float) / 255.0
2016
+ mtl_file.write(f'Kd {rgb[0]:.6f} {rgb[1]:.6f} {rgb[2]:.6f}\n')
2017
+ mtl_file.write(f'd {color[3]/255.0:.6f}\n\n') # Alpha value
2018
+
2019
+ # Create material groups based on face colors
2020
+ color_to_material = {tuple(c): f'material_{i}' for i, c in enumerate(unique_colors)}
2021
+
2022
+ # Write OBJ file
2023
+ with open(obj_path, 'w') as obj_file:
2024
+ obj_file.write(f'mtllib {os.path.basename(mtl_path)}\n')
2025
+
2026
+ # Write vertices
2027
+ for vertex in combined_mesh.vertices:
2028
+ obj_file.write(f'v {vertex[0]:.6f} {vertex[1]:.6f} {vertex[2]:.6f}\n')
2029
+
2030
+ # Write faces grouped by material
2031
+ current_material = None
2032
+ for face_idx, face in enumerate(combined_mesh.faces):
2033
+ face_color = tuple(face_colors[face_idx])
2034
+ material_name = color_to_material[face_color]
2035
+
2036
+ if material_name != current_material:
2037
+ obj_file.write(f'usemtl {material_name}\n')
2038
+ current_material = material_name
2039
+
2040
+ # OBJ indices are 1-based
2041
+ obj_file.write(f'f {face[0]+1} {face[1]+1} {face[2]+1}\n')
2042
+
2043
+ return obj_path, mtl_path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voxcity
3
- Version: 0.5.6
3
+ Version: 0.5.8
4
4
  Summary: voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data
5
5
  Author-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
6
6
  Maintainer-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
@@ -45,7 +45,7 @@ Requires-Dist: pycountry
45
45
  Requires-Dist: osm2geojson
46
46
  Requires-Dist: seaborn
47
47
  Requires-Dist: overturemaps
48
- Requires-Dist: protobuf<=3.20.3
48
+ Requires-Dist: protobuf<6,>=4.21
49
49
  Requires-Dist: timezonefinder
50
50
  Requires-Dist: astral
51
51
  Requires-Dist: osmnx
@@ -13,10 +13,10 @@ voxcity/downloader/utils.py,sha256=z6MdPxM96FWQVqvZW2Eg5pMewVHVysUP7F6ueeCwMfI,1
13
13
  voxcity/exporter/__init_.py,sha256=cVyNyE6axEpSd3CT5hGuMOAlOyU1p8lVP4jkF1-0Ad8,94
14
14
  voxcity/exporter/envimet.py,sha256=m-y2IYw-yp45AT2wN9UIlxvMjvDvupTKzyfRJl057fE,24300
15
15
  voxcity/exporter/magicavoxel.py,sha256=Fsv7yGRXeKmp82xcG3rOb0t_HtoqltNq2tHl08xVlqY,7500
16
- voxcity/exporter/obj.py,sha256=0RBFPMKGRH6uNmCLIwAoYFko1bOZKtTSwg7QnoPMud0,21593
16
+ voxcity/exporter/obj.py,sha256=M0MT9UZOVYsRJBEJea3qg1uu7NsMXagD24jQnmPaJLo,21629
17
17
  voxcity/geoprocessor/__init_.py,sha256=JzPVhhttxBWvaZ0IGX2w7OWL5bCo_TIvpHefWeNXruA,133
18
18
  voxcity/geoprocessor/draw.py,sha256=8Em2NvazFpYfFJUqG9LofNXaxdghKLL_rNuztmPwn8Q,13911
19
- voxcity/geoprocessor/grid.py,sha256=JNTtapcVypZduryGUfOURj_s8rq3ZbtJMapdbmx7PJ0,56372
19
+ voxcity/geoprocessor/grid.py,sha256=NzUACYEtJ3Wc348ESo_N9VbaXPtSssuF_zad7BtDcmM,56389
20
20
  voxcity/geoprocessor/mesh.py,sha256=r3cRPLgpbhjwgESBemHWWJ5pEWl2KdkRhID6mdLhios,11171
21
21
  voxcity/geoprocessor/network.py,sha256=opb_kpUCAxDd1qtrWPStqR5reYZtVe96XxazNSen7Lk,18851
22
22
  voxcity/geoprocessor/polygon.py,sha256=8Vb2AbkpKYhq1kk2hQMc-gitmUo9pFIe910v4p1vP2g,37772
@@ -28,11 +28,11 @@ voxcity/simulator/view.py,sha256=YufbLuDXrLg1d1dedM6pVyiJ7uHsqY8F2sLLnIoJvB4,749
28
28
  voxcity/utils/__init_.py,sha256=nLYrj2huBbDBNMqfchCwexGP8Tlt9O_XluVDG7MoFkw,98
29
29
  voxcity/utils/lc.py,sha256=RwPd-VY3POV3gTrBhM7TubgGb9MCd3nVah_G8iUEF7k,11562
30
30
  voxcity/utils/material.py,sha256=Vt3IID5Ft54HNJcEC4zi31BCPqi_687X3CSp7rXaRVY,5907
31
- voxcity/utils/visualization.py,sha256=SF8W7sqvBl3sZbB5noWCY9ic2D34Gq01VZYJ9NDNZ4Y,85237
31
+ voxcity/utils/visualization.py,sha256=ouDRx_KLm25P47CP3d85HTURYb6JdfT-N2frkSx24cU,88923
32
32
  voxcity/utils/weather.py,sha256=CFPtoqRTajwMRswswDChwQ3BW1cGsnA3orgWHgz7Ehg,26304
33
- voxcity-0.5.6.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
34
- voxcity-0.5.6.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
35
- voxcity-0.5.6.dist-info/METADATA,sha256=ObAFnBoJ-6FuRW6sqfrcrp9AW7gcKZz-T0W0vd40wcY,25733
36
- voxcity-0.5.6.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
37
- voxcity-0.5.6.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
38
- voxcity-0.5.6.dist-info/RECORD,,
33
+ voxcity-0.5.8.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
34
+ voxcity-0.5.8.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
35
+ voxcity-0.5.8.dist-info/METADATA,sha256=MIylY8nmUI7RuH1_Bruq4xp04c82oUyV4lL5cZ6vOws,25734
36
+ voxcity-0.5.8.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
37
+ voxcity-0.5.8.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
38
+ voxcity-0.5.8.dist-info/RECORD,,