Skip to content

Commit 22a5995

Browse files
committed
added a simple program to export files in .vdb format
1 parent abd0feb commit 22a5995

File tree

6 files changed

+405
-1
lines changed

6 files changed

+405
-1
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ Contributors:
2727
* Zhiyi Wu <xiki-tempula>
2828
* Olivier Languin-Cattoën <ollyfutur>
2929
* Andrés Montoya <conradolandia> (logo)
30+
* Shreejan Dolai <spyke7>

CHANGELOG

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ The rules for this file:
1313
* accompany each entry with github issue/PR number (Issue #xyz)
1414

1515
------------------------------------------------------------------------------
16+
27/12/2025 IAlibay, spyke7, orbeckst
17+
* 1.1.0
18+
19+
Changes
20+
21+
* Added `OpenVDB.py` inside `gridData` to simply export and write in .vdb format
22+
* Added `test_vdb.py` inside `gridData\tests`
23+
24+
Fixes
25+
26+
* Adding openVDB formats (Issue #141)
27+
28+
1629
??/??/???? IAlibay, ollyfutur, conradolandia, orbeckst
1730
* 1.1.0
1831

gridData/OpenVDB.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
r"""
2+
:mod:`~gridData.OpenVDB` --- routines to write OpenVDB files
3+
=============================================================
4+
5+
The OpenVDB format is used by Blender and other VFX software for
6+
volumetric data. See https://www.openvdb.org
7+
8+
.. Note:: This module implements a simple writer for 3D regular grids,
9+
sufficient to export density data for visualization in Blender.
10+
11+
The OpenVDB format uses a sparse tree structure to efficiently store
12+
volumetric data. It is the native format for Blender's volume system.
13+
14+
15+
Writing OpenVDB files
16+
---------------------
17+
18+
If you have a :class:`~gridData.core.Grid` object, you can write it to
19+
OpenVDB format::
20+
21+
from gridData import Grid
22+
g = Grid("data.dx")
23+
g.export("data.vdb")
24+
25+
This will create a file that can be imported directly into Blender
26+
(File -> Import -> OpenVDB).
27+
28+
29+
Building an OpenVDB field from a numpy array
30+
---------------------------------------------
31+
32+
Requires:
33+
34+
grid
35+
numpy 3D array
36+
origin
37+
cartesian coordinates of the center of the (0,0,0) grid cell
38+
delta
39+
n x n array with the length of a grid cell along each axis
40+
41+
Example::
42+
43+
import OpenVDB
44+
vdb_field = OpenVDB.field('density')
45+
vdb_field.populate(grid, origin, delta)
46+
vdb_field.write('output.vdb')
47+
48+
49+
Classes and functions
50+
---------------------
51+
52+
"""
53+
54+
import numpy
55+
import warnings
56+
57+
try:
58+
import pyopenvdb as vdb
59+
except ImportError:
60+
vdb = None
61+
62+
63+
class field(object):
64+
"""OpenVDB field object for writing volumetric data.
65+
66+
This class provides a simple interface to write 3D grid data to
67+
OpenVDB format, which can be imported into Blender and other
68+
VFX software.
69+
70+
The field object holds grid data and metadata, and can write it
71+
to a .vdb file.
72+
73+
Example
74+
-------
75+
Create a field and write it::
76+
77+
vdb_field = OpenVDB.field('density')
78+
vdb_field.populate(grid, origin, delta)
79+
vdb_field.write('output.vdb')
80+
81+
Or use directly from Grid::
82+
83+
g = Grid(...)
84+
g.export('output.vdb', format='vdb')
85+
86+
"""
87+
88+
def __init__(self, name='density'):
89+
"""Initialize an OpenVDB field.
90+
91+
Parameters
92+
----------
93+
name : str
94+
Name of the grid (will be visible in Blender)
95+
96+
"""
97+
if vdb is None:
98+
raise ImportError(
99+
"pyopenvdb is required to write VDB files. "
100+
)
101+
self.name = name
102+
self.grid = None
103+
self.origin = None
104+
self.delta = None
105+
106+
def populate(self, grid, origin, delta):
107+
"""Populate the field with grid data.
108+
109+
Parameters
110+
----------
111+
grid : numpy.ndarray
112+
3D numpy array with the data
113+
origin : numpy.ndarray
114+
Coordinates of the center of grid cell [0,0,0]
115+
delta : numpy.ndarray
116+
Grid spacing (can be 1D array or diagonal matrix)
117+
118+
Raises
119+
------
120+
ValueError
121+
If grid is not 3D
122+
123+
"""
124+
grid = numpy.asarray(grid)
125+
if grid.ndim != 3:
126+
raise ValueError(
127+
"OpenVDB only supports 3D grids, got {}D".format(grid.ndim))
128+
129+
self.grid = grid.astype(numpy.float32) # OpenVDB uses float32
130+
self.origin = numpy.asarray(origin)
131+
132+
# Handle delta: could be 1D array or diagonal matrix
133+
delta = numpy.asarray(delta)
134+
if delta.ndim == 2:
135+
# Extract diagonal if it's a matrix
136+
self.delta = numpy.array([delta[i, i] for i in range(3)])
137+
else:
138+
self.delta = delta
139+
140+
def write(self, filename):
141+
"""Write the field to an OpenVDB file.
142+
143+
Parameters
144+
----------
145+
filename : str
146+
Output filename (should end in .vdb)
147+
148+
"""
149+
if self.grid is None:
150+
raise ValueError("No data to write. Use populate() first.")
151+
152+
# Create OpenVDB grid
153+
vdb_grid = vdb.FloatGrid()
154+
vdb_grid.name = self.name
155+
156+
# Set up transform (voxel size and position)
157+
# Check for uniform spacing
158+
if not numpy.allclose(self.delta, self.delta[0]):
159+
warnings.warn(
160+
"Non-uniform grid spacing {}. Using average spacing.".format(
161+
self.delta))
162+
voxel_size = float(numpy.mean(self.delta))
163+
else:
164+
voxel_size = float(self.delta[0])
165+
166+
# Create linear transform with uniform voxel size
167+
transform = vdb.createLinearTransform(voxelSize=voxel_size)
168+
169+
# OpenVDB transform is at corner of voxel [0,0,0],
170+
# but GridDataFormats origin is at center of voxel [0,0,0]
171+
corner_origin = self.origin - 0.5 * self.delta
172+
transform.translate(corner_origin)
173+
vdb_grid.transform = transform
174+
175+
# Set background value for sparse storage
176+
vdb_grid.background = 0.0
177+
178+
# Populate the grid
179+
180+
accessor = vdb_grid.getAccessor()
181+
threshold = 1e-10
182+
183+
for i in range(self.grid.shape[0]):
184+
for j in range(self.grid.shape[1]):
185+
for k in range(self.grid.shape[2]):
186+
value = float(self.grid[i, j, k])
187+
if abs(value) > threshold:
188+
accessor.setValueOn((i, j, k), value)
189+
190+
vdb.write(filename, grids=[vdb_grid])

gridData/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@
110110
from . import OpenDX
111111
from . import gOpenMol
112112
from . import mrc
113+
from . import OpenVDB
113114

114-
__all__ = ['Grid', 'OpenDX', 'gOpenMol', 'mrc']
115+
__all__ = ['Grid', 'OpenDX', 'gOpenMol', 'mrc', 'OpenVDB']
115116

116117
from importlib.metadata import version
117118
__version__ = version("GridDataFormats")

gridData/core.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from . import OpenDX
3636
from . import gOpenMol
3737
from . import mrc
38+
from . import OpenVDB
3839

3940

4041
def _grid(x):
@@ -203,6 +204,7 @@ def __init__(self, grid=None, edges=None, origin=None, delta=None,
203204
'PKL': self._export_python,
204205
'PICKLE': self._export_python, # compatibility
205206
'PYTHON': self._export_python, # compatibility
207+
'VDB': self._export_vdb,
206208
}
207209
self._loaders = {
208210
'CCP4': self._load_mrc,
@@ -676,7 +678,26 @@ def _export_dx(self, filename, type=None, typequote='"', **kwargs):
676678
if ext == '.gz':
677679
filename = root + ext
678680
dx.write(filename)
681+
682+
def _export_vdb(self, filename, **kwargs):
683+
"""Export the density grid to an OpenVDB file.
679684
685+
The file format is compatible with Blender's volume system.
686+
Only 3D grids are supported.
687+
688+
For the file format see https://www.openvdb.org
689+
"""
690+
if self.grid.ndim != 3:
691+
raise ValueError(
692+
"OpenVDB export requires a 3D grid, got {}D".format(self.grid.ndim))
693+
694+
# Get grid name from metadata if available
695+
grid_name = self.metadata.get('name', 'density')
696+
697+
# Create and populate VDB field
698+
vdb_field = OpenVDB.field(grid_name)
699+
vdb_field.populate(self.grid, self.origin, self.delta)
700+
vdb_field.write(filename)
680701
def save(self, filename):
681702
"""Save a grid object to `filename` and add ".pickle" extension.
682703

0 commit comments

Comments
 (0)