USD: Add support for Point Instancing during Export
Adds a Point Instancing exporter based on the existing USDPointInstancerReader. Covers both round-trip and Blender-native workflows. Exports 'Instance on Points' setups as USDGeomPointInstancer, supporting objects, collections, and nested prototypes. A warning is shown during export if invalid prototype references are detected. These would occur if an instancer attempts to instance itself. This feature is currently gated behind an off-by-default export option (`use_instancing`) as there are still a few cases which can yield incorrect results. Further details in the PR. Ref: #139758 Authored by Apple: Zili (Liz) Zhou Pull Request: https://projects.blender.org/blender/blender/pulls/139760
This commit is contained in:
committed by
Jesse Yurkovich
parent
fc0b659066
commit
07342407d3
@@ -1689,6 +1689,95 @@ class USDExportTest(AbstractUSDTest):
|
||||
|
||||
self.assertTupleEqual(expected, actual)
|
||||
|
||||
def test_point_instancing_export(self):
|
||||
"""Test exporting scenes that use point instancing."""
|
||||
|
||||
def confirm_point_instancing_stats(stage, num_meshes, num_instancers, num_instances, num_prototypes):
|
||||
mesh_count = 0
|
||||
instancer_count = 0
|
||||
instance_count = 0
|
||||
prototype_count = 0
|
||||
|
||||
for prim in stage.TraverseAll():
|
||||
prim_path = prim.GetPath()
|
||||
prim_type_name = prim.GetTypeName()
|
||||
|
||||
if prim_type_name == "PointInstancer":
|
||||
point_instancer = UsdGeom.PointInstancer(prim)
|
||||
if point_instancer:
|
||||
|
||||
# get instance count
|
||||
positions_attr = point_instancer.GetPositionsAttr()
|
||||
if positions_attr:
|
||||
positions = positions_attr.Get()
|
||||
if positions:
|
||||
instance_count += len(positions)
|
||||
|
||||
# get prototype count
|
||||
prototypes_rel = point_instancer.GetPrototypesRel()
|
||||
if prototypes_rel:
|
||||
target_prims = prototypes_rel.GetTargets()
|
||||
prototype_count += len(target_prims)
|
||||
|
||||
# show all prims and types
|
||||
# output_string = f" Path: {prim_path}, Type: {prim_type_name}"
|
||||
# print(output_string)
|
||||
|
||||
stats = UsdUtils.ComputeUsdStageStats(stage)
|
||||
mesh_count = stats['primary']['primCountsByType']['Mesh']
|
||||
instancer_count = stats['primary']['primCountsByType']['PointInstancer']
|
||||
|
||||
return mesh_count, instancer_count, instance_count, prototype_count
|
||||
|
||||
point_instance_test_scenarios = [
|
||||
# object reference treated as geometry set
|
||||
{'input_file': str(self.testdir / "usd_point_instancer_object_ref.blend"),
|
||||
'output_file': self.tempdir / "usd_export_point_instancer_object_ref.usda",
|
||||
'mesh_count': 3,
|
||||
'instancer_count': 1,
|
||||
'total_instances': 16,
|
||||
'total_prototypes': 1},
|
||||
# collection reference from single point instancer
|
||||
{'input_file': str(self.testdir / "usd_point_instancer_collection_ref.blend"),
|
||||
'output_file': self.tempdir / "usd_export_point_instancer_collection_ref.usda",
|
||||
'mesh_count': 5,
|
||||
'instancer_count': 1,
|
||||
'total_instances': 32,
|
||||
'total_prototypes': 2},
|
||||
# collection references in nested point instancer
|
||||
{'input_file': str(self.testdir / "usd_point_instancer_nested.blend"),
|
||||
'output_file': self.tempdir / "usd_export_point_instancer_nested.usda",
|
||||
'mesh_count': 9,
|
||||
'instancer_count': 3,
|
||||
'total_instances': 14,
|
||||
'total_prototypes': 4},
|
||||
# object reference coming from a collection with separate children
|
||||
{'input_file': str(self.testdir / "../render/shader/texture_coordinate_camera.blend"),
|
||||
'output_file': self.tempdir / "usd_export_point_instancer_separate_children.usda",
|
||||
'mesh_count': 9,
|
||||
'instancer_count': 1,
|
||||
'total_instances': 4,
|
||||
'total_prototypes': 2}
|
||||
]
|
||||
|
||||
for scenario in point_instance_test_scenarios:
|
||||
bpy.ops.wm.open_mainfile(filepath=scenario['input_file'])
|
||||
|
||||
export_path = scenario['output_file']
|
||||
self.export_and_validate(
|
||||
filepath=str(export_path),
|
||||
use_instancing=True
|
||||
)
|
||||
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
|
||||
mesh_count, instancer_count, instance_count, proto_count = confirm_point_instancing_stats(
|
||||
stage, scenario['mesh_count'], scenario['instancer_count'], scenario['total_instances'], scenario['total_prototypes'])
|
||||
self.assertEqual(scenario['mesh_count'], mesh_count, "Unexpected number of primary meshes")
|
||||
self.assertEqual(scenario['instancer_count'], instancer_count, "Unexpected number of point instancers")
|
||||
self.assertEqual(scenario['total_instances'], instance_count, "Unexpected number of total instances")
|
||||
self.assertEqual(scenario['total_prototypes'], proto_count, "Unexpected number of total prototypes")
|
||||
|
||||
|
||||
class USDHookBase:
|
||||
instructions = {}
|
||||
|
||||
Reference in New Issue
Block a user