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.
- voxcity/downloader/citygml.py +202 -28
- voxcity/downloader/eubucco.py +91 -14
- voxcity/downloader/gee.py +164 -22
- voxcity/downloader/mbfp.py +55 -9
- voxcity/downloader/oemj.py +110 -24
- voxcity/downloader/omt.py +74 -7
- voxcity/downloader/osm.py +109 -23
- voxcity/downloader/overture.py +108 -23
- voxcity/downloader/utils.py +37 -7
- voxcity/exporter/envimet.py +180 -61
- voxcity/exporter/magicavoxel.py +138 -28
- voxcity/exporter/obj.py +159 -36
- voxcity/generator.py +159 -76
- voxcity/geoprocessor/draw.py +180 -27
- voxcity/geoprocessor/grid.py +178 -38
- voxcity/geoprocessor/mesh.py +347 -43
- voxcity/geoprocessor/network.py +196 -63
- voxcity/geoprocessor/polygon.py +365 -88
- voxcity/geoprocessor/utils.py +283 -72
- voxcity/simulator/solar.py +596 -201
- voxcity/simulator/view.py +278 -723
- voxcity/utils/lc.py +183 -0
- voxcity/utils/material.py +99 -32
- voxcity/utils/visualization.py +2578 -1988
- voxcity/utils/weather.py +816 -615
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/METADATA +11 -13
- voxcity-0.5.16.dist-info/RECORD +38 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/WHEEL +1 -1
- voxcity-0.5.14.dist-info/RECORD +0 -38
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/licenses/LICENSE +0 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/top_level.txt +0 -0
voxcity/exporter/envimet.py
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
"""ENVI-met model file exporter module.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to export voxel city data to ENVI-met INX format.
|
|
4
|
+
ENVI-met is a three-dimensional microclimate model designed to simulate surface-plant-air
|
|
5
|
+
interactions in urban environments.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
- Converts voxel grids to ENVI-met compatible format
|
|
9
|
+
- Handles building heights, vegetation, materials, and terrain
|
|
10
|
+
- Supports telescoping grid for vertical mesh refinement
|
|
11
|
+
- Generates complete INX files with all required parameters
|
|
12
|
+
- Creates plant database (EDB) files for 3D vegetation
|
|
13
|
+
|
|
14
|
+
Main Functions:
|
|
15
|
+
- prepare_grids: Processes input grids for ENVI-met format
|
|
16
|
+
- create_xml_content: Generates INX file XML content
|
|
17
|
+
- export_inx: Main function to export model to INX format
|
|
18
|
+
- generate_edb_file: Creates plant database file
|
|
19
|
+
- array_to_string: Helper functions for grid formatting
|
|
20
|
+
|
|
21
|
+
Dependencies:
|
|
22
|
+
- numpy: For array operations
|
|
23
|
+
- datetime: For timestamp generation
|
|
24
|
+
"""
|
|
25
|
+
|
|
1
26
|
import os
|
|
2
27
|
import numpy as np
|
|
3
28
|
import datetime
|
|
@@ -9,57 +34,98 @@ from ..utils.lc import convert_land_cover
|
|
|
9
34
|
def array_to_string(arr):
|
|
10
35
|
"""Convert a 2D numpy array to a string representation with comma-separated values.
|
|
11
36
|
|
|
37
|
+
This function formats array values for ENVI-met INX files, where each row must be:
|
|
38
|
+
1. Indented by 5 spaces
|
|
39
|
+
2. Values separated by commas
|
|
40
|
+
3. No trailing comma
|
|
41
|
+
|
|
12
42
|
Args:
|
|
13
|
-
arr: 2D numpy array to convert
|
|
43
|
+
arr (numpy.ndarray): 2D numpy array to convert
|
|
14
44
|
|
|
15
45
|
Returns:
|
|
16
|
-
String representation with each row indented by 5 spaces and values comma-separated
|
|
46
|
+
str: String representation with each row indented by 5 spaces and values comma-separated
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
>>> arr = np.array([[1, 2], [3, 4]])
|
|
50
|
+
>>> print(array_to_string(arr))
|
|
51
|
+
1,2
|
|
52
|
+
3,4
|
|
17
53
|
"""
|
|
18
54
|
return '\n'.join(' ' + ','.join(str(cell) for cell in row) for row in arr)
|
|
19
55
|
|
|
20
56
|
def array_to_string_with_value(arr, value):
|
|
21
57
|
"""Convert a 2D numpy array to a string representation, replacing all values with a constant.
|
|
22
58
|
|
|
59
|
+
This function is useful for creating uniform value grids in ENVI-met INX files,
|
|
60
|
+
such as for soil profiles or fixed height indicators.
|
|
61
|
+
|
|
23
62
|
Args:
|
|
24
|
-
arr: 2D numpy array to convert
|
|
25
|
-
value: Value to use for all cells
|
|
63
|
+
arr (numpy.ndarray): 2D numpy array to convert (only shape is used)
|
|
64
|
+
value (str or numeric): Value to use for all cells
|
|
26
65
|
|
|
27
66
|
Returns:
|
|
28
|
-
String representation with each row indented by 5 spaces and constant value repeated
|
|
67
|
+
str: String representation with each row indented by 5 spaces and constant value repeated
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> arr = np.zeros((2, 2))
|
|
71
|
+
>>> print(array_to_string_with_value(arr, '0'))
|
|
72
|
+
0,0
|
|
73
|
+
0,0
|
|
29
74
|
"""
|
|
30
75
|
return '\n'.join(' ' + ','.join(str(value) for cell in row) for row in arr)
|
|
31
76
|
|
|
32
77
|
def array_to_string_int(arr):
|
|
33
78
|
"""Convert a 2D numpy array to a string representation of rounded integers.
|
|
34
79
|
|
|
80
|
+
This function is used for grids that must be represented as integers in ENVI-met,
|
|
81
|
+
such as building numbers or terrain heights. Values are rounded to nearest integer.
|
|
82
|
+
|
|
35
83
|
Args:
|
|
36
|
-
arr: 2D numpy array to convert
|
|
84
|
+
arr (numpy.ndarray): 2D numpy array to convert
|
|
37
85
|
|
|
38
86
|
Returns:
|
|
39
|
-
String representation with each row indented by 5 spaces and values rounded to integers
|
|
87
|
+
str: String representation with each row indented by 5 spaces and values rounded to integers
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
>>> arr = np.array([[1.6, 2.3], [3.7, 4.1]])
|
|
91
|
+
>>> print(array_to_string_int(arr))
|
|
92
|
+
2,2
|
|
93
|
+
4,4
|
|
40
94
|
"""
|
|
41
95
|
return '\n'.join(' ' + ','.join(str(int(cell+0.5)) for cell in row) for row in arr)
|
|
42
96
|
|
|
43
97
|
def prepare_grids(building_height_grid_ori, building_id_grid_ori, canopy_height_grid_ori, land_cover_grid_ori, dem_grid_ori, meshsize, land_cover_source):
|
|
44
98
|
"""Prepare and process input grids for ENVI-met model.
|
|
45
99
|
|
|
100
|
+
This function performs several key transformations on input grids:
|
|
101
|
+
1. Flips grids vertically to match ENVI-met coordinate system
|
|
102
|
+
2. Handles missing values and border conditions
|
|
103
|
+
3. Converts land cover classes to ENVI-met vegetation and material codes
|
|
104
|
+
4. Processes building IDs and heights
|
|
105
|
+
5. Adjusts DEM relative to minimum elevation
|
|
106
|
+
|
|
46
107
|
Args:
|
|
47
|
-
building_height_grid_ori: Original building height grid
|
|
48
|
-
building_id_grid_ori: Original building ID grid
|
|
49
|
-
canopy_height_grid_ori: Original canopy height grid
|
|
50
|
-
land_cover_grid_ori: Original land cover grid
|
|
51
|
-
dem_grid_ori: Original DEM grid
|
|
52
|
-
meshsize: Size of mesh cells
|
|
53
|
-
land_cover_source: Source of land cover data
|
|
108
|
+
building_height_grid_ori (numpy.ndarray): Original building height grid (meters)
|
|
109
|
+
building_id_grid_ori (numpy.ndarray): Original building ID grid
|
|
110
|
+
canopy_height_grid_ori (numpy.ndarray): Original canopy height grid (meters)
|
|
111
|
+
land_cover_grid_ori (numpy.ndarray): Original land cover grid (class codes)
|
|
112
|
+
dem_grid_ori (numpy.ndarray): Original DEM grid (meters)
|
|
113
|
+
meshsize (float): Size of mesh cells in meters
|
|
114
|
+
land_cover_source (str): Source of land cover data for class conversion
|
|
54
115
|
|
|
55
116
|
Returns:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
117
|
+
tuple: Processed grids:
|
|
118
|
+
- building_height_grid (numpy.ndarray): Building heights
|
|
119
|
+
- building_id_grid (numpy.ndarray): Building IDs
|
|
120
|
+
- land_cover_veg_grid (numpy.ndarray): Vegetation codes
|
|
121
|
+
- land_cover_mat_grid (numpy.ndarray): Material codes
|
|
122
|
+
- canopy_height_grid (numpy.ndarray): Canopy heights
|
|
123
|
+
- dem_grid (numpy.ndarray): Processed DEM
|
|
124
|
+
|
|
125
|
+
Notes:
|
|
126
|
+
- Building heights at grid borders are set to 0
|
|
127
|
+
- DEM is normalized to minimum elevation
|
|
128
|
+
- Land cover is converted based on source-specific mapping
|
|
63
129
|
"""
|
|
64
130
|
# Flip building height grid vertically and replace NaN with 10m height
|
|
65
131
|
building_height_grid = np.flipud(np.nan_to_num(building_height_grid_ori, nan=10.0)).copy()
|
|
@@ -118,26 +184,35 @@ def prepare_grids(building_height_grid_ori, building_id_grid_ori, canopy_height_
|
|
|
118
184
|
def create_xml_content(building_height_grid, building_id_grid, land_cover_veg_grid, land_cover_mat_grid, canopy_height_grid, dem_grid, meshsize, rectangle_vertices, **kwargs):
|
|
119
185
|
"""Create XML content for ENVI-met INX file.
|
|
120
186
|
|
|
187
|
+
This function generates the complete XML structure for an ENVI-met INX file,
|
|
188
|
+
including model metadata, geometry settings, and all required grid data.
|
|
189
|
+
|
|
121
190
|
Args:
|
|
122
|
-
building_height_grid: Processed building heights
|
|
123
|
-
building_id_grid: Processed building IDs
|
|
124
|
-
land_cover_veg_grid: Vegetation codes grid
|
|
125
|
-
land_cover_mat_grid: Material codes grid
|
|
126
|
-
canopy_height_grid: Processed canopy heights
|
|
127
|
-
dem_grid: Processed DEM
|
|
128
|
-
meshsize: Size of mesh cells
|
|
129
|
-
rectangle_vertices: Vertices defining model area
|
|
191
|
+
building_height_grid (numpy.ndarray): Processed building heights
|
|
192
|
+
building_id_grid (numpy.ndarray): Processed building IDs
|
|
193
|
+
land_cover_veg_grid (numpy.ndarray): Vegetation codes grid
|
|
194
|
+
land_cover_mat_grid (numpy.ndarray): Material codes grid
|
|
195
|
+
canopy_height_grid (numpy.ndarray): Processed canopy heights
|
|
196
|
+
dem_grid (numpy.ndarray): Processed DEM
|
|
197
|
+
meshsize (float): Size of mesh cells in meters
|
|
198
|
+
rectangle_vertices (list): Vertices defining model area as [(lon, lat), ...]
|
|
130
199
|
**kwargs: Additional keyword arguments:
|
|
131
|
-
- author_name: Name of model author
|
|
132
|
-
- model_description: Description of model
|
|
133
|
-
- domain_building_max_height_ratio: Ratio of domain height to max building height
|
|
134
|
-
- useTelescoping_grid: Whether to use telescoping grid
|
|
135
|
-
- verticalStretch: Vertical stretch factor
|
|
136
|
-
- startStretch: Height to start stretching
|
|
137
|
-
- min_grids_Z: Minimum vertical grid cells
|
|
200
|
+
- author_name (str): Name of model author
|
|
201
|
+
- model_description (str): Description of model
|
|
202
|
+
- domain_building_max_height_ratio (float): Ratio of domain height to max building height
|
|
203
|
+
- useTelescoping_grid (bool): Whether to use telescoping grid
|
|
204
|
+
- verticalStretch (float): Vertical stretch factor
|
|
205
|
+
- startStretch (float): Height to start stretching
|
|
206
|
+
- min_grids_Z (int): Minimum vertical grid cells
|
|
138
207
|
|
|
139
208
|
Returns:
|
|
140
|
-
|
|
209
|
+
str: Complete XML content for INX file
|
|
210
|
+
|
|
211
|
+
Notes:
|
|
212
|
+
- Automatically determines location information from coordinates
|
|
213
|
+
- Handles both telescoping and uniform vertical grids
|
|
214
|
+
- Sets appropriate defaults for optional parameters
|
|
215
|
+
- Includes all required ENVI-met model settings
|
|
141
216
|
"""
|
|
142
217
|
# XML template defining the structure of an ENVI-met INX file
|
|
143
218
|
xml_template = """<ENVI-MET_Datafile>
|
|
@@ -366,11 +441,19 @@ def create_xml_content(building_height_grid, building_id_grid, land_cover_veg_gr
|
|
|
366
441
|
return xml_template
|
|
367
442
|
|
|
368
443
|
def save_file(content, output_file_path):
|
|
369
|
-
"""Save content to a file.
|
|
444
|
+
"""Save content to a file with UTF-8 encoding.
|
|
445
|
+
|
|
446
|
+
This function ensures consistent file encoding and error handling when
|
|
447
|
+
saving ENVI-met files.
|
|
370
448
|
|
|
371
449
|
Args:
|
|
372
|
-
content: String content to save
|
|
373
|
-
output_file_path: Path to save file to
|
|
450
|
+
content (str): String content to save
|
|
451
|
+
output_file_path (str): Path to save file to
|
|
452
|
+
|
|
453
|
+
Notes:
|
|
454
|
+
- Creates parent directories if they don't exist
|
|
455
|
+
- Uses UTF-8 encoding for compatibility
|
|
456
|
+
- Overwrites existing file if present
|
|
374
457
|
"""
|
|
375
458
|
with open(output_file_path, 'w', encoding='utf-8') as file:
|
|
376
459
|
file.write(content)
|
|
@@ -378,19 +461,28 @@ def save_file(content, output_file_path):
|
|
|
378
461
|
def export_inx(building_height_grid_ori, building_id_grid_ori, canopy_height_grid_ori, land_cover_grid_ori, dem_grid_ori, meshsize, land_cover_source, rectangle_vertices, **kwargs):
|
|
379
462
|
"""Export model data to ENVI-met INX file format.
|
|
380
463
|
|
|
464
|
+
This is the main function for exporting voxel city data to ENVI-met format.
|
|
465
|
+
It coordinates the entire export process from grid preparation to file saving.
|
|
466
|
+
|
|
381
467
|
Args:
|
|
382
|
-
building_height_grid_ori: Original building height grid
|
|
383
|
-
building_id_grid_ori: Original building ID grid
|
|
384
|
-
canopy_height_grid_ori: Original canopy height grid
|
|
385
|
-
land_cover_grid_ori: Original land cover grid
|
|
386
|
-
dem_grid_ori: Original DEM grid
|
|
387
|
-
meshsize: Size of mesh cells
|
|
388
|
-
land_cover_source: Source of land cover data
|
|
389
|
-
rectangle_vertices: Vertices defining model area
|
|
468
|
+
building_height_grid_ori (numpy.ndarray): Original building height grid
|
|
469
|
+
building_id_grid_ori (numpy.ndarray): Original building ID grid
|
|
470
|
+
canopy_height_grid_ori (numpy.ndarray): Original canopy height grid
|
|
471
|
+
land_cover_grid_ori (numpy.ndarray): Original land cover grid
|
|
472
|
+
dem_grid_ori (numpy.ndarray): Original DEM grid
|
|
473
|
+
meshsize (float): Size of mesh cells in meters
|
|
474
|
+
land_cover_source (str): Source of land cover data
|
|
475
|
+
rectangle_vertices (list): Vertices defining model area
|
|
390
476
|
**kwargs: Additional keyword arguments:
|
|
391
|
-
- output_directory: Directory to save output
|
|
392
|
-
- file_basename: Base filename for output
|
|
477
|
+
- output_directory (str): Directory to save output
|
|
478
|
+
- file_basename (str): Base filename for output
|
|
393
479
|
- Other args passed to create_xml_content()
|
|
480
|
+
|
|
481
|
+
Notes:
|
|
482
|
+
- Creates output directory if it doesn't exist
|
|
483
|
+
- Handles grid preparation and transformation
|
|
484
|
+
- Generates complete INX file with all required data
|
|
485
|
+
- Uses standardized file naming convention
|
|
394
486
|
"""
|
|
395
487
|
# Prepare grids
|
|
396
488
|
building_height_grid_inx, building_id_grid, land_cover_veg_grid_inx, land_cover_mat_grid_inx, canopy_height_grid_inx, dem_grid_inx = prepare_grids(
|
|
@@ -409,10 +501,21 @@ def export_inx(building_height_grid_ori, building_id_grid_ori, canopy_height_gri
|
|
|
409
501
|
def generate_edb_file(**kwargs):
|
|
410
502
|
"""Generate ENVI-met database file for 3D plants.
|
|
411
503
|
|
|
504
|
+
Creates a plant database file (EDB) containing definitions for trees of
|
|
505
|
+
different heights with customizable leaf area density profiles.
|
|
506
|
+
|
|
412
507
|
Args:
|
|
413
508
|
**kwargs: Keyword arguments:
|
|
414
|
-
- lad: Leaf area density (default 1.0)
|
|
415
|
-
- trunk_height_ratio: Ratio of trunk height to total height
|
|
509
|
+
- lad (float): Leaf area density in m²/m³ (default 1.0)
|
|
510
|
+
- trunk_height_ratio (float): Ratio of trunk height to total height
|
|
511
|
+
(default 11.76/19.98)
|
|
512
|
+
|
|
513
|
+
Notes:
|
|
514
|
+
- Generates plants for heights from 1-50m
|
|
515
|
+
- Uses standardized plant IDs in format 'HxxW01'
|
|
516
|
+
- Includes physical properties like wood density
|
|
517
|
+
- Sets seasonal variation profiles
|
|
518
|
+
- Creates complete ENVI-met plant database format
|
|
416
519
|
"""
|
|
417
520
|
|
|
418
521
|
lad = kwargs.get('lad')
|
|
@@ -527,13 +630,21 @@ def generate_edb_file(**kwargs):
|
|
|
527
630
|
def generate_lad_profile(height, trunk_height_ratio, lad = '1.00000'):
|
|
528
631
|
"""Generate leaf area density profile for a plant.
|
|
529
632
|
|
|
633
|
+
Creates a vertical profile of leaf area density (LAD) values for ENVI-met
|
|
634
|
+
plant definitions, accounting for trunk space and crown distribution.
|
|
635
|
+
|
|
530
636
|
Args:
|
|
531
|
-
height: Total height of plant
|
|
532
|
-
trunk_height_ratio: Ratio of trunk height to total height
|
|
533
|
-
lad: Leaf area density value as string (default '1.00000')
|
|
637
|
+
height (int): Total height of plant in meters
|
|
638
|
+
trunk_height_ratio (float): Ratio of trunk height to total height
|
|
639
|
+
lad (str): Leaf area density value as string (default '1.00000')
|
|
534
640
|
|
|
535
641
|
Returns:
|
|
536
|
-
|
|
642
|
+
str: LAD profile data formatted for ENVI-met EDB file
|
|
643
|
+
|
|
644
|
+
Notes:
|
|
645
|
+
- LAD values start above trunk height
|
|
646
|
+
- Uses 5-space indentation for ENVI-met format
|
|
647
|
+
- Profile follows format: "z-level,x,y,LAD"
|
|
537
648
|
"""
|
|
538
649
|
lad_profile = []
|
|
539
650
|
# Only add LAD values above trunk height
|
|
@@ -545,14 +656,22 @@ def generate_lad_profile(height, trunk_height_ratio, lad = '1.00000'):
|
|
|
545
656
|
def find_min_n(a, r, S_target, max_n=1000000):
|
|
546
657
|
"""Find minimum number of terms needed in geometric series to exceed target sum.
|
|
547
658
|
|
|
659
|
+
Used for calculating telescoping grid parameters to achieve desired domain height.
|
|
660
|
+
Solves for n in the equation: a(1-r^n)/(1-r) > S_target
|
|
661
|
+
|
|
548
662
|
Args:
|
|
549
|
-
a: First term of series
|
|
550
|
-
r: Common ratio
|
|
551
|
-
S_target: Target sum to exceed
|
|
552
|
-
max_n: Maximum number of terms to try (default 1000000)
|
|
663
|
+
a (float): First term of series (base cell size)
|
|
664
|
+
r (float): Common ratio (stretch factor)
|
|
665
|
+
S_target (float): Target sum to exceed (desired height)
|
|
666
|
+
max_n (int): Maximum number of terms to try (default 1000000)
|
|
553
667
|
|
|
554
668
|
Returns:
|
|
555
|
-
Minimum number of terms needed, or None if not possible within max_n
|
|
669
|
+
int or None: Minimum number of terms needed, or None if not possible within max_n
|
|
670
|
+
|
|
671
|
+
Notes:
|
|
672
|
+
- Handles special case of r=1 (arithmetic series)
|
|
673
|
+
- Protects against overflow with large exponents
|
|
674
|
+
- Returns None if solution not found within max_n terms
|
|
556
675
|
"""
|
|
557
676
|
n = 1
|
|
558
677
|
while n <= max_n:
|
voxcity/exporter/magicavoxel.py
CHANGED
|
@@ -3,6 +3,19 @@ Module for handling MagicaVoxel .vox files.
|
|
|
3
3
|
|
|
4
4
|
This module provides functionality for converting 3D numpy arrays to MagicaVoxel .vox files,
|
|
5
5
|
including color mapping and splitting large models into smaller chunks.
|
|
6
|
+
|
|
7
|
+
The module handles:
|
|
8
|
+
- Color map conversion and optimization
|
|
9
|
+
- Large model splitting into MagicaVoxel-compatible chunks
|
|
10
|
+
- Custom palette creation
|
|
11
|
+
- Coordinate system transformation
|
|
12
|
+
- Batch export of multiple .vox files
|
|
13
|
+
|
|
14
|
+
Key Features:
|
|
15
|
+
- Supports models larger than MagicaVoxel's 256³ size limit
|
|
16
|
+
- Automatic color palette optimization
|
|
17
|
+
- Preserves color mapping across chunks
|
|
18
|
+
- Handles coordinate system differences between numpy and MagicaVoxel
|
|
6
19
|
"""
|
|
7
20
|
|
|
8
21
|
# Required imports for voxel file handling and array manipulation
|
|
@@ -10,21 +23,35 @@ import numpy as np
|
|
|
10
23
|
from pyvox.models import Vox
|
|
11
24
|
from pyvox.writer import VoxWriter
|
|
12
25
|
import os
|
|
13
|
-
from ..utils.visualization import
|
|
26
|
+
from ..utils.visualization import get_voxel_color_map
|
|
14
27
|
|
|
15
28
|
def convert_colormap_and_array(original_map, original_array):
|
|
16
29
|
"""
|
|
17
30
|
Convert a color map with arbitrary indices to sequential indices starting from 0
|
|
18
31
|
and update the corresponding 3D numpy array.
|
|
19
32
|
|
|
33
|
+
This function optimizes the color mapping by:
|
|
34
|
+
1. Converting arbitrary color indices to sequential ones
|
|
35
|
+
2. Creating a new mapping that preserves color relationships
|
|
36
|
+
3. Updating the voxel array to use the new sequential indices
|
|
37
|
+
|
|
20
38
|
Args:
|
|
21
|
-
original_map (dict): Dictionary with integer keys and RGB color value lists
|
|
22
|
-
|
|
39
|
+
original_map (dict): Dictionary with integer keys and RGB color value lists.
|
|
40
|
+
Each key is a color index, and each value is a list of [R,G,B] values.
|
|
41
|
+
original_array (numpy.ndarray): 3D array with integer values corresponding to color map keys.
|
|
42
|
+
The array contains indices that match the keys in original_map.
|
|
23
43
|
|
|
24
44
|
Returns:
|
|
25
45
|
tuple: (new_color_map, new_array)
|
|
26
46
|
- new_color_map (dict): Color map with sequential indices starting from 0
|
|
27
|
-
- new_array (numpy.ndarray): Updated array with new indices
|
|
47
|
+
- new_array (numpy.ndarray): Updated array with new sequential indices
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> color_map = {5: [255,0,0], 10: [0,255,0]}
|
|
51
|
+
>>> array = np.array([[[5,10],[10,5]]])
|
|
52
|
+
>>> new_map, new_array = convert_colormap_and_array(color_map, array)
|
|
53
|
+
>>> print(new_map)
|
|
54
|
+
{0: [255,0,0], 1: [0,255,0]}
|
|
28
55
|
"""
|
|
29
56
|
# Get all the keys and sort them
|
|
30
57
|
keys = sorted(original_map.keys())
|
|
@@ -48,13 +75,25 @@ def convert_colormap_and_array(original_map, original_array):
|
|
|
48
75
|
|
|
49
76
|
def create_custom_palette(color_map):
|
|
50
77
|
"""
|
|
51
|
-
Create a palette array from a color map dictionary.
|
|
78
|
+
Create a palette array from a color map dictionary suitable for MagicaVoxel format.
|
|
79
|
+
|
|
80
|
+
This function:
|
|
81
|
+
1. Creates a 256x4 RGBA palette array
|
|
82
|
+
2. Sets full opacity (alpha=255) for all colors by default
|
|
83
|
+
3. Reserves index 0 for transparent black (void)
|
|
84
|
+
4. Maps colors sequentially starting from index 1
|
|
52
85
|
|
|
53
86
|
Args:
|
|
54
|
-
color_map (dict): Dictionary mapping indices to RGB color values
|
|
87
|
+
color_map (dict): Dictionary mapping indices to RGB color values.
|
|
88
|
+
Each value should be a list of 3 integers [R,G,B] in range 0-255.
|
|
55
89
|
|
|
56
90
|
Returns:
|
|
57
|
-
numpy.ndarray: 256x4 array containing RGBA color values
|
|
91
|
+
numpy.ndarray: 256x4 array containing RGBA color values.
|
|
92
|
+
- Shape: (256, 4)
|
|
93
|
+
- Type: uint8
|
|
94
|
+
- Format: [R,G,B,A] for each color
|
|
95
|
+
- Index 0: [0,0,0,0] (transparent)
|
|
96
|
+
- Indices 1-255: Colors from color_map with alpha=255
|
|
58
97
|
"""
|
|
59
98
|
# Initialize empty palette with alpha channel
|
|
60
99
|
palette = np.zeros((256, 4), dtype=np.uint8)
|
|
@@ -68,13 +107,20 @@ def create_custom_palette(color_map):
|
|
|
68
107
|
|
|
69
108
|
def create_mapping(color_map):
|
|
70
109
|
"""
|
|
71
|
-
Create a mapping from color map keys to sequential indices.
|
|
110
|
+
Create a mapping from color map keys to sequential indices for MagicaVoxel compatibility.
|
|
111
|
+
|
|
112
|
+
Creates a mapping that:
|
|
113
|
+
- Reserves index 0 for void space
|
|
114
|
+
- Reserves index 1 (typically for special use)
|
|
115
|
+
- Maps colors sequentially starting from index 2
|
|
72
116
|
|
|
73
117
|
Args:
|
|
74
|
-
color_map (dict): Dictionary mapping indices to RGB color values
|
|
118
|
+
color_map (dict): Dictionary mapping indices to RGB color values.
|
|
119
|
+
The keys can be any integers, they will be remapped sequentially.
|
|
75
120
|
|
|
76
121
|
Returns:
|
|
77
|
-
dict: Mapping from original indices to sequential indices starting at 2
|
|
122
|
+
dict: Mapping from original indices to sequential indices starting at 2.
|
|
123
|
+
Example: {original_index1: 2, original_index2: 3, ...}
|
|
78
124
|
"""
|
|
79
125
|
# Create mapping starting at index 2 (0 is void, 1 is reserved)
|
|
80
126
|
return {value: i+2 for i, value in enumerate(color_map.keys())}
|
|
@@ -83,12 +129,26 @@ def split_array(array, max_size=255):
|
|
|
83
129
|
"""
|
|
84
130
|
Split a 3D array into smaller chunks that fit within MagicaVoxel size limits.
|
|
85
131
|
|
|
132
|
+
This function handles large voxel models by:
|
|
133
|
+
1. Calculating required splits in each dimension
|
|
134
|
+
2. Dividing the model into chunks of max_size or smaller
|
|
135
|
+
3. Yielding each chunk with its position information
|
|
136
|
+
|
|
86
137
|
Args:
|
|
87
|
-
array (numpy.ndarray): 3D array to split
|
|
88
|
-
|
|
138
|
+
array (numpy.ndarray): 3D array to split.
|
|
139
|
+
Can be any size, will be split into chunks of max_size or smaller.
|
|
140
|
+
max_size (int, optional): Maximum size allowed for each dimension.
|
|
141
|
+
Defaults to 255 (MagicaVoxel's limit is 256).
|
|
89
142
|
|
|
90
143
|
Yields:
|
|
91
|
-
tuple: (sub_array, (i,j,k))
|
|
144
|
+
tuple: (sub_array, (i,j,k))
|
|
145
|
+
- sub_array: numpy.ndarray of size <= max_size in each dimension
|
|
146
|
+
- (i,j,k): tuple of indices indicating chunk position in the original model
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
>>> array = np.ones((300, 300, 300))
|
|
150
|
+
>>> for chunk, (i,j,k) in split_array(array):
|
|
151
|
+
... print(f"Chunk at position {i},{j},{k} has shape {chunk.shape}")
|
|
92
152
|
"""
|
|
93
153
|
# Calculate number of splits needed in each dimension
|
|
94
154
|
x, y, z = array.shape
|
|
@@ -113,13 +173,29 @@ def numpy_to_vox(array, color_map, output_file):
|
|
|
113
173
|
"""
|
|
114
174
|
Convert a numpy array to a MagicaVoxel .vox file.
|
|
115
175
|
|
|
176
|
+
This function handles the complete conversion process:
|
|
177
|
+
1. Creates a custom color palette from the color map
|
|
178
|
+
2. Generates value mapping for voxel indices
|
|
179
|
+
3. Transforms coordinates to match MagicaVoxel's system
|
|
180
|
+
4. Saves the model in .vox format
|
|
181
|
+
|
|
116
182
|
Args:
|
|
117
|
-
array (numpy.ndarray): 3D array containing voxel data
|
|
118
|
-
|
|
119
|
-
|
|
183
|
+
array (numpy.ndarray): 3D array containing voxel data.
|
|
184
|
+
Values should correspond to keys in color_map.
|
|
185
|
+
color_map (dict): Dictionary mapping indices to RGB color values.
|
|
186
|
+
Each value should be a list of [R,G,B] values (0-255).
|
|
187
|
+
output_file (str): Path to save the .vox file.
|
|
188
|
+
Will overwrite if file exists.
|
|
120
189
|
|
|
121
190
|
Returns:
|
|
122
|
-
tuple: (value_mapping, palette, shape)
|
|
191
|
+
tuple: (value_mapping, palette, shape)
|
|
192
|
+
- value_mapping: dict mapping original indices to MagicaVoxel indices
|
|
193
|
+
- palette: numpy.ndarray of shape (256,4) containing RGBA values
|
|
194
|
+
- shape: tuple of (width, height, depth) of the output model
|
|
195
|
+
|
|
196
|
+
Note:
|
|
197
|
+
- Coordinates are transformed to match MagicaVoxel's coordinate system
|
|
198
|
+
- Z-axis is flipped and axes are reordered in the process
|
|
123
199
|
"""
|
|
124
200
|
# Create color palette and value mapping
|
|
125
201
|
palette = create_custom_palette(color_map)
|
|
@@ -142,15 +218,34 @@ def export_large_voxel_model(array, color_map, output_prefix, max_size=255, base
|
|
|
142
218
|
"""
|
|
143
219
|
Export a large voxel model by splitting it into multiple .vox files.
|
|
144
220
|
|
|
221
|
+
This function handles models of any size by:
|
|
222
|
+
1. Creating the output directory if needed
|
|
223
|
+
2. Splitting the model into manageable chunks
|
|
224
|
+
3. Saving each chunk as a separate .vox file
|
|
225
|
+
4. Maintaining consistent color mapping across all chunks
|
|
226
|
+
|
|
145
227
|
Args:
|
|
146
|
-
array (numpy.ndarray): 3D array containing voxel data
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
228
|
+
array (numpy.ndarray): 3D array containing voxel data.
|
|
229
|
+
Can be any size, will be split into chunks if needed.
|
|
230
|
+
color_map (dict): Dictionary mapping indices to RGB color values.
|
|
231
|
+
Each value should be a list of [R,G,B] values (0-255).
|
|
232
|
+
output_prefix (str): Directory to save the .vox files.
|
|
233
|
+
Will be created if it doesn't exist.
|
|
234
|
+
max_size (int, optional): Maximum size allowed for each dimension.
|
|
235
|
+
Defaults to 255 (MagicaVoxel's limit is 256).
|
|
236
|
+
base_filename (str, optional): Base name for the output files.
|
|
237
|
+
Defaults to 'chunk'. Final filenames will be {base_filename}_{i}_{j}_{k}.vox
|
|
151
238
|
|
|
152
239
|
Returns:
|
|
153
|
-
tuple: (value_mapping, palette)
|
|
240
|
+
tuple: (value_mapping, palette)
|
|
241
|
+
- value_mapping: dict mapping original indices to MagicaVoxel indices
|
|
242
|
+
- palette: numpy.ndarray of shape (256,4) containing RGBA values
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
>>> array = np.ones((500,500,500))
|
|
246
|
+
>>> color_map = {1: [255,0,0]}
|
|
247
|
+
>>> export_large_voxel_model(array, color_map, "output/model")
|
|
248
|
+
# Creates files like: output/model/chunk_0_0_0.vox, chunk_0_0_1.vox, etc.
|
|
154
249
|
"""
|
|
155
250
|
# Create output directory if it doesn't exist
|
|
156
251
|
os.makedirs(output_prefix, exist_ok=True)
|
|
@@ -168,16 +263,31 @@ def export_magicavoxel_vox(array, output_dir, base_filename='chunk', voxel_color
|
|
|
168
263
|
"""
|
|
169
264
|
Export a voxel model to MagicaVoxel .vox format.
|
|
170
265
|
|
|
266
|
+
This is the main entry point for voxel model export. It handles:
|
|
267
|
+
1. Color map management (using default if none provided)
|
|
268
|
+
2. Color index optimization
|
|
269
|
+
3. Large model splitting and export
|
|
270
|
+
4. Progress reporting
|
|
271
|
+
|
|
171
272
|
Args:
|
|
172
|
-
array (numpy.ndarray): 3D array containing voxel data
|
|
173
|
-
|
|
174
|
-
|
|
273
|
+
array (numpy.ndarray): 3D array containing voxel data.
|
|
274
|
+
Values should correspond to keys in voxel_color_map.
|
|
275
|
+
output_dir (str): Directory to save the .vox files.
|
|
276
|
+
Will be created if it doesn't exist.
|
|
277
|
+
base_filename (str, optional): Base name for the output files.
|
|
278
|
+
Defaults to 'chunk'. Used when model is split into multiple files.
|
|
175
279
|
voxel_color_map (dict, optional): Dictionary mapping indices to RGB color values.
|
|
176
|
-
If None, uses default color map.
|
|
280
|
+
If None, uses default color map from utils.visualization.
|
|
281
|
+
Each value should be a list of [R,G,B] values (0-255).
|
|
282
|
+
|
|
283
|
+
Note:
|
|
284
|
+
- Large models are automatically split into multiple files
|
|
285
|
+
- Color mapping is optimized and made sequential
|
|
286
|
+
- Progress information is printed to stdout
|
|
177
287
|
"""
|
|
178
288
|
# Use default color map if none provided
|
|
179
289
|
if voxel_color_map is None:
|
|
180
|
-
voxel_color_map =
|
|
290
|
+
voxel_color_map = get_voxel_color_map()
|
|
181
291
|
|
|
182
292
|
# Convert color map and array to sequential indices
|
|
183
293
|
converted_voxel_color_map, converted_array = convert_colormap_and_array(voxel_color_map, array)
|