USD: import scenegraph instances. #115076

Merged
Michael Kowalski merged 20 commits from makowalski/blender:usd-scene-instancing-import into main 2023-12-28 19:08:32 +01:00
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 {
makowalski marked this conversation as resolved
Review

unused include

unused include
Review

Thanks for spotting this!

Thanks for spotting this!
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;
}
makowalski marked this conversation as resolved
Review

Is that depsgraph tagging actually needed? Afaik newly created data does not exist in the depsgraph, so it is automatically fully evaluated on the next update?

In fact, I think it would be much more correct to tag the parent collection, since that one sees its hierarchy modified (and also rather use ID_RECALC_HIERARCHY).

Is that depsgraph tagging actually needed? Afaik newly created data does not exist in the depsgraph, so it is automatically fully evaluated on the next update? In fact, I think it would be much more correct to tag the _parent_ collection, since that one sees its hierarchy modified (and also rather use `ID_RECALC_HIERARCHY`).
makowalski marked this conversation as resolved
Review

I want to say that setting the fake user for collections isn't really necessary but will need Bastien to weigh in. I believe they'll always have a real user because there's always a 'parent' collection available.

I want to say that setting the fake user for collections isn't really necessary but will need Bastien to weigh in. I believe they'll always have a real user because there's always a 'parent' collection available.
Review

I removed the call to add the fake user. Thanks for catching this.

I removed the call to add the fake user. Thanks for catching this.
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());
}
makowalski marked this conversation as resolved
Review

Should use CLOG instead of 'raw' prints.

Should use `CLOG` instead of 'raw' prints.
}
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();
makowalski marked this conversation as resolved Outdated

Not so important to me, but just observation that now that scene instances are supported, this reads rather strange, and it may be a good idea to change the name and meaning of the underlying variable.

E.g.,

if (params_.support_scene_instancing) {

Not so important to me, but just observation that now that scene instances are supported, this reads rather strange, and it may be a good idea to change the name and meaning of the underlying variable. E.g., `if (params_.support_scene_instancing) {`

You make a good point.

If I understand your idea correctly, we'd still have just one import option related to instancing: "Convert Instances to Copies", as you suggested above.

But the params_.import_instance_proxies variable would be renamed to params_.support_scene_instancing, which would be set to true if the "Convert Instances to Copies" option is off.

Is that correct?

You make a good point. If I understand your idea correctly, we'd still have just one import option related to instancing: "Convert Instances to Copies", as you suggested above. But the `params_.import_instance_proxies` variable would be renamed to `params_.support_scene_instancing`, which would be set to `true` if the "Convert Instances to Copies" option is off. Is that correct?

Per discussion with Matt today, I will replace the "Convert Instances to Copies" option with a "Support Scene Instancing" option in the UI and import params.

Per discussion with Matt today, I will replace the "Convert Instances to Copies" option with a "Support Scene Instancing" option in the UI and import params.

I replaced the Import Instance Proxies with a new Scene Instancing option.

I replaced the `Import Instance Proxies` with a new `Scene Instancing` option.
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()) {
makowalski marked this conversation as resolved
Review

Use CLOG instead.

Use `CLOG` instead.
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;