Fix: USD: Correct the exported extents for point instancers
Adds and corrects the extent attributes of USD PointInstancer prims. Extents are now computed for PointInstancers just before the USD stage is saved and during the export finalization step. The unit test has been updated accordingly. This PR also marks all point instancers' prototypes as over after the extent calculation is done, including the prototypes used by nested point instancers. This follows the official USD recommendation to place prototypes under a point instancer marked as over: https://openusd.org/docs/api/class_usd_geom_point_instancer.html#:~:text=place%20them%20under%20a%20prim%20that%20is%20just%20an%20%22over%22 Authored by Apple: Zili Zhou (Liz) Co-authored-by: Zili (Liz) Zhou <zili_zhou@apple.com> Pull Request: https://projects.blender.org/blender/blender/pulls/141299
This commit is contained in:
committed by
Jesse Yurkovich
parent
4431d7a369
commit
a5f915d3d3
@@ -388,10 +388,18 @@ std::string cache_image_color(const float color[4])
|
||||
return file_path;
|
||||
}
|
||||
|
||||
static void mark_point_instancer_prototypes_as_over(const pxr::UsdStageRefPtr &stage,
|
||||
const pxr::SdfPath &wrapper_path,
|
||||
std::set<pxr::SdfPath> &visited)
|
||||
static void collect_point_instancer_prototypes_and_set_extent(
|
||||
pxr::UsdGeomPointInstancer instancer,
|
||||
const pxr::UsdStageRefPtr &stage,
|
||||
const pxr::SdfPath &wrapper_path,
|
||||
std::vector<pxr::UsdPrim> &proto_list)
|
||||
{
|
||||
/* Compute extent of the current point instancer.*/
|
||||
pxr::VtArray<pxr::GfVec3f> extent;
|
||||
instancer.ComputeExtentAtTime(
|
||||
&extent, pxr::UsdTimeCode().Default(), pxr::UsdTimeCode().Default());
|
||||
instancer.CreateExtentAttr().Set(extent);
|
||||
|
||||
pxr::UsdPrim wrapper_prim = stage->GetPrimAtPath(wrapper_path);
|
||||
if (!wrapper_prim || !wrapper_prim.IsValid()) {
|
||||
return;
|
||||
@@ -423,17 +431,13 @@ static void mark_point_instancer_prototypes_as_over(const pxr::UsdStageRefPtr &s
|
||||
const pxr::SdfPath real_path(real_path_str);
|
||||
pxr::UsdPrim proto_prim = stage->GetPrimAtPath(real_path);
|
||||
|
||||
if (visited.count(real_path)) {
|
||||
return;
|
||||
}
|
||||
visited.insert(real_path);
|
||||
|
||||
if (!proto_prim || !proto_prim.IsValid()) {
|
||||
CLOG_WARN(&LOG, "Referenced prototype not found at: %s", real_path.GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
proto_prim.SetSpecifier(pxr::SdfSpecifierOver);
|
||||
proto_list.push_back(proto_prim);
|
||||
proto_list.push_back(wrapper_prim.GetParent());
|
||||
|
||||
std::string doc_message = fmt::format(
|
||||
"This prim is used as a prototype by the PointInstancer \"{}\" so we override the def "
|
||||
@@ -442,12 +446,28 @@ static void mark_point_instancer_prototypes_as_over(const pxr::UsdStageRefPtr &s
|
||||
wrapper_prim.GetName().GetString());
|
||||
proto_prim.SetDocumentation(doc_message);
|
||||
|
||||
if (wrapper_prim.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
pxr::UsdGeomPointInstancer nested_instancer(wrapper_prim);
|
||||
/* Check if the proto prim itself is a PointInstancer. */
|
||||
if (proto_prim.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
pxr::UsdGeomPointInstancer nested_instancer(proto_prim);
|
||||
pxr::SdfPathVector nested_targets;
|
||||
if (nested_instancer.GetPrototypesRel().GetTargets(&nested_targets)) {
|
||||
for (const pxr::SdfPath &nested_wrapper_path : nested_targets) {
|
||||
mark_point_instancer_prototypes_as_over(stage, nested_wrapper_path, visited);
|
||||
collect_point_instancer_prototypes_and_set_extent(
|
||||
nested_instancer, stage, nested_wrapper_path, proto_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Also check all children of the proto prim for nested PointInstancers. */
|
||||
for (const pxr::UsdPrim &child : proto_prim.GetAllChildren()) {
|
||||
if (child.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
pxr::UsdGeomPointInstancer nested_instancer(child);
|
||||
pxr::SdfPathVector nested_targets;
|
||||
if (nested_instancer.GetPrototypesRel().GetTargets(&nested_targets)) {
|
||||
for (const pxr::SdfPath &nested_wrapper_path : nested_targets) {
|
||||
collect_point_instancer_prototypes_and_set_extent(
|
||||
nested_instancer, stage, nested_wrapper_path, proto_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -635,7 +655,7 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
|
||||
|
||||
/* Traverse the point instancer to make sure the prototype referenced by nested point instancers
|
||||
* are also marked as over. */
|
||||
std::set<pxr::SdfPath> visited;
|
||||
std::vector<pxr::UsdPrim> proto_list;
|
||||
for (const pxr::UsdPrim &prim : usd_stage->Traverse()) {
|
||||
if (!prim.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
continue;
|
||||
@@ -644,11 +664,19 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
|
||||
pxr::SdfPathVector targets;
|
||||
if (instancer.GetPrototypesRel().GetTargets(&targets)) {
|
||||
for (const pxr::SdfPath &wrapper_path : targets) {
|
||||
mark_point_instancer_prototypes_as_over(usd_stage, wrapper_path, visited);
|
||||
collect_point_instancer_prototypes_and_set_extent(
|
||||
instancer, usd_stage, wrapper_path, proto_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The standard way is to mark the point instancer's prototypes as over. Reference in OpenUSD:
|
||||
* https://openusd.org/docs/api/class_usd_geom_point_instancer.html#:~:text=place%20them%20under%20a%20prim%20that%20is%20just%20an%20%22over%22
|
||||
*/
|
||||
for (pxr::UsdPrim &proto : proto_list) {
|
||||
proto.SetSpecifier(pxr::SdfSpecifierOver);
|
||||
}
|
||||
|
||||
usd_stage->GetRootLayer()->Save();
|
||||
|
||||
data->export_ok = true;
|
||||
|
||||
@@ -182,7 +182,6 @@ void USDPointInstancerWriter::do_write(HierarchyContext &context)
|
||||
++iter;
|
||||
}
|
||||
usd_instancer.GetPrototypesRel().SetTargets(proto_wrapper_paths);
|
||||
prototypesOver.GetPrim().SetSpecifier(pxr::SdfSpecifierOver);
|
||||
stage->GetRootLayer()->Save();
|
||||
}
|
||||
|
||||
|
||||
@@ -1736,28 +1736,42 @@ class USDExportTest(AbstractUSDTest):
|
||||
'mesh_count': 3,
|
||||
'instancer_count': 1,
|
||||
'total_instances': 16,
|
||||
'total_prototypes': 1},
|
||||
'total_prototypes': 1,
|
||||
'extent': {
|
||||
"/root/Plane/Mesh": [Gf.Vec3f(-1.0999999, -1.0999999, -0.1),
|
||||
Gf.Vec3f(1.1, 1.1, 0.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},
|
||||
'total_prototypes': 2,
|
||||
'extent': {
|
||||
"/root/Plane/Mesh": [Gf.Vec3f(-1.1758227, -1.1, -0.1),
|
||||
Gf.Vec3f(1.1, 1.1526861, 0.14081651)]}},
|
||||
# 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},
|
||||
'total_prototypes': 4,
|
||||
'extent': {
|
||||
"/root/Triangle/Triangle": [Gf.Vec3f(-0.976631, -1.2236981, -0.7395363),
|
||||
Gf.Vec3f(1.8081428, 3.371673, 1.2604637)],
|
||||
"/root/Plane/Plane": [Gf.Vec3f(-1.164238, -3.5953712, -0.2883494),
|
||||
Gf.Vec3f(-0.68365526, -3.1147888, -0.18980181)]}},
|
||||
# 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}
|
||||
'total_prototypes': 2,
|
||||
'extent': {
|
||||
"/root/Rotated_and_Scaled_Instances/Cube_003": [Gf.Vec3f(-8.488519, -6.1219244, -6.964829),
|
||||
Gf.Vec3f(3.2331002, 5.4789553, 7.095813)]}}
|
||||
]
|
||||
|
||||
for scenario in point_instance_test_scenarios:
|
||||
@@ -1777,6 +1791,20 @@ class USDExportTest(AbstractUSDTest):
|
||||
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")
|
||||
if 'extent' in scenario:
|
||||
for prim_path, (expected_min, expected_max) in scenario['extent'].items():
|
||||
prim = stage.GetPrimAtPath(prim_path)
|
||||
self.assertTrue(prim.IsValid(), f"Prim {prim_path} not found on stage")
|
||||
|
||||
boundable = UsdGeom.Boundable(prim)
|
||||
extent_attr = boundable.GetExtentAttr()
|
||||
self.assertTrue(extent_attr.HasAuthoredValue(), f"Prim {prim_path} has no authored extent")
|
||||
|
||||
extent = extent_attr.Get()
|
||||
self.assertIsNotNone(extent, f"Extent on {prim_path} could not be retrieved")
|
||||
|
||||
self.compareVec3d(Gf.Vec3d(extent[0]), expected_min)
|
||||
self.compareVec3d(Gf.Vec3d(extent[1]), expected_max)
|
||||
|
||||
|
||||
class USDHookBase:
|
||||
|
||||
Reference in New Issue
Block a user