-
Notifications
You must be signed in to change notification settings - Fork 22
added a simple program to export files in .vdb format #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
22a5995
c871534
d809198
242edb5
79c96de
0649210
f83f709
09b88a3
3c93f8d
1765ae1
31831cb
c55366b
ea446d7
1e2fd80
145a1dd
4bd1d8a
d010391
c8e643d
f87d3d8
97aef4b
b6e5568
53a6017
2c75469
acff126
0231a44
df03e8b
65bcc79
40ca264
08d7b68
34ff183
19e996b
9fd09c2
08009a2
0f16137
90eba9d
24dc68b
717bb33
ddd8c0e
91c58ed
3472d4f
1533e1e
0234ee4
ec05ff6
6b17ab9
e256be4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,19 @@ The rules for this file: | |
| * accompany each entry with github issue/PR number (Issue #xyz) | ||
|
|
||
| ------------------------------------------------------------------------------ | ||
| 27/12/2025 IAlibay, spyke7, orbeckst | ||
| * 1.1.0 | ||
|
|
||
| Changes | ||
|
|
||
| * Added `OpenVDB.py` inside `gridData` to simply export and write in .vdb format | ||
| * Added `test_vdb.py` inside `gridData\tests` | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
|
|
||
| Fixes | ||
|
|
||
| * Adding openVDB formats (Issue #141) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not a fix but an Enhancement – put it into the existing 1.1.0 section and add you name there.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the CHANGELOG, this PR and issue are in the 1.1.0 release, so should I add my name in the 1.1.0 release or remove those lines and put them in the new section?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, now move it to the new section above since we released 1.1.0. |
||
|
|
||
|
|
||
| ??/??/???? IAlibay, ollyfutur, conradolandia, orbeckst | ||
| * 1.1.0 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| r""" | ||
| :mod:`~gridData.OpenVDB` --- routines to write OpenVDB files | ||
| ============================================================= | ||
|
|
||
| The OpenVDB format is used by Blender and other VFX software for | ||
| volumetric data. See https://www.openvdb.org | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
|
|
||
| .. Note:: This module implements a simple writer for 3D regular grids, | ||
| sufficient to export density data for visualization in Blender. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just make Blender a link again, eg to the relevant docs https://docs.blender.org/manual/en/latest/modeling/volumes/introduction.html |
||
|
|
||
| The OpenVDB format uses a sparse tree structure to efficiently store | ||
| volumetric data. It is the native format for Blender's volume system. | ||
|
spyke7 marked this conversation as resolved.
|
||
|
|
||
|
|
||
| Writing OpenVDB files | ||
| --------------------- | ||
|
|
||
| If you have a :class:`~gridData.core.Grid` object, you can write it to | ||
| OpenVDB format:: | ||
|
|
||
| from gridData import Grid | ||
| g = Grid("data.dx") | ||
| g.export("data.vdb") | ||
|
|
||
| This will create a file that can be imported directly into Blender | ||
|
orbeckst marked this conversation as resolved.
|
||
| (File -> Import -> OpenVDB). | ||
|
|
||
|
|
||
| Building an OpenVDB field from a numpy array | ||
| --------------------------------------------- | ||
|
|
||
|
orbeckst marked this conversation as resolved.
|
||
| Requires: | ||
|
|
||
| grid | ||
| numpy 3D array | ||
| origin | ||
| cartesian coordinates of the center of the (0,0,0) grid cell | ||
| delta | ||
| n x n array with the length of a grid cell along each axis | ||
|
spyke7 marked this conversation as resolved.
|
||
|
|
||
| Example:: | ||
|
|
||
| import OpenVDB | ||
|
orbeckst marked this conversation as resolved.
Outdated
|
||
| vdb_field = OpenVDB.field('density') | ||
| vdb_field.populate(grid, origin, delta) | ||
| vdb_field.write('output.vdb') | ||
|
|
||
|
|
||
| Classes and functions | ||
| --------------------- | ||
|
|
||
| """ | ||
|
|
||
| import numpy | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
| import warnings | ||
|
|
||
| try: | ||
| import pyopenvdb as vdb | ||
| except ImportError: | ||
| vdb = None | ||
|
spyke7 marked this conversation as resolved.
|
||
|
|
||
|
|
||
| class field(object): | ||
| """OpenVDB field object for writing volumetric data. | ||
|
|
||
| This class provides a simple interface to write 3D grid data to | ||
| OpenVDB format, which can be imported into Blender and other | ||
| VFX software. | ||
|
|
||
| The field object holds grid data and metadata, and can write it | ||
| to a .vdb file. | ||
|
|
||
| Example | ||
| ------- | ||
| Create a field and write it:: | ||
|
|
||
| vdb_field = OpenVDB.field('density') | ||
| vdb_field.populate(grid, origin, delta) | ||
| vdb_field.write('output.vdb') | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will need updating |
||
| Or use directly from Grid:: | ||
|
|
||
| g = Grid(...) | ||
| g.export('output.vdb', format='vdb') | ||
|
|
||
| """ | ||
|
|
||
| def __init__(self, name='density'): | ||
| """Initialize an OpenVDB field. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason for having to manually populate the Rewrite this code so that it takes as input the grid, origin, delta. Rename the class Make
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah indeed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done, made the populate private, and renamed the class |
||
|
|
||
| Parameters | ||
| ---------- | ||
| name : str | ||
| Name of the grid (will be visible in Blender) | ||
|
|
||
| """ | ||
| if vdb is None: | ||
| raise ImportError( | ||
| "pyopenvdb is required to write VDB files. " | ||
| ) | ||
| self.name = name | ||
| self.grid = None | ||
| self.origin = None | ||
| self.delta = None | ||
|
|
||
|
orbeckst marked this conversation as resolved.
|
||
| def populate(self, grid, origin, delta): | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
| """Populate the field with grid data. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| grid : numpy.ndarray | ||
| 3D numpy array with the data | ||
| origin : numpy.ndarray | ||
| Coordinates of the center of grid cell [0,0,0] | ||
| delta : numpy.ndarray | ||
| Grid spacing (can be 1D array or diagonal matrix) | ||
|
|
||
| Raises | ||
| ------ | ||
| ValueError | ||
| If grid is not 3D | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also describe error handling for |
||
|
|
||
| """ | ||
| grid = numpy.asarray(grid) | ||
| if grid.ndim != 3: | ||
| raise ValueError( | ||
| "OpenVDB only supports 3D grids, got {}D".format(grid.ndim)) | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
|
|
||
| self.grid = grid.astype(numpy.float32) # OpenVDB uses float32 | ||
| self.origin = numpy.asarray(origin) | ||
|
|
||
| # Handle delta: could be 1D array or diagonal matrix | ||
| delta = numpy.asarray(delta) | ||
|
orbeckst marked this conversation as resolved.
Outdated
|
||
| if delta.ndim == 2: | ||
| # Extract diagonal if it's a matrix | ||
| self.delta = numpy.array([delta[i, i] for i in range(3)]) | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
| else: | ||
| self.delta = delta | ||
|
orbeckst marked this conversation as resolved.
|
||
|
|
||
| def write(self, filename): | ||
| """Write the field to an OpenVDB file. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| filename : str | ||
| Output filename (should end in .vdb) | ||
|
|
||
| """ | ||
| if self.grid is None: | ||
| raise ValueError("No data to write. Use populate() first.") | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Create OpenVDB grid | ||
| vdb_grid = vdb.FloatGrid() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to a comment above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So for this I guess - FloatGrid, BoolGrid, and Vec3SGrid these three are by deafult. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From the same link above:
It looks like even the official module on conda-forge has only these fixed types: Blender packages its own version of This is not ideal. MN has so far created the grids based on the corresponding data types (defaulting to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll have to work with what's available and this seems to be FloatGrid, BoolGrid (and Vec3SGrid, which we don't care about because all our densities are scalar). Let's add a check that selects BoolGrid if the input array is a python ( |
||
| vdb_grid.name = self.name | ||
|
|
||
| # Set up transform (voxel size and position) | ||
| # Check for uniform spacing | ||
| if not numpy.allclose(self.delta, self.delta[0]): | ||
| warnings.warn( | ||
| "Non-uniform grid spacing {}. Using average spacing.".format( | ||
| self.delta)) | ||
| voxel_size = float(numpy.mean(self.delta)) | ||
| else: | ||
| voxel_size = float(self.delta[0]) | ||
|
|
||
| # Create linear transform with uniform voxel size | ||
| transform = vdb.createLinearTransform(voxelSize=voxel_size) | ||
|
|
||
| # OpenVDB transform is at corner of voxel [0,0,0], | ||
| # but GridDataFormats origin is at center of voxel [0,0,0] | ||
| corner_origin = self.origin - 0.5 * self.delta | ||
| transform.translate(corner_origin) | ||
| vdb_grid.transform = transform | ||
|
|
||
| # Set background value for sparse storage | ||
| vdb_grid.background = 0.0 | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Populate the grid | ||
|
|
||
| accessor = vdb_grid.getAccessor() | ||
| threshold = 1e-10 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where does 1e-10 come from? This could be a kwarg for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that in OpenVDB, does not store zero unless asked to. So values<threshold are treated as background. |
||
|
|
||
| for i in range(self.grid.shape[0]): | ||
| for j in range(self.grid.shape[1]): | ||
| for k in range(self.grid.shape[2]): | ||
| value = float(self.grid[i, j, k]) | ||
| if abs(value) > threshold: | ||
| accessor.setValueOn((i, j, k), value) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks really slow — iterating over a grid explicitly. For a start, you can find all cells above a threshold with numpy operations ( |
||
|
|
||
| vdb.write(filename, grids=[vdb_grid]) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
| from . import OpenDX | ||
| from . import gOpenMol | ||
| from . import mrc | ||
| from . import OpenVDB | ||
|
|
||
|
|
||
| def _grid(x): | ||
|
|
@@ -203,6 +204,7 @@ def __init__(self, grid=None, edges=None, origin=None, delta=None, | |
| 'PKL': self._export_python, | ||
| 'PICKLE': self._export_python, # compatibility | ||
| 'PYTHON': self._export_python, # compatibility | ||
| 'VDB': self._export_vdb, | ||
| } | ||
| self._loaders = { | ||
| 'CCP4': self._load_mrc, | ||
|
|
@@ -676,7 +678,26 @@ def _export_dx(self, filename, type=None, typequote='"', **kwargs): | |
| if ext == '.gz': | ||
| filename = root + ext | ||
| dx.write(filename) | ||
|
|
||
| def _export_vdb(self, filename, **kwargs): | ||
| """Export the density grid to an OpenVDB file. | ||
|
|
||
| The file format is compatible with Blender's volume system. | ||
| Only 3D grids are supported. | ||
|
|
||
| For the file format see https://www.openvdb.org | ||
| """ | ||
| if self.grid.ndim != 3: | ||
| raise ValueError( | ||
| "OpenVDB export requires a 3D grid, got {}D".format(self.grid.ndim)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use f-string
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still uses format. |
||
|
|
||
| # Get grid name from metadata if available | ||
| grid_name = self.metadata.get('name', 'density') | ||
|
|
||
| # Create and populate VDB field | ||
| vdb_field = OpenVDB.field(grid_name) | ||
| vdb_field.populate(self.grid, self.origin, self.delta) | ||
| vdb_field.write(filename) | ||
| def save(self, filename): | ||
| """Save a grid object to `filename` and add ".pickle" extension. | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.