diff --git a/tests/python/bl_usd_export_test.py b/tests/python/bl_usd_export_test.py index 4994c2bc7a7..4c57eba2a3d 100644 --- a/tests/python/bl_usd_export_test.py +++ b/tests/python/bl_usd_export_test.py @@ -444,6 +444,36 @@ class USDExportTest(AbstractUSDTest): expected_prim_verts = {"Ball_A": 2232, "Ball_B": 2876, "Ball_C": 1152} self.assertDictEqual(actual_prim_verts, expected_prim_verts) + def test_particle_hair(self): + """Validate correct export of particle hair emitters.""" + + bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_particle_hair.blend")) + + # Ensure the hair dynamics are baked for all relevant frames... + for frame in range(1, 11): + bpy.context.scene.frame_set(frame) + bpy.context.scene.frame_set(1) + + export_path = self.tempdir / "usd_particle_hair.usda" + self.export_and_validate( + filepath=str(export_path), export_hair=True, export_animation=True, evaluation_mode="RENDER") + + stage = Usd.Stage.Open(str(export_path)) + main_prim = stage.GetPrimAtPath("/root/Sphere") + hair_prim = stage.GetPrimAtPath("/root/Sphere/ParticleSystem") + self.assertTrue(main_prim.IsValid()) + self.assertTrue(hair_prim.IsValid()) + + # Ensure we have 5 frames of rotation data for the main Sphere and 10 frames for the hair data + rot_samples = UsdGeom.Xformable(main_prim).GetRotateXYZOp().GetTimeSamples() + self.assertEqual(len(rot_samples), 5) + + hair_curves = UsdGeom.BasisCurves(hair_prim) + hair_samples = hair_curves.GetPointsAttr().GetTimeSamples() + self.assertEqual(hair_curves.GetTypeAttr().Get(), "cubic") + self.assertEqual(hair_curves.GetBasisAttr().Get(), "bspline") + self.assertEqual(len(hair_samples), 10) + 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 6591f55abc6..2c132ac27d5 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -734,7 +734,7 @@ class USDImportTest(AbstractUSDTest): self.assertAlmostEqual(f.evaluate(10), 0.0, 2, "Unexpected value for rotation quaternion Z curve at frame 10") def check_curve(self, blender_curve, usd_curve): - curve_type_map = {"linear": 1, "cubic": 2} + curve_type_map = {"linear": 1, "cubic-bezier": 2, "cubic-bspline": 3} cyclic_map = {"nonperiodic": False, "periodic": True} # Check correct spline count. @@ -744,8 +744,11 @@ class USDImportTest(AbstractUSDTest): # Check correct type of curve. All splines should have the same type and periodicity. usd_curve_type = usd_curve.GetTypeAttr().Get() + usd_curve_type_basis = usd_curve_type + if usd_curve_type != "linear": + usd_curve_type_basis = usd_curve_type + "-" + usd_curve.GetBasisAttr().Get() usd_cyclic = usd_curve.GetWrapAttr().Get() - expected_curve_type = curve_type_map[usd_curve_type] + expected_curve_type = curve_type_map[usd_curve_type_basis] expected_cyclic = cyclic_map[usd_cyclic] for i in range(0, blender_spline_count): @@ -762,10 +765,10 @@ class USDImportTest(AbstractUSDTest): blender_positions = blender_curve.attributes["position"].data point_count = 0 - if usd_curve_type == "linear": + if usd_curve_type_basis == "linear": point_count = len(usd_positions) self.assertEqual(len(blender_positions), point_count) - elif usd_curve_type == "cubic": + elif usd_curve_type_basis == "cubic-bezier": control_point_count = 0 usd_vert_counts = usd_curve.GetCurveVertexCountsAttr().Get() for i in range(0, usd_spline_count): @@ -776,19 +779,25 @@ class USDImportTest(AbstractUSDTest): point_count = control_point_count self.assertEqual(len(blender_positions), point_count) + elif usd_curve_type_basis == "cubic-bspline": + point_count = len(usd_positions) + self.assertEqual(len(blender_positions), point_count) + + # Check radius data. (note: the currently available bsplines have no radii) + if usd_curve_type_basis == "cubic-bspline": + return - # Check radius data. usd_width_interpolation = usd_curve.GetWidthsInterpolation() usd_radius = [w / 2 for w in usd_curve.GetWidthsAttr().Get()] blender_radius = [r.value for r in blender_curve.attributes["radius"].data] - if usd_curve_type == "linear": + if usd_curve_type_basis == "linear": if usd_width_interpolation == "constant": usd_radius = usd_radius * point_count for i in range(0, len(blender_radius)): self.assertAlmostEqual(blender_radius[i], usd_radius[i], 2) - elif usd_curve_type == "cubic": + elif usd_curve_type_basis == "cubic-bezier": if usd_width_interpolation == "constant": usd_radius = usd_radius * point_count @@ -911,6 +920,27 @@ class USDImportTest(AbstractUSDTest): usd_prim = stage.GetPrimAtPath("/root/bezier_periodic/multiple/bezier_periodic_multiple_vertex") self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim)) + def test_import_curves_bspline(self): + """Test importing bspline curve variations.""" + + # Use the existing hair 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_particle_hair.blend")) + testfile = str(self.tempdir / "usd_particle_hair.usda") + res = bpy.ops.wm.usd_export(filepath=testfile, export_hair=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) + + blender_curve = bpy.data.objects["ParticleSystem"].data + usd_prim = stage.GetPrimAtPath("/root/Sphere/ParticleSystem") + self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim)) + def test_import_point_instancer(self): """Test importing a typical point instancer setup."""