From cb9ca2f7a73de1285fe614d6259f859b278ea8aa Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Mon, 12 Aug 2024 01:50:34 +0200 Subject: [PATCH] Fix: USD: Write vertex crease data correctly and add tests While adding test coverage for mesh subdivision surface scenarios, a few problems were noticed with vertex crease support. This PR fixes: - Used incorrect `crease_sharpnesses` instead of `corner_sharpnesses` - Used incorrect value for an "infinitely sharp" vertex crease - Unnecessarily wrote out Blender's `crease_vert` attribute as a primvar Tests are added which validate everything we support. Pull Request: https://projects.blender.org/blender/blender/pulls/126209 --- .../blender/io/usd/intern/usd_writer_mesh.cc | 9 ++- tests/python/bl_usd_export_test.py | 75 +++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc index ae344f4bf91..15f9ce97290 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.cc +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -166,7 +166,7 @@ void USDGenericMeshWriter::write_custom_data(const Object *obj, * Skip edge domain because USD doesn't have a good conversion for them. */ if (attribute_id.name()[0] == '.' || attribute_id.is_anonymous() || meta_data.domain == bke::AttrDomain::Edge || - ELEM(attribute_id.name(), "position", "material_index", "velocity")) + ELEM(attribute_id.name(), "position", "material_index", "velocity", "crease_vert")) { return true; } @@ -459,7 +459,7 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, usd_value_writer_.SetAttribute( attr_corner_indices, pxr::VtValue(usd_mesh_data.corner_indices), timecode); usd_value_writer_.SetAttribute( - attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode); + attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.corner_sharpnesses), timecode); } write_custom_data(context.object, mesh, usd_mesh); @@ -638,9 +638,10 @@ static void get_vert_creases(const Mesh *mesh, USDMeshData &usd_mesh_data) } const VArraySpan creases(*attribute); for (const int i : creases.index_range()) { - const float sharpness = creases[i]; + const float crease = creases[i]; - if (sharpness != 0.0f) { + if (crease > 0.0f) { + const float sharpness = crease >= 1.0f ? pxr::UsdGeomMesh::SHARPNESS_INFINITE : crease; usd_mesh_data.corner_indices.push_back(i); usd_mesh_data.corner_sharpnesses.push_back(sharpness); } diff --git a/tests/python/bl_usd_export_test.py b/tests/python/bl_usd_export_test.py index 4ed34239e07..8f756168e3f 100644 --- a/tests/python/bl_usd_export_test.py +++ b/tests/python/bl_usd_export_test.py @@ -318,6 +318,81 @@ class USDExportTest(AbstractUSDTest): self.assertEqual(UsdGeom.PrimvarsAPI(mesh2).GetPrimvar("test").GetTimeSamples(), []) self.assertEqual(UsdGeom.PrimvarsAPI(mesh3).GetPrimvar("test").GetTimeSamples(), sparse_frames) + def test_export_mesh_subd(self): + """Test exporting Subdivision Surface attributes and values""" + bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_mesh_subd.blend")) + export_path = self.tempdir / "usd_mesh_subd.usda" + res = bpy.ops.wm.usd_export( + filepath=str(export_path), + export_subdivision='BEST_MATCH', + evaluation_mode="RENDER", + ) + self.assertEqual({'FINISHED'}, res, f"Unable to export to {export_path}") + + stage = Usd.Stage.Open(str(export_path)) + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/uv_smooth_none_boundary_smooth_all/mesh1")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'all') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/uv_smooth_corners_boundary_smooth_all/mesh2")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'cornersOnly') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/uv_smooth_corners_junctions_boundary_smooth_all/mesh3")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'cornersPlus1') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/uv_smooth_corners_junctions_concave_boundary_smooth_all/mesh4")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'cornersPlus2') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/uv_smooth_boundaries_boundary_smooth_all/mesh5")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'boundaries') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/uv_smooth_all_boundary_smooth_all/mesh6")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'none') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/uv_smooth_boundaries_boundary_smooth_keep/mesh7")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'boundaries') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeAndCorner') + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/crease_verts/crease_verts")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'boundaries') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + self.assertEqual(len(mesh.GetCornerIndicesAttr().Get()), 7) + usd_vert_sharpness = mesh.GetCornerSharpnessesAttr().Get() + self.assertEqual(len(usd_vert_sharpness), 7) + # A 1.0 crease is INFINITE (10) in USD + self.assertAlmostEqual(min(usd_vert_sharpness), 0.1, 5) + self.assertEqual(len([sharp for sharp in usd_vert_sharpness if sharp < 1]), 6) + self.assertEqual(len([sharp for sharp in usd_vert_sharpness if sharp == 10]), 1) + + mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/crease_edge/crease_edge")) + self.assertEqual(mesh.GetSubdivisionSchemeAttr().Get(), 'catmullClark') + self.assertEqual(mesh.GetFaceVaryingLinearInterpolationAttr().Get(), 'boundaries') + self.assertEqual(mesh.GetInterpolateBoundaryAttr().Get(), 'edgeOnly') + self.assertEqual(len(mesh.GetCreaseIndicesAttr().Get()), 20) + usd_crease_lengths = mesh.GetCreaseLengthsAttr().Get() + self.assertEqual(len(usd_crease_lengths), 10) + self.assertTrue(all([length == 2 for length in usd_crease_lengths])) + usd_crease_sharpness = mesh.GetCreaseSharpnessesAttr().Get() + self.assertEqual(len(usd_crease_sharpness), 10) + # A 1.0 crease is INFINITE (10) in USD + self.assertAlmostEqual(min(usd_crease_sharpness), 0.1, 5) + self.assertEqual(len([sharp for sharp in usd_crease_sharpness if sharp < 1]), 9) + self.assertEqual(len([sharp for sharp in usd_crease_sharpness if sharp == 10]), 1) + 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"