-
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 all commits
22a5995
c871534
d809198
242edb5
79c96de
0649210
f83f709
09b88a3
3c93f8d
1765ae1
31831cb
c55366b
ea446d7
1e2fd80
145a1dd
4bd1d8a
d010391
c8e643d
f87d3d8
97aef4b
b6e5568
53a6017
2c75469
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 |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| .. automodule:: gridData.OpenVDb | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,255 @@ | ||
| r""" | ||
| :mod:`~gridData.OpenVDB` --- routines to write OpenVDB files | ||
| ============================================================= | ||
|
|
||
| The `OpenVDB format`_ is used by Blender_ and other VFX software for | ||
| volumetric data. | ||
|
|
||
| .. _`OpenVDB format`: https://www.openvdb.org | ||
| .. _Blender: https://www.blender.org/ | ||
|
|
||
| This module uses the openvdb_ library to write OpenVDB files. | ||
|
|
||
| .. _openvdb: https://github.com/AcademySoftwareFoundation/openvdb | ||
|
|
||
| .. Note:: This module implements a simple writer for 3D regular grids, | ||
| sufficient to export density data for visualization in Blender_. | ||
| See the `Blender volume docs`_ for details on importing VDB files. | ||
|
|
||
| .. _`Blender volume 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. | ||
|
|
||
|
|
||
| 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.
Show resolved
Hide resolved
|
||
| (File -> Import -> OpenVDB) or (shift+A -> Volume -> Import OpenVDB). See `importing VDB in Blender`_ for details. | ||
|
|
||
| .. _`importing VDB in Blender`: https://docs.blender.org/manual/en/latest/modeling/geometry_nodes/input/import/vdb.html | ||
|
|
||
|
|
||
| Building an OpenVDB field from a numpy array | ||
| --------------------------------------------- | ||
|
|
||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| If you want to create VDB files without using the Grid class, | ||
| you can directly use the OpenVDB field API. This is useful | ||
| for custom workflows or when integrating with other libraries. | ||
|
|
||
| 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.
Show resolved
Hide resolved
|
||
|
|
||
| Example:: | ||
|
|
||
| from gridData import Grid | ||
|
|
||
| g = Grid("data.dx") | ||
| g.export("data.vdb") | ||
|
|
||
|
|
||
| Classes and functions | ||
| --------------------- | ||
|
|
||
| """ | ||
|
|
||
| import numpy | ||
| import warnings | ||
|
|
||
| try: | ||
| import openvdb as vdb | ||
|
|
||
| except ImportError: | ||
| vdb = None | ||
|
|
||
|
|
||
| class OpenVDBField(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:: | ||
|
|
||
| import gridData.OpenVDB as OpenVDB | ||
|
|
||
| vdb_field = OpenVDB.OpenVDBField('density') | ||
| vdb_field.write('output.vdb') | ||
|
|
||
| Or use directly from Grid:: | ||
|
|
||
| g = Grid(...) | ||
| g.export('output.vdb', format='vdb') | ||
|
|
||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| grid=None, | ||
| origin=None, | ||
| delta=None, | ||
| name="density", | ||
| tolerance=None, | ||
| metadata=None, | ||
| ): | ||
| """Initialize an OpenVDB field. | ||
|
|
||
| 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) | ||
| name : str | ||
| Name of the grid (will be visible in Blender), default 'density' | ||
| tolerance : float (optional) | ||
| Values below this tolerance are treated as background (sparse), | ||
| default None | ||
| metadata : dict (optional) | ||
| Additional metadata to embed in the VDB file. | ||
|
|
||
| Raises | ||
| ------ | ||
| ImportError | ||
| If openvdb is not installed | ||
| ValueError | ||
| If grid is not 3D, or if delta is not 1D/2D or describes | ||
| non-orthorhombic cell | ||
|
|
||
| """ | ||
| if vdb is None: | ||
| raise ImportError( | ||
| "openvdb is required to write VDB files. " | ||
| "Install it with: conda install -c conda-forge openvdb" | ||
| ) | ||
| self.name = name | ||
| self.tolerance = tolerance | ||
| if metadata is not None: | ||
| self.metadata = metadata | ||
| else: | ||
| self.metadata = {} | ||
|
|
||
| if grid is not None: | ||
| self._populate(grid, origin, delta) | ||
| else: | ||
| self.grid = None | ||
| self.origin = None | ||
| self.delta = None | ||
|
|
||
|
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. Create the OpenVDB data structure in |
||
| def _populate(self, grid, origin, delta): | ||
| """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, or if delta is not 1D/2D or describes | ||
| non-orthorhombic cell | ||
|
|
||
| """ | ||
| self.grid = numpy.asarray(grid) | ||
| if grid.ndim != 3: | ||
| raise ValueError(f"OpenVDB only supports 3D grids, got {grid.ndim}D") | ||
|
|
||
| self.grid = numpy.ascontiguousarray(self.grid) | ||
|
|
||
| self.origin = numpy.asarray(origin) | ||
|
|
||
| # Handle delta: could be 1D array or diagonal matrix | ||
| delta = numpy.asarray(delta) | ||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if delta.ndim == 2: | ||
| if delta.shape != (3, 3): | ||
| raise ValueError("delta as a matrix must be 3x3") | ||
|
|
||
| if not numpy.allclose(delta, numpy.diag(numpy.diag(delta))): | ||
| raise ValueError("Non-orthorhombic cells are not supported") | ||
|
|
||
| self.delta = numpy.diag(delta) | ||
|
|
||
| elif delta.ndim == 1: | ||
| if len(delta) != 3: | ||
| raise ValueError("delta must have length-3 for 3D grids") | ||
| self.delta = delta | ||
|
|
||
| else: | ||
| raise ValueError( | ||
| "delta must be either a length-3 vector or a 3x3 diagonal matrix" | ||
| ) | ||
|
|
||
| def write(self, filename): | ||
| """Write the field to an OpenVDB file. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| filename : str | ||
| Output filename (should end in .vdb) | ||
|
|
||
| Notes | ||
| ----- | ||
| Limitations in OpenVDB can lead to loss of precision. If the input | ||
| data is not of type float32, it will be converted to FloatGrid which is float32. | ||
|
|
||
| """ | ||
|
|
||
| if self.grid.dtype == numpy.bool_ or self.grid.dtype == bool: | ||
| vdb_grid = vdb.BoolGrid() | ||
| use_tolerance = False | ||
|
|
||
| else: | ||
| vdb_grid = vdb.FloatGrid() | ||
| if self.tolerance == None or self.tolerance == 0: | ||
| use_tolerance = False | ||
| else: | ||
| use_tolerance = True | ||
|
Comment on lines
+224
to
+233
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. Refine these code to choose the best available OpenVDB data type, based on the table in #148 I would probably create a dict such as datatypes = {np.bool: ["BoolGrid"],
np.int8: ["Int32Grid", "FloatGrid"],
np.uint8: ["Int32Grid", "FloatGrid"],
...
np.float16: ["HalfGrid", "FloatGrid"],
np.float32: ["FloatGrid"],
np.float64: ["DoubleGrid", "FloatGrid"],
...}
data = grid.array
try:
vdb_gridtypes = datatypes[data.dtype]
except KeyError:
raise TypeError(f"data type {data.dtype} is not supported for VDB")
# look for the best matching grid type by searching the list of
# gridtypes in order
for gridtype in vdb_gridtypes:
try:
VDB_Grid = getattr(openvdb, gridtype)
except AttributeError:
# not compiled with appropriate support
# try next one
pass
else:
break
else:
raise TypeError(f"Could not find VDB Grid {gridtype} for numpy type {data.dtype}")
# create the VDB grid instance
vdb = VDB_Grid(....)
...What's missing here is the ability to issue warnings when we are down-converting. Perhaps the easiest way to do this is to create another list with all the GridTypes that need a warning, or make the 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. One other potential alternative to specify whether it is a downcast (and hence needs a warning) is to specify it in the same list with a simple dataclass wrapper. For example: from dataclasses import dataclass
@dataclass
class DownCastTo:
gridType: str
datatypes = {
...,
np.float64: ["DoubleGrid", DownCastTo("FloatGrid")],
...,
}An ...
if isinstance(gridtype, DownCastTo):
gridtype = gridtype.gridType
...This will keep it all in a single list and also allow this to be per-type. I don't however know if this is a good/bad idea and an appropriate use of the dataclass. Thanks |
||
|
|
||
| vdb_grid.name = self.name | ||
|
|
||
| vdb_grid.transform = vdb.createLinearTransform() | ||
| vdb_grid.transform.preScale(self.delta.tolist()) | ||
| vdb_grid.transform.postTranslate(self.origin.tolist()) | ||
|
|
||
| if self.metadata: | ||
| for key, val in self.metadata.items(): | ||
| try: | ||
| vdb_grid[key] = val | ||
| except (TypeError, ValueError) as e: | ||
| warnings.warn(f"Could not set metadata '{key}': {e}", UserWarning) | ||
|
|
||
| if use_tolerance: | ||
| vdb_grid.copyFromArray(self.grid, tolerance=self.tolerance) | ||
| vdb_grid.prune() | ||
| else: | ||
| vdb_grid.copyFromArray(self.grid) | ||
| vdb_grid.prune(tolerance=False) | ||
|
Comment on lines
+224
to
+253
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. Move all of the code for creating the OpenVDB grid object in a separate private method (e.g., Then have |
||
|
|
||
| vdb.write(filename, grids=[vdb_grid]) | ||
|
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. MN would need metadata support in the exported file - either all the current grid metadata or something explicitly passed during the export.
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. please check the metadata part as implemented in the recent push |
||
Uh oh!
There was an error while loading. Please reload this page.