USD: import scenegraph instances.

Added support for importing USD instanceable primitives into Blender
as collection instances.

Added a new USDInstanceReader class for importing USD
instances as Blender objects that instance collections containing
prototype data.

Extended the USDStageReader to read USD prototype prims into
collections that are instanced on the objects created by the instance
readers.

Removed the "Import Instance Proxies" import option.

Importing instances is enabled with a new "Scene Instancing" import
option, which is true by default.  If this option is off, instances will be
imported as copies (which is the functionality previously enabled by
the "Import Instance Proxies" option).

Removed calls to UsdSkelBindingAPI::Apply() in the skeleton and
blend shape import code, as these calls were unnecessary and were
generating errors when importing instance prototypes with UsdSkel
data.

Nested instancing and animated prototypes are supported.

Pull Request: https://projects.blender.org/blender/blender/pulls/115076
This commit is contained in:
Michael Kowalski 2023-12-28 19:08:23 +01:00 committed by Michael Kowalski
parent d1923eeadc
commit ea89e11e01
10 changed files with 304 additions and 37 deletions

View File

@ -473,7 +473,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const bool import_subdiv = RNA_boolean_get(op->ptr, "import_subdiv");
const bool import_instance_proxies = RNA_boolean_get(op->ptr, "import_instance_proxies");
const bool support_scene_instancing = RNA_boolean_get(op->ptr, "support_scene_instancing");
const bool import_visible_only = RNA_boolean_get(op->ptr, "import_visible_only");
@ -537,7 +537,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
params.import_blendshapes = import_blendshapes;
params.prim_path_mask = prim_path_mask;
params.import_subdiv = import_subdiv;
params.import_instance_proxies = import_instance_proxies;
params.support_scene_instancing = support_scene_instancing;
params.create_collection = create_collection;
params.import_guide = import_guide;
params.import_proxy = import_proxy;
@ -593,7 +593,7 @@ static void wm_usd_import_draw(bContext * /*C*/, wmOperator *op)
uiItemR(col, ptr, "read_mesh_attributes", UI_ITEM_NONE, nullptr, ICON_NONE);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Include"));
uiItemR(col, ptr, "import_subdiv", UI_ITEM_NONE, IFACE_("Subdivision"), ICON_NONE);
uiItemR(col, ptr, "import_instance_proxies", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "support_scene_instancing", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "import_visible_only", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "import_guide", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "import_proxy", UI_ITEM_NONE, nullptr, ICON_NONE);
@ -688,10 +688,10 @@ void WM_OT_usd_import(wmOperatorType *ot)
"SubdivisionScheme attribute");
RNA_def_boolean(ot->srna,
"import_instance_proxies",
"support_scene_instancing",
true,
"Import Instance Proxies",
"Create unique Blender objects for USD instances");
"Scene Instancing",
"Import USD scene graph instances as collection instances");
RNA_def_boolean(ot->srna,
"import_visible_only",

View File

@ -99,6 +99,7 @@ set(SRC
intern/usd_reader_camera.cc
intern/usd_reader_curve.cc
intern/usd_reader_geom.cc
intern/usd_reader_instance.cc
intern/usd_reader_light.cc
intern/usd_reader_material.cc
intern/usd_reader_mesh.cc
@ -133,6 +134,7 @@ set(SRC
intern/usd_reader_camera.h
intern/usd_reader_curve.h
intern/usd_reader_geom.h
intern/usd_reader_instance.h
intern/usd_reader_light.h
intern/usd_reader_material.h
intern/usd_reader_mesh.h

View File

@ -395,11 +395,18 @@ static void import_endjob(void *customdata)
lc = BKE_layer_collection_get_active(view_layer);
/* Create prototype collections for instancing. */
data->archive->create_proto_collections(data->bmain, lc->collection);
/* Add all objects to the collection. */
for (USDPrimReader *reader : data->archive->readers()) {
if (!reader) {
continue;
}
if (reader->prim().IsInPrototype()) {
/* Skip prototype prims, as these are added to prototype collections. */
continue;
}
Object *ob = reader->object();
if (!ob) {
continue;

View File

@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_reader_instance.h"
#include "BKE_lib_id.h"
#include "BKE_object.hh"
#include "DNA_collection_types.h"
#include "DNA_object_types.h"
namespace blender::io::usd {
USDInstanceReader::USDInstanceReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)
: USDXformReader(prim, import_params, settings)
{
}
bool USDInstanceReader::valid() const
{
return prim_.IsValid() && prim_.IsInstance();
}
void USDInstanceReader::create_object(Main *bmain, const double /* motionSampleTime */)
{
this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str());
this->object_->data = nullptr;
this->object_->instance_collection = nullptr;
this->object_->transflag |= OB_DUPLICOLLECTION;
}
void USDInstanceReader::set_instance_collection(Collection *coll)
{
if (this->object_ && this->object_->instance_collection != coll) {
if (this->object_->instance_collection) {
id_us_min(&this->object_->instance_collection->id);
this->object_->instance_collection = nullptr;
}
id_us_plus(&coll->id);
this->object_->instance_collection = coll;
}
}
pxr::SdfPath USDInstanceReader::proto_path() const
{
if (pxr::UsdPrim proto = prim_.GetPrototype()) {
return proto.GetPath();
}
return pxr::SdfPath();
}
} // namespace blender::io::usd

View File

@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "usd_reader_xform.h"
#include <pxr/usd/usdGeom/xform.h>
struct Collection;
namespace blender::io::usd {
/**
* Convert a USD instanced prim to a blender collection instance.
*/
class USDInstanceReader : public USDXformReader {
public:
USDInstanceReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings);
bool valid() const override;
/**
* Create an object that instances a collection.
*/
void create_object(Main *bmain, double motionSampleTime) override;
/**
* Assign the given collection to the object.
*/
void set_instance_collection(Collection *coll);
/**
* Get the path of the USD prototype prim.
*/
pxr::SdfPath proto_path() const;
};
} // namespace blender::io::usd

View File

@ -1144,11 +1144,7 @@ std::string USDMeshReader::get_skeleton_path() const
return "";
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_);
if (!skel_api) {
return "";
}
pxr::UsdSkelBindingAPI skel_api(prim_);
if (pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton()) {
return skel.GetPath().GetAsString();
@ -1166,8 +1162,9 @@ std::optional<XformResult> USDMeshReader::get_local_usd_xform(const float time)
return USDXformReader::get_local_usd_xform(time);
}
if (pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_)) {
if (skel_api.GetGeomBindTransformAttr().HasAuthoredValue()) {
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI(prim_);
if (pxr::UsdAttribute xf_attr = skel_api.GetGeomBindTransformAttr()) {
if (xf_attr.HasAuthoredValue()) {
pxr::GfMatrix4d bind_xf;
if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) {
/* The USD bind transform is a matrix of doubles,

View File

@ -5,6 +5,7 @@
#include "usd_reader_stage.h"
#include "usd_reader_camera.h"
#include "usd_reader_curve.h"
#include "usd_reader_instance.h"
#include "usd_reader_light.h"
#include "usd_reader_material.h"
#include "usd_reader_mesh.h"
@ -42,16 +43,60 @@
#include "BLI_sort.hh"
#include "BLI_string.h"
#include "BKE_collection.h"
#include "BKE_lib_id.h"
#include "BKE_modifier.hh"
#include "BKE_report.h"
#include "CLG_log.h"
#include "DNA_collection_types.h"
#include "DNA_material_types.h"
#include "WM_api.hh"
static CLG_LogRef LOG = {"io.usd"};
namespace blender::io::usd {
/**
* Create a collection with the given parent and name.
*/
static Collection *create_collection(Main *bmain, Collection *parent, const char *name)
{
if (!bmain) {
return nullptr;
}
return BKE_collection_add(bmain, parent, name);
}
/**
* Set the instance collection on the given instance reader.
* The collection is assigned from the given map based on
* the prototype prim path.
*/
static void set_instance_collection(
USDInstanceReader *instance_reader,
const std::map<pxr::SdfPath, Collection *> &proto_collection_map)
{
if (!instance_reader) {
return;
}
pxr::SdfPath proto_path = instance_reader->proto_path();
std::map<pxr::SdfPath, Collection *>::const_iterator it = proto_collection_map.find(proto_path);
if (it != proto_collection_map.end()) {
instance_reader->set_instance_collection(it->second);
}
else {
CLOG_WARN(
&LOG, "Couldn't find prototype collection for %s", instance_reader->prim_path().c_str());
}
}
USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
const USDImportParams &params,
const ImportSettings &settings)
@ -61,6 +106,7 @@ USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
USDStageReader::~USDStageReader()
{
clear_proto_readers();
clear_readers();
}
@ -78,6 +124,9 @@ bool USDStageReader::is_primitive_prim(const pxr::UsdPrim &prim) const
USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim)
{
if (params_.support_scene_instancing && prim.IsInstance()) {
return new USDInstanceReader(prim, params_, settings_);
}
if (params_.import_shapes && is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
}
@ -117,6 +166,9 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
{
if (params_.support_scene_instancing && prim.IsInstance()) {
return new USDInstanceReader(prim, params_, settings_);
}
if (is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
}
@ -249,7 +301,9 @@ static bool merge_with_parent(USDPrimReader *reader)
return true;
}
USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &prim)
USDPrimReader *USDStageReader::collect_readers(Main *bmain,
const pxr::UsdPrim &prim,
std::vector<USDPrimReader *> &r_readers)
{
if (prim.IsA<pxr::UsdGeomImageable>()) {
pxr::UsdGeomImageable imageable(prim);
@ -265,7 +319,7 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate;
if (params_.import_instance_proxies) {
if (!params_.support_scene_instancing) {
filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate);
}
@ -274,7 +328,7 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
std::vector<USDPrimReader *> child_readers;
for (const auto &childPrim : children) {
if (USDPrimReader *child_reader = collect_readers(bmain, childPrim)) {
if (USDPrimReader *child_reader = collect_readers(bmain, childPrim, r_readers)) {
child_readers.push_back(child_reader);
}
}
@ -316,7 +370,7 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
return nullptr;
}
readers_.push_back(reader);
r_readers.push_back(reader);
reader->incref();
/* Set each child reader's parent. */
@ -334,12 +388,29 @@ void USDStageReader::collect_readers(Main *bmain)
}
clear_readers();
clear_proto_readers();
/* Iterate through the stage. */
pxr::UsdPrim root = stage_->GetPseudoRoot();
stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
collect_readers(bmain, root);
collect_readers(bmain, root, readers_);
if (params_.support_scene_instancing) {
/* Collect the scenegraph instance prototypes. */
std::vector<pxr::UsdPrim> protos = stage_->GetPrototypes();
for (const pxr::UsdPrim &proto_prim : protos) {
std::vector<USDPrimReader *> proto_readers;
collect_readers(bmain, proto_prim, proto_readers);
proto_readers_.insert(std::make_pair(proto_prim.GetPath(), proto_readers));
for (USDPrimReader *reader : proto_readers) {
readers_.push_back(reader);
reader->incref();
}
}
}
}
void USDStageReader::process_armature_modifiers() const
@ -466,6 +537,27 @@ void USDStageReader::clear_readers()
readers_.clear();
}
void USDStageReader::clear_proto_readers()
{
for (auto &pair : proto_readers_) {
for (USDPrimReader *reader : pair.second) {
if (!reader) {
continue;
}
reader->decref();
if (reader->refcount() == 0) {
delete reader;
}
}
}
proto_readers_.clear();
}
void USDStageReader::sort_readers()
{
blender::parallel_sort(
@ -476,4 +568,66 @@ void USDStageReader::sort_readers()
});
}
void USDStageReader::create_proto_collections(Main *bmain, Collection *parent_collection)
{
if (proto_readers_.empty()) {
return;
}
Collection *all_protos_collection = create_collection(bmain, parent_collection, "prototypes");
if (all_protos_collection) {
all_protos_collection->flag |= COLLECTION_HIDE_VIEWPORT;
all_protos_collection->flag |= COLLECTION_HIDE_RENDER;
if (parent_collection) {
DEG_id_tag_update(&parent_collection->id, ID_RECALC_HIERARCHY);
}
}
std::map<pxr::SdfPath, Collection *> proto_collection_map;
for (const auto &pair : proto_readers_) {
Collection *proto_collection = create_collection(bmain, all_protos_collection, "proto");
proto_collection_map.insert(std::make_pair(pair.first, proto_collection));
}
/* Set the instance collections on the readers, including the prototype
* readers (which are included in readers_), as instancing may be nested. */
for (USDPrimReader *reader : readers_) {
if (USDInstanceReader *instance_reader = dynamic_cast<USDInstanceReader *>(reader)) {
set_instance_collection(instance_reader, proto_collection_map);
}
}
/* Add the prototype objects to the collections. */
for (const auto &pair : proto_readers_) {
std::map<pxr::SdfPath, Collection *>::const_iterator it = proto_collection_map.find(
pair.first);
if (it == proto_collection_map.end()) {
std::cerr << "WARNING: Couldn't find collection when adding objects for prototype "
<< pair.first << std::endl;
CLOG_WARN(&LOG,
"Couldn't find collection when adding objects for prototype %s",
pair.first.GetAsString().c_str());
continue;
}
for (USDPrimReader *reader : pair.second) {
Object *ob = reader->object();
if (!ob) {
continue;
}
Collection *coll = it->second;
BKE_collection_object_add(bmain, coll, ob);
}
}
}
} // Namespace blender::io::usd

