USD: Add tests to cover curve, light, and point instancer import
The curve variations were used during development of the GeometrySet changes. The lights and point instancer coverage would have helped uncover bugs earlier. Bugs that eventually had to be fixed in 4.1. Better late than never. The one downside is that the light tests is actually a round-trip test, which is normally fine to do, except it technically does an export during the import test. Pull Request: https://projects.blender.org/blender/blender/pulls/119858
This commit is contained in:
committed by
Jesse Yurkovich
parent
efee753e8f
commit
9ae24fee48
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import math
|
||||
import pathlib
|
||||
import sys
|
||||
import unittest
|
||||
@@ -411,6 +412,269 @@ class USDImportTest(AbstractUSDTest):
|
||||
self.assertAlmostEqual(f.evaluate(0), 0.0, 2, "Unexpected value for rotation quaternion Z curve at frame 0")
|
||||
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}
|
||||
cyclic_map = {"nonperiodic": False, "periodic": True}
|
||||
|
||||
# Check correct spline count.
|
||||
blender_spline_count = len(blender_curve.attributes["curve_type"].data)
|
||||
usd_spline_count = len(usd_curve.GetCurveVertexCountsAttr().Get())
|
||||
self.assertEqual(blender_spline_count, usd_spline_count)
|
||||
|
||||
# Check correct type of curve. All splines should have the same type and periodicity.
|
||||
usd_curve_type = usd_curve.GetTypeAttr().Get()
|
||||
usd_cyclic = usd_curve.GetWrapAttr().Get()
|
||||
expected_curve_type = curve_type_map[usd_curve_type]
|
||||
expected_cyclic = cyclic_map[usd_cyclic]
|
||||
|
||||
for i in range(0, blender_spline_count):
|
||||
blender_curve_type = blender_curve.attributes["curve_type"].data[i].value
|
||||
blender_cyclic = False
|
||||
if "cyclic" in blender_curve.attributes:
|
||||
blender_cyclic = blender_curve.attributes["cyclic"].data[i].value
|
||||
|
||||
self.assertEqual(blender_curve_type, expected_curve_type)
|
||||
self.assertEqual(blender_cyclic, expected_cyclic)
|
||||
|
||||
# Check position data.
|
||||
usd_positions = usd_curve.GetPointsAttr().Get()
|
||||
blender_positions = blender_curve.attributes["position"].data
|
||||
|
||||
point_count = 0
|
||||
if usd_curve_type == "linear":
|
||||
point_count = len(usd_positions)
|
||||
self.assertEqual(len(blender_positions), point_count)
|
||||
elif usd_curve_type == "cubic":
|
||||
control_point_count = 0
|
||||
usd_vert_counts = usd_curve.GetCurveVertexCountsAttr().Get()
|
||||
for i in range(0, usd_spline_count):
|
||||
if usd_cyclic == "nonperiodic":
|
||||
control_point_count += (int(usd_vert_counts[i] / 3) + 1)
|
||||
else:
|
||||
control_point_count += (int(usd_vert_counts[i] / 3))
|
||||
|
||||
point_count = control_point_count
|
||||
self.assertEqual(len(blender_positions), point_count)
|
||||
|
||||
# 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_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":
|
||||
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_width_interpolation == "varying":
|
||||
# Do a quick min/max sanity check instead of reimplementing width interpolation
|
||||
usd_min = min(usd_radius)
|
||||
usd_max = max(usd_radius)
|
||||
blender_min = min(blender_radius)
|
||||
blender_max = max(blender_radius)
|
||||
|
||||
self.assertAlmostEqual(blender_min, usd_min, 2)
|
||||
self.assertAlmostEqual(blender_max, usd_max, 2)
|
||||
elif usd_width_interpolation == "vertex":
|
||||
# Do a quick check to ensure radius has been set at all
|
||||
self.assertEqual(True, all([r > 0 and r < 1 for r in blender_radius]))
|
||||
|
||||
def test_import_curves_linear(self):
|
||||
"""Test importing linear curve variations."""
|
||||
|
||||
infile = str(self.testdir / "usd_curve_linear_all.usda")
|
||||
res = bpy.ops.wm.usd_import(filepath=infile)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
|
||||
|
||||
curves = [o for o in bpy.data.objects if o.type == 'CURVES']
|
||||
self.assertEqual(8, len(curves), f"Test scene {infile} should have 8 curves; found {len(curves)}")
|
||||
|
||||
stage = Usd.Stage.Open(infile)
|
||||
|
||||
blender_curve = bpy.data.objects["linear_nonperiodic_single_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_nonperiodic/single/linear_nonperiodic_single_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["linear_nonperiodic_single_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_nonperiodic/single/linear_nonperiodic_single_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["linear_nonperiodic_multiple_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_nonperiodic/multiple/linear_nonperiodic_multiple_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["linear_nonperiodic_multiple_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_nonperiodic/multiple/linear_nonperiodic_multiple_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["linear_periodic_single_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_periodic/single/linear_periodic_single_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["linear_periodic_single_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_periodic/single/linear_periodic_single_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["linear_periodic_multiple_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_periodic/multiple/linear_periodic_multiple_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["linear_periodic_multiple_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/linear_periodic/multiple/linear_periodic_multiple_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
def test_import_curves_bezier(self):
|
||||
"""Test importing bezier curve variations."""
|
||||
|
||||
infile = str(self.testdir / "usd_curve_bezier_all.usda")
|
||||
res = bpy.ops.wm.usd_import(filepath=infile)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
|
||||
|
||||
curves = [o for o in bpy.data.objects if o.type == 'CURVES']
|
||||
self.assertEqual(12, len(curves), f"Test scene {infile} should have 12 curves; found {len(curves)}")
|
||||
|
||||
stage = Usd.Stage.Open(infile)
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_nonperiodic_single_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_nonperiodic/single/bezier_nonperiodic_single_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_nonperiodic_single_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_nonperiodic/single/bezier_nonperiodic_single_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_nonperiodic_single_vertex"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_nonperiodic/single/bezier_nonperiodic_single_vertex")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_nonperiodic_multiple_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_nonperiodic/multiple/bezier_nonperiodic_multiple_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_nonperiodic_multiple_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_nonperiodic/multiple/bezier_nonperiodic_multiple_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_nonperiodic_multiple_vertex"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_nonperiodic/multiple/bezier_nonperiodic_multiple_vertex")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_periodic_single_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_periodic/single/bezier_periodic_single_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_periodic_single_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_periodic/single/bezier_periodic_single_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_periodic_single_vertex"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_periodic/single/bezier_periodic_single_vertex")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_periodic_multiple_constant"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_periodic/multiple/bezier_periodic_multiple_constant")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_periodic_multiple_varying"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_periodic/multiple/bezier_periodic_multiple_varying")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
blender_curve = bpy.data.objects["bezier_periodic_multiple_vertex"].data
|
||||
usd_prim = stage.GetPrimAtPath("/root/bezier_periodic/multiple/bezier_periodic_multiple_vertex")
|
||||
self.check_curve(blender_curve, UsdGeom.BasisCurves(usd_prim))
|
||||
|
||||
def test_import_point_instancer(self):
|
||||
"""Test importing a typical point instancer setup."""
|
||||
|
||||
infile = str(self.testdir / "usd_nested_point_instancer.usda")
|
||||
res = bpy.ops.wm.usd_import(filepath=infile)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
|
||||
|
||||
pointclouds = [o for o in bpy.data.objects if o.type == 'POINTCLOUD']
|
||||
self.assertEqual(
|
||||
2,
|
||||
len(pointclouds),
|
||||
f"Test scene {infile} should have 2 pointclouds; found {len(pointclouds)}")
|
||||
|
||||
vertical_points = len(bpy.data.pointclouds['verticalpoints'].attributes["position"].data)
|
||||
horizontal_points = len(bpy.data.pointclouds['horizontalpoints'].attributes["position"].data)
|
||||
self.assertEqual(3, vertical_points)
|
||||
self.assertEqual(2, horizontal_points)
|
||||
|
||||
def test_import_light_types(self):
|
||||
"""Test importing light types and attributes."""
|
||||
|
||||
# Use the current scene to first create and export the lights
|
||||
bpy.ops.object.light_add(type='POINT', align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
bpy.context.active_object.data.energy = 2
|
||||
bpy.context.active_object.data.shadow_soft_size = 2.2
|
||||
|
||||
bpy.ops.object.light_add(type='SPOT', align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
bpy.context.active_object.data.energy = 3
|
||||
bpy.context.active_object.data.shadow_soft_size = 3.3
|
||||
bpy.context.active_object.data.spot_blend = 0.25
|
||||
bpy.context.active_object.data.spot_size = math.radians(60)
|
||||
|
||||
bpy.ops.object.light_add(type='SUN', align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
bpy.context.active_object.data.energy = 4
|
||||
bpy.context.active_object.data.angle = math.radians(1)
|
||||
|
||||
bpy.ops.object.light_add(type='AREA', align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
bpy.context.active_object.data.energy = 5
|
||||
bpy.context.active_object.data.shape = 'RECTANGLE'
|
||||
bpy.context.active_object.data.size = 0.5
|
||||
bpy.context.active_object.data.size_y = 1.5
|
||||
|
||||
bpy.ops.object.light_add(type='AREA', align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
bpy.context.active_object.data.energy = 6
|
||||
bpy.context.active_object.data.shape = 'DISK'
|
||||
bpy.context.active_object.data.size = 2
|
||||
|
||||
test_path = self.tempdir / "temp_lights.usda"
|
||||
res = bpy.ops.wm.usd_export(filepath=str(test_path), evaluation_mode="RENDER")
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to export to {test_path}")
|
||||
|
||||
# Reload the empty file and import back in
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
|
||||
infile = str(test_path)
|
||||
res = bpy.ops.wm.usd_import(filepath=infile)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
|
||||
|
||||
lights = [o for o in bpy.data.objects if o.type == 'LIGHT']
|
||||
self.assertEqual(5, len(lights), f"Test scene {infile} should have 5 lights; found {len(lights)}")
|
||||
|
||||
blender_light = bpy.data.lights["Point"]
|
||||
self.assertAlmostEqual(blender_light.energy, 2, 3)
|
||||
self.assertAlmostEqual(blender_light.shadow_soft_size, 2.2, 3)
|
||||
|
||||
blender_light = bpy.data.lights["Spot"]
|
||||
self.assertAlmostEqual(blender_light.energy, 3, 3)
|
||||
self.assertAlmostEqual(blender_light.shadow_soft_size, 3.3, 3)
|
||||
self.assertAlmostEqual(blender_light.spot_blend, 0.25, 3)
|
||||
self.assertAlmostEqual(blender_light.spot_size, math.radians(60), 3)
|
||||
|
||||
blender_light = bpy.data.lights["Sun"]
|
||||
self.assertAlmostEqual(blender_light.energy, 4, 3)
|
||||
self.assertAlmostEqual(blender_light.angle, math.radians(1), 3)
|
||||
|
||||
blender_light = bpy.data.lights["Area"]
|
||||
self.assertAlmostEqual(blender_light.energy, 5, 3)
|
||||
self.assertEqual(blender_light.shape, 'RECTANGLE')
|
||||
self.assertAlmostEqual(blender_light.size, 0.5, 3)
|
||||
self.assertAlmostEqual(blender_light.size_y, 1.5, 3)
|
||||
|
||||
blender_light = bpy.data.lights["Area_001"]
|
||||
self.assertAlmostEqual(blender_light.energy, 6, 3)
|
||||
self.assertEqual(blender_light.shape, 'DISK')
|
||||
self.assertAlmostEqual(blender_light.size, 2, 3)
|
||||
|
||||
|
||||
def main():
|
||||
global args
|
||||
|
||||
Reference in New Issue
Block a user