Fix: USD: Ensure mesh velocity data is written sparsely and add tests
The mesh velocity data was not using the UsdUtilsSparseValueWriter and was writing out data for all frames even if the velocity didn't change. Adds test coverage for this scenario as well as other situations where a MeshSequenceCache (MSC) would be required: - Ensures that when positions vary a MSC is added - Ensures that when velocities vary a MSC is added (see blender/blender@c862d40e09) - Ensures that when attributes vary a MSC is added (see blender/blender@3c394d39f2) Pull Request: https://projects.blender.org/blender/blender/pulls/126208
This commit is contained in:
committed by
Jesse Yurkovich
parent
b2f65b9bcb
commit
56779c7bb0
@@ -769,26 +769,24 @@ void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh,
|
||||
{
|
||||
/* Export velocity attribute output by fluid sim, sequence cache modifier
|
||||
* and geometry nodes. */
|
||||
AttributeOwner owner = AttributeOwner::from_id(const_cast<ID *>(&mesh->id));
|
||||
CustomDataLayer *velocity_layer = BKE_attribute_find(
|
||||
owner, "velocity", CD_PROP_FLOAT3, bke::AttrDomain::Point);
|
||||
|
||||
if (velocity_layer == nullptr) {
|
||||
const VArraySpan velocity = *mesh->attributes().lookup<float3>("velocity",
|
||||
blender::bke::AttrDomain::Point);
|
||||
if (velocity.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float(*velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data);
|
||||
|
||||
/* Export per-vertex velocity vectors. */
|
||||
Span<pxr::GfVec3f> data = velocity.cast<pxr::GfVec3f>();
|
||||
pxr::VtVec3fArray usd_velocities;
|
||||
usd_velocities.reserve(mesh->verts_num);
|
||||
|
||||
for (int vertex_idx = 0, totvert = mesh->verts_num; vertex_idx < totvert; ++vertex_idx) {
|
||||
usd_velocities.push_back(pxr::GfVec3f(velocities[vertex_idx]));
|
||||
}
|
||||
usd_velocities.assign(data.begin(), data.end());
|
||||
|
||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||
usd_mesh.CreateVelocitiesAttr().Set(usd_velocities, timecode);
|
||||
pxr::UsdAttribute attr_vel = usd_mesh.CreateVelocitiesAttr(pxr::VtValue(), true);
|
||||
if (!attr_vel.HasValue()) {
|
||||
attr_vel.Set(usd_velocities, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
usd_value_writer_.SetAttribute(attr_vel, usd_velocities, timecode);
|
||||
}
|
||||
|
||||
USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx)
|
||||
|
||||
@@ -283,6 +283,41 @@ class USDExportTest(AbstractUSDTest):
|
||||
self.check_primvar(prim, "sp_quat", "VtArray<GfQuatf>", "uniform", 3)
|
||||
self.check_primvar_missing(prim, "sp_mat4x4")
|
||||
|
||||
def test_export_attributes_varying(self):
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_attribute_varying_test.blend"))
|
||||
# Ensure the simulation zone data is baked for all relevant frames...
|
||||
for frame in range(1, 16):
|
||||
bpy.context.scene.frame_set(frame)
|
||||
bpy.context.scene.frame_set(1)
|
||||
|
||||
export_path = self.tempdir / "usd_attribute_varying_test.usda"
|
||||
res = bpy.ops.wm.usd_export(filepath=str(export_path), export_animation=True, evaluation_mode="RENDER")
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to export to {export_path}")
|
||||
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
|
||||
#
|
||||
# Validate Mesh data
|
||||
#
|
||||
mesh1 = UsdGeom.Mesh(stage.GetPrimAtPath("/root/mesh1/mesh1"))
|
||||
mesh2 = UsdGeom.Mesh(stage.GetPrimAtPath("/root/mesh2/mesh2"))
|
||||
mesh3 = UsdGeom.Mesh(stage.GetPrimAtPath("/root/mesh3/mesh3"))
|
||||
|
||||
sparse_frames = [4.0, 5.0, 8.0, 9.0, 12.0, 13.0]
|
||||
|
||||
# Positions (should be sparsely written)
|
||||
self.assertEqual(mesh1.GetPointsAttr().GetTimeSamples(), sparse_frames)
|
||||
self.assertEqual(mesh2.GetPointsAttr().GetTimeSamples(), [])
|
||||
self.assertEqual(mesh3.GetPointsAttr().GetTimeSamples(), [])
|
||||
# Velocity (should be sparsely written)
|
||||
self.assertEqual(mesh1.GetVelocitiesAttr().GetTimeSamples(), [])
|
||||
self.assertEqual(mesh2.GetVelocitiesAttr().GetTimeSamples(), sparse_frames)
|
||||
self.assertEqual(mesh3.GetVelocitiesAttr().GetTimeSamples(), [])
|
||||
# Regular primvar (should be sparsely written)
|
||||
self.assertEqual(UsdGeom.PrimvarsAPI(mesh1).GetPrimvar("test").GetTimeSamples(), [])
|
||||
self.assertEqual(UsdGeom.PrimvarsAPI(mesh2).GetPrimvar("test").GetTimeSamples(), [])
|
||||
self.assertEqual(UsdGeom.PrimvarsAPI(mesh3).GetPrimvar("test").GetTimeSamples(), sparse_frames)
|
||||
|
||||
def test_animation(self):
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_anim_test.blend"))
|
||||
export_path = self.tempdir / "usd_anim_test.usda"
|
||||
|
||||
@@ -727,11 +727,11 @@ class USDImportTest(AbstractUSDTest):
|
||||
self.assertFalse(attribute_name in blender_data.attributes)
|
||||
|
||||
def test_import_attributes(self):
|
||||
testfile = str(self.tempdir / "usd_attribute_test.usda")
|
||||
|
||||
# Use the existing attributes file to create the USD test file
|
||||
# for import. It is validated as part of the bl_usd_export test.
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_attribute_test.blend"))
|
||||
|
||||
testfile = str(self.tempdir / "usd_attribute_test.usda")
|
||||
res = bpy.ops.wm.usd_export(filepath=testfile, evaluation_mode="RENDER")
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to export to {testfile}")
|
||||
|
||||
@@ -832,6 +832,71 @@ class USDImportTest(AbstractUSDTest):
|
||||
self.check_attribute(curves, "sp_quat", 'CURVE', 'QUATERNION', 3)
|
||||
self.check_attribute_missing(curves, "sp_mat4x4")
|
||||
|
||||
def test_import_attributes_varying(self):
|
||||
# Use the existing attributes file to create the USD test file
|
||||
# for import. It is validated as part of the bl_usd_export test.
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_attribute_varying_test.blend"))
|
||||
for frame in range(1, 16):
|
||||
bpy.context.scene.frame_set(frame)
|
||||
bpy.context.scene.frame_set(1)
|
||||
|
||||
testfile = str(self.tempdir / "usd_attribute_varying_test.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}")
|
||||
|
||||
stage = Usd.Stage.Open(testfile)
|
||||
|
||||
#
|
||||
# Validate Mesh data
|
||||
#
|
||||
blender_mesh = [bpy.data.objects["mesh1"], bpy.data.objects["mesh2"], bpy.data.objects["mesh3"]]
|
||||
usd_mesh = [UsdGeom.Mesh(stage.GetPrimAtPath("/root/mesh1/mesh1")),
|
||||
UsdGeom.Mesh(stage.GetPrimAtPath("/root/mesh2/mesh2")),
|
||||
UsdGeom.Mesh(stage.GetPrimAtPath("/root/mesh3/mesh3"))]
|
||||
mesh_num = len(blender_mesh)
|
||||
|
||||
# A MeshSequenceCache modifier should be present on every imported object
|
||||
for i in range(0, mesh_num):
|
||||
self.assertTrue(len(blender_mesh[i].modifiers) == 1 and blender_mesh[i].modifiers[0].type ==
|
||||
'MESH_SEQUENCE_CACHE', f"{blender_mesh[i].name} has incorrect modifiers")
|
||||
|
||||
def round_vector(vector):
|
||||
return (round(vector[0], 5), round(vector[1], 5), round(vector[2], 5))
|
||||
|
||||
# Compare Blender and USD data against each other for every frame
|
||||
for frame in range(1, 16):
|
||||
bpy.context.scene.frame_set(frame)
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
for i in range(0, mesh_num):
|
||||
blender_mesh[i] = bpy.data.objects["mesh" + str(i + 1)].evaluated_get(depsgraph)
|
||||
|
||||
# Check positions, velocity, and test data
|
||||
for i in range(0, mesh_num):
|
||||
blender_pos_data = [round_vector(d.vector) for d in blender_mesh[i].data.attributes["position"].data]
|
||||
blender_vel_data = [round_vector(d.vector) for d in blender_mesh[i].data.attributes["velocity"].data]
|
||||
blender_test_data = [round(d.value, 5) for d in blender_mesh[i].data.attributes["test"].data]
|
||||
usd_pos_data = [round_vector(d) for d in usd_mesh[i].GetPointsAttr().Get(frame)]
|
||||
usd_vel_data = [round_vector(d) for d in usd_mesh[i].GetVelocitiesAttr().Get(frame)]
|
||||
usd_test_data = [round(d, 5) for d in UsdGeom.PrimvarsAPI(usd_mesh[i]).GetPrimvar("test").Get(frame)]
|
||||
|
||||
self.assertEqual(
|
||||
blender_pos_data,
|
||||
usd_pos_data,
|
||||
f"Frame {frame}: {blender_mesh[i].name} positions do not match")
|
||||
self.assertEqual(
|
||||
blender_vel_data,
|
||||
usd_vel_data,
|
||||
f"Frame {frame}: {blender_mesh[i].name} velocities do not match")
|
||||
self.assertEqual(
|
||||
blender_test_data,
|
||||
usd_test_data,
|
||||
f"Frame {frame}: {blender_mesh[i].name} test attributes do not match")
|
||||
|
||||
|
||||
def main():
|
||||
global args
|
||||
|
||||
Reference in New Issue
Block a user