USD: Add support for animated volumes
The existing Volume export, which already supports VDB file sequences and static volumes created inside Blender, is now extended to handle dynamically created and modified volumes. This allows scenarios where a Volume Displace modifier is placed over-top an existing VDB sequence or when Geometry Nodes is used to create animated volumes procedurally. Detection of what counts as animation is simplistic and mimics what has been used for Meshes. Essentially if there are any modifiers on the volume we assume that the volume is "varying" in some way. This can lead to situations where new volume files are written unnecessarily. Volume import was also adjusted to correctly set the sequence "offset" value. This is required to properly handle the case when a VDB sequence begins animating at a different frame than what's implied by the file name. For example, a VDB file sequence with file names containing 14-19 but the user wants to animate on frames 8-13 instead. Tests are added which cover: - Animated VDB file sequences - Animated Mesh To Volume where the mesh has been animated - Animated Volume Displacement where displacement settings are animated - Animated Volumes created with a Geometry Nodes simulation ---- New test data has been checked in: `tests/data/usd/usd_volumes.blend` and files inside `tests/data/usd/volume-data/` Pull Request: https://projects.blender.org/blender/blender/pulls/128907
This commit is contained in:
committed by
Jesse Yurkovich
parent
08a9c8b786
commit
391612c725
@@ -8,7 +8,7 @@ import pprint
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pxr import Gf, Sdf, Usd, UsdGeom, UsdShade, UsdSkel, UsdUtils
|
||||
from pxr import Gf, Sdf, Usd, UsdGeom, UsdShade, UsdSkel, UsdUtils, UsdVol
|
||||
|
||||
import bpy
|
||||
|
||||
@@ -605,6 +605,61 @@ class USDExportTest(AbstractUSDTest):
|
||||
weight_samples = anim.GetBlendShapeWeightsAttr().GetTimeSamples()
|
||||
self.assertEqual(weight_samples, [1.0, 2.0, 3.0, 4.0, 5.0])
|
||||
|
||||
def test_export_volumes(self):
|
||||
"""Test various combinations of volume export including with all supported volume modifiers."""
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_volumes.blend"))
|
||||
# Ensure the simulation zone data is baked for all relevant frames...
|
||||
for frame in range(4, 15):
|
||||
bpy.context.scene.frame_set(frame)
|
||||
bpy.context.scene.frame_set(4)
|
||||
|
||||
export_path = self.tempdir / "usd_volumes.usda"
|
||||
self.export_and_validate(filepath=str(export_path), export_animation=True, evaluation_mode="RENDER")
|
||||
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
|
||||
# Validate that we see some form of time varyability across the Volume prim's extents and
|
||||
# file paths. The data should be sparse so it should only be written on the frames which
|
||||
# change.
|
||||
|
||||
# File sequence
|
||||
vol_fileseq = UsdVol.Volume(stage.GetPrimAtPath("/root/vol_filesequence/vol_filesequence"))
|
||||
density = UsdVol.OpenVDBAsset(stage.GetPrimAtPath("/root/vol_filesequence/vol_filesequence/density_noise"))
|
||||
flame = UsdVol.OpenVDBAsset(stage.GetPrimAtPath("/root/vol_filesequence/vol_filesequence/flame_noise"))
|
||||
self.assertEqual(vol_fileseq.GetExtentAttr().GetTimeSamples(), [10.0, 11.0])
|
||||
self.assertEqual(density.GetFieldNameAttr().GetTimeSamples(), [])
|
||||
self.assertEqual(density.GetFilePathAttr().GetTimeSamples(), [8.0, 9.0, 10.0, 11.0, 12.0, 13.0])
|
||||
self.assertEqual(flame.GetFieldNameAttr().GetTimeSamples(), [])
|
||||
self.assertEqual(flame.GetFilePathAttr().GetTimeSamples(), [8.0, 9.0, 10.0, 11.0, 12.0, 13.0])
|
||||
|
||||
# Mesh To Volume
|
||||
vol_mesh2vol = UsdVol.Volume(stage.GetPrimAtPath("/root/vol_mesh2vol/vol_mesh2vol"))
|
||||
density = UsdVol.OpenVDBAsset(stage.GetPrimAtPath("/root/vol_mesh2vol/vol_mesh2vol/density"))
|
||||
self.assertEqual(vol_mesh2vol.GetExtentAttr().GetTimeSamples(),
|
||||
[6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0])
|
||||
self.assertEqual(density.GetFieldNameAttr().GetTimeSamples(), [])
|
||||
self.assertEqual(density.GetFilePathAttr().GetTimeSamples(),
|
||||
[4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0])
|
||||
|
||||
# Volume Displace
|
||||
vol_displace = UsdVol.Volume(stage.GetPrimAtPath("/root/vol_displace/vol_displace"))
|
||||
unnamed = UsdVol.OpenVDBAsset(stage.GetPrimAtPath("/root/vol_displace/vol_displace/_"))
|
||||
self.assertEqual(vol_displace.GetExtentAttr().GetTimeSamples(),
|
||||
[5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0])
|
||||
self.assertEqual(unnamed.GetFieldNameAttr().GetTimeSamples(), [])
|
||||
self.assertEqual(unnamed.GetFilePathAttr().GetTimeSamples(),
|
||||
[4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0])
|
||||
|
||||
# Geometry Node simulation
|
||||
vol_sim = UsdVol.Volume(stage.GetPrimAtPath("/root/vol_sim/Volume"))
|
||||
density = UsdVol.OpenVDBAsset(stage.GetPrimAtPath("/root/vol_sim/Volume/density"))
|
||||
self.assertEqual(vol_sim.GetExtentAttr().GetTimeSamples(),
|
||||
[4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0])
|
||||
self.assertEqual(density.GetFieldNameAttr().GetTimeSamples(), [])
|
||||
self.assertEqual(density.GetFilePathAttr().GetTimeSamples(),
|
||||
[4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0])
|
||||
|
||||
def test_export_xform_ops(self):
|
||||
"""Test exporting different xform operation modes."""
|
||||
|
||||
|
||||
@@ -442,6 +442,56 @@ class USDImportTest(AbstractUSDTest):
|
||||
self.assertAlmostEqual(ob_arm2_side_a.matrix_world.to_euler('XYZ').z, 1.5708, 5)
|
||||
self.assertAlmostEqual(ob_arm2_side_b.matrix_world.to_euler('XYZ').z, 1.5708, 5)
|
||||
|
||||
def test_import_volumes(self):
|
||||
"""Validate volume import."""
|
||||
|
||||
# Use the existing volume test file to create the USD file
|
||||
# for import. It is validated as part of the bl_usd_export test.
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_volumes.blend"))
|
||||
# Ensure the simulation zone data is baked for all relevant frames...
|
||||
for frame in range(4, 15):
|
||||
bpy.context.scene.frame_set(frame)
|
||||
bpy.context.scene.frame_set(4)
|
||||
|
||||
testfile = str(self.tempdir / "usd_volumes.usda")
|
||||
res = bpy.ops.wm.usd_export(filepath=testfile, export_animation=True, evaluation_mode="RENDER")
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to export to {testfile}")
|
||||
|
||||
# Reload the empty file and import back in
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
|
||||
res = bpy.ops.wm.usd_import(filepath=testfile)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {testfile}")
|
||||
|
||||
# Validate that all volumes are properly configured.
|
||||
vol_displace = bpy.data.objects["vol_displace"]
|
||||
vol_filesequence = bpy.data.objects["vol_filesequence"]
|
||||
vol_mesh2vol = bpy.data.objects["vol_mesh2vol"]
|
||||
vol_sim = bpy.data.objects["Volume"]
|
||||
|
||||
def check_sequence(ob, frames, start, offset):
|
||||
self.assertTrue(ob.data.is_sequence)
|
||||
self.assertEqual(ob.data.frame_duration, frames)
|
||||
self.assertEqual(ob.data.frame_start, start)
|
||||
self.assertEqual(ob.data.frame_offset, offset)
|
||||
|
||||
check_sequence(vol_displace, 11, 4, 3)
|
||||
check_sequence(vol_filesequence, 6, 8, 13)
|
||||
check_sequence(vol_mesh2vol, 11, 4, 3)
|
||||
check_sequence(vol_sim, 11, 4, 3)
|
||||
|
||||
# Validate that their object dimensions are changing by spot checking 2 interesting frames
|
||||
bpy.context.scene.frame_set(8)
|
||||
dim_displace = vol_displace.dimensions.copy()
|
||||
dim_filesequence = vol_filesequence.dimensions.copy()
|
||||
dim_mesh2vol = vol_mesh2vol.dimensions.copy()
|
||||
dim_sim = vol_sim.dimensions.copy()
|
||||
|
||||
bpy.context.scene.frame_set(12)
|
||||
self.assertTrue(vol_displace.dimensions != dim_displace)
|
||||
self.assertTrue(vol_filesequence.dimensions != dim_filesequence)
|
||||
self.assertTrue(vol_mesh2vol.dimensions != dim_mesh2vol)
|
||||
self.assertTrue(vol_sim.dimensions != dim_sim)
|
||||
|
||||
def test_import_usd_blend_shapes(self):
|
||||
"""Test importing USD blend shapes with animated weights."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user