From 4426526813f78cd0c0837505973eb4ca33fa45ae Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Sun, 29 Sep 2024 23:53:11 +0200 Subject: [PATCH] USD: Add tests for multiple materials assigned to single mesh This includes tests for a "static" mesh where the materials have been assigned and are expected to remain the same over time. The test scenario also includes a "dynamic" mesh where both new faces and new materials are assigned to the same mesh over time (inspired by #118754). However, that cannot be tested right now due to missing features of both the MeshSequenceCache and our USD IO code. No testing will occur for that case until the features are implemented. Pull Request: https://projects.blender.org/blender/blender/pulls/128341 --- tests/data | 2 +- tests/python/bl_usd_export_test.py | 37 ++++++++++++++++++++++++++++++ tests/python/bl_usd_import_test.py | 31 +++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/data b/tests/data index 0ff59eafc76..46d451fcb7d 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 0ff59eafc7658787b55db167b4157e7eed40bc6c +Subproject commit 46d451fcb7d6a7fca7c612c1658afb1836b79d5a diff --git a/tests/python/bl_usd_export_test.py b/tests/python/bl_usd_export_test.py index 59d44561fce..057a0ea3280 100644 --- a/tests/python/bl_usd_export_test.py +++ b/tests/python/bl_usd_export_test.py @@ -221,6 +221,43 @@ class USDExportTest(AbstractUSDTest): self.assertEqual(opacity_input.HasConnectedSource(), True, "Alpha input should be connected") self.assertAlmostEqual(opacity_thresh_input.Get(), 0.2, 2, "Opacity threshold input should be 0.2") + def test_export_material_subsets(self): + """Validate multiple materials assigned to the same mesh work correctly.""" + + bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_materials_multi.blend")) + + # Ensure the simulation zone data is baked for all relevant frames... + for frame in range(1, 5): + bpy.context.scene.frame_set(frame) + bpy.context.scene.frame_set(1) + + export_path = self.tempdir / "usd_materials_multi.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)) + + # The static mesh should have 4 materials each assigned to 4 faces (16 faces total) + static_mesh_prim = UsdGeom.Mesh(stage.GetPrimAtPath("/root/static_mesh/static_mesh")) + geom_subsets = UsdGeom.Subset.GetGeomSubsets(static_mesh_prim) + self.assertEqual(len(geom_subsets), 4) + + unique_face_indices = set() + for subset in geom_subsets: + face_indices = subset.GetIndicesAttr().Get() + self.assertEqual(len(face_indices), 4) + unique_face_indices.update(face_indices) + self.assertEqual(len(unique_face_indices), 16) + + # The dynamic mesh varies over time (currently blocked, see #124554 and #118754) + # - Frame 1: 1 face and 1 material [mat2] + # - Frame 2: 2 faces and 2 materials [mat2, mat3] + # - Frame 3: 4 faces and 3 materials [mat2, mat3, mat2, mat1] + # - Frame 4: 4 faces and 2 materials [mat2, mat3, mat2, mat3] + dynamic_mesh_prim = UsdGeom.Mesh(stage.GetPrimAtPath("/root/dynamic_mesh/dynamic_mesh")) + geom_subsets = UsdGeom.Subset.GetGeomSubsets(dynamic_mesh_prim) + self.assertEqual(len(geom_subsets), 0) + def check_primvar(self, prim, pv_name, pv_typeName, pv_interp, elements_len): pv = UsdGeom.PrimvarsAPI(prim).GetPrimvar(pv_name) self.assertTrue(pv.HasValue()) diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index b20adafe440..fa2c0e021c8 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -264,6 +264,37 @@ class USDImportTest(AbstractUSDTest): self.assertEqual(self.round_vector(node.inputs[1].default_value), [2, -2, 2]) self.assertEqual(self.round_vector(node.inputs[2].default_value), [-1, 1, -1]) + def test_import_material_subsets(self): + """Validate multiple materials assigned to the same mesh work correctly.""" + + # Use the existing materials 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_materials_multi.blend")) + # Ensure the simulation zone data is baked for all relevant frames... + for frame in range(1, 5): + bpy.context.scene.frame_set(frame) + bpy.context.scene.frame_set(1) + + testfile = str(self.tempdir / "usd_materials_multi.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}") + + # The static mesh should have 4 materials each assigned to 4 faces (16 faces total) + static_mesh = bpy.data.objects["static_mesh"].data + material_index_attr = static_mesh.attributes["material_index"] + self.assertEqual(len(static_mesh.materials), 4) + self.assertEqual(len(static_mesh.polygons), 16) + self.assertEqual(len(material_index_attr.data), 16) + + for mat_index in range(0, 4): + face_indices = [i for i, d in enumerate(material_index_attr.data) if d.value == mat_index] + self.assertEqual(len(face_indices), 4, f"Incorrect number of faces with material index {mat_index}") + def test_import_shader_varname_with_connection(self): """Test importing USD shader where uv primvar is a connection"""