View File

@ -19,7 +19,11 @@ struct ImportSettings;
namespace blender::io::usd {
typedef std::map<pxr::SdfPath, std::vector<USDPrimReader *>> ProtoReaderMap;
/**
* Map a USD prototype prim path to the list of readers that convert
* the prototype data.
*/
using ProtoReaderMap = std::map<pxr::SdfPath, std::vector<USDPrimReader *>>;
class USDStageReader {
@ -34,6 +38,9 @@ class USDStageReader {
* traversal, for importing unused materials. */
std::vector<std::string> material_paths_;
/* Readers for scenegraph instance prototypes. */
ProtoReaderMap proto_readers_;
public:
USDStageReader(pxr::UsdStageRefPtr stage,
const USDImportParams &params,
@ -89,6 +96,8 @@ class USDStageReader {
void clear_readers();
void clear_proto_readers();
const std::vector<USDPrimReader *> &readers() const
{
return readers_;
@ -96,8 +105,15 @@ class USDStageReader {
void sort_readers();
/**
* Create prototype collections for instancing by the USD instance readers.
*/
void create_proto_collections(Main *bmain, Collection *parent_collection);
private:
USDPrimReader *collect_readers(Main *bmain, const pxr::UsdPrim &prim);
USDPrimReader *collect_readers(Main *bmain,
const pxr::UsdPrim &prim,
std::vector<USDPrimReader *> &r_readers);
/**
* Returns true if the given prim should be included in the

View File

@ -354,12 +354,7 @@ void import_blendshapes(Main *bmain,
return;
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim);
if (!skel_api) {
/* No skel binding. */
return;
}
pxr::UsdSkelBindingAPI skel_api(prim);
/* Get the blend shape targets, which are the USD paths to the
* blend shape primitives. */
@ -538,14 +533,16 @@ void import_blendshapes(Main *bmain,
return;
}
skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim.GetPrim());
if (!skel_api) {
return;
}
skel_api = pxr::UsdSkelBindingAPI(skel_prim.GetPrim());
pxr::UsdPrim anim_prim = skel_api.GetInheritedAnimationSource();
if (!anim_prim) {
/* Querying the directly bound animation source may be necessary
* if the prim does not have an applied skel binding API schema. */
skel_api.GetAnimationSource(&anim_prim);
}
if (!anim_prim) {
return;
}
@ -896,11 +893,7 @@ void import_mesh_skel_bindings(Main *bmain,
return;
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim);
if (!skel_api) {
return;
}
pxr::UsdSkelBindingAPI skel_api(prim);
pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton();

View File

@ -100,7 +100,7 @@ struct USDImportParams {
bool import_blendshapes;
char *prim_path_mask;
bool import_subdiv;
bool import_instance_proxies;
bool support_scene_instancing;
bool create_collection;
bool import_guide;
bool import_proxy;