USD: Add option to merge shape with parent xform during import

This new import option will allow users to chose if we merge USD prims
with their Xform parent.

Given a USD like:

```
def Xform "MyObject"
{
  def Mesh "MyObject_LOD0"
  {
  }
}
```

When the option is set to True (existing default), only the mesh will be
imported and its parent Xform transformation will be baked into the
Mesh.
```
# In blender after import

- MyObject_LOD0 (Mesh)
```

When the option is set to False, the parent Xform will always be
imported as an Empty object:

```
# In blender after import

- MyObject (Empty)
├─ MyObject_LOD0 (Mesh)
```

Co-authored-by: Odréanne Breton <odreanne.breton@ubisoft.com>
Co-authored-by: Sttevan Carnali Joga <sttevan.carnali-joga@ubisoft.com>
This commit is contained in:
Charles Flèche
2024-11-14 18:28:23 +01:00
committed by Jesse Yurkovich
parent 7074daed04
commit 000416c933
5 changed files with 49 additions and 4 deletions

View File

@@ -935,6 +935,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const bool create_world_material = RNA_boolean_get(op->ptr, "create_world_material");
const bool merge_parent_xform = RNA_boolean_get(op->ptr, "merge_parent_xform");
/* TODO(makowalski): Add support for sequences. */
const bool is_sequence = false;
int offset = 0;
@@ -982,6 +984,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
params.import_blendshapes = import_blendshapes;
params.validate_meshes = validate_meshes;
params.merge_parent_xform = merge_parent_xform;
params.import_guide = import_guide;
params.import_proxy = import_proxy;
@@ -1073,6 +1076,7 @@ static void wm_usd_import_draw(bContext *C, wmOperator *op)
col = uiLayoutColumn(panel, false);
uiItemR(col, ptr, "validate_meshes", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "merge_parent_xform", UI_ITEM_NONE, nullptr, ICON_NONE);
}
if (uiLayout *panel = uiLayoutPanel(C, layout, "USD_import_rigging", true, IFACE_("Rigging"))) {
@@ -1322,6 +1326,13 @@ void WM_OT_usd_import(wmOperatorType *ot)
"Defined Primitives Only",
"Import only defined USD primitives. When disabled this allows importing USD "
"primitives which are not defined, such as those with an override specifier");
RNA_def_boolean(ot->srna,
"merge_parent_xform",
true,
"Merge parent Xform",
"Allow USD primitives to merge with their Xform parent "
"if they are the only child in the hierarchy");
}
namespace blender::ed::io {

View File

@@ -281,11 +281,13 @@ bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable)
return true;
}
/* Determine if the given reader can use the parent of the encapsulated USD prim
* to compute the Blender object's transform. If so, the reader is appropriately
* flagged and the function returns true. Otherwise, the function returns false. */
static bool merge_with_parent(USDPrimReader *reader)
bool USDStageReader::merge_with_parent(USDPrimReader *reader) const
{
/* Don't merge if the param is set to false */
if (!params_.merge_parent_xform) {
return false;
}
USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader);
if (!xform_reader) {

View File

@@ -170,6 +170,13 @@ class USDStageReader {
*/
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
/**
* Returns true if the given reader can use the parent of the encapsulated USD prim
* to compute the Blender object's transform. If so, the reader is appropriately
* flagged and the function returns true. Otherwise, the function returns false.
*/
bool merge_with_parent(USDPrimReader *reader) const;
/**
* Returns true if the specified UsdPrim is a UsdGeom primitive,
* procedural shape, such as UsdGeomCube.

View File

@@ -212,6 +212,7 @@ struct USDImportParams {
bool set_material_blend;
bool validate_meshes;
bool merge_parent_xform;
eUSDMtlPurpose mtl_purpose;
eUSDMtlNameCollisionMode mtl_name_collision_mode;

View File

@@ -72,6 +72,30 @@ class USDImportTest(AbstractUSDTest):
self.assertEqual(objects['World'], objects['Empty'].parent, "Empty should be a child of /World")
self.assertEqual(objects['Empty'], objects['Plane_002'].parent, "Plane_002 should be a child of /World")
def test_import_xform_and_mesh_merged_false(self):
"""Test importing a simple object hierarchy (xform and mesh) from a USDA file."""
infile = str(self.testdir / "usd_mesh_polygon_types.usda")
res = bpy.ops.wm.usd_import(filepath=infile, merge_parent_xform=False)
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
objects = bpy.context.scene.collection.objects
self.assertEqual(10, len(objects), f"Test scene {infile} should have ten objects; found {len(objects)}")
# Test the hierarchy.
self.assertEqual(
objects['degenerate'],
objects['m_degenerate'].parent,
"m_degenerate should be child of /degenerate")
self.assertEqual(
objects['triangles'],
objects['m_triangles'].parent,
"m_triangles should be a child of /triangles")
self.assertEqual(objects['quad'], objects['m_quad'].parent, "m_quad should be a child of /quad")
self.assertEqual(objects['ngon_concave'], objects['m_ngon_concave'].parent,
"m_ngon_concave should be a child of /ngon_concave")
def test_import_mesh_topology(self):
"""Test importing meshes with different polygon types."""