diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index 221dc3b3185..3882e49c9ea 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -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", diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index c420eeceac6..8807c26510a 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -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 diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index 059fac45761..3d44ee3c07d 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -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; diff --git a/source/blender/io/usd/intern/usd_reader_instance.cc b/source/blender/io/usd/intern/usd_reader_instance.cc new file mode 100644 index 00000000000..70e4e13e24a --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_instance.cc @@ -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 diff --git a/source/blender/io/usd/intern/usd_reader_instance.h b/source/blender/io/usd/intern/usd_reader_instance.h new file mode 100644 index 00000000000..dcb35dfd2c7 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_instance.h @@ -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 + +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 diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index bbb2e565889..4a401161ebe 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -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 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, diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index b5451dbb782..7f0d3dc4318 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -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 &proto_collection_map) +{ + if (!instance_reader) { + return; + } + + pxr::SdfPath proto_path = instance_reader->proto_path(); + + std::map::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 ¶ms, 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 &r_readers) { if (prim.IsA()) { 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 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 protos = stage_->GetPrototypes(); + + for (const pxr::UsdPrim &proto_prim : protos) { + std::vector 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 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(reader)) { + set_instance_collection(instance_reader, proto_collection_map); + } + } + + /* Add the prototype objects to the collections. */ + for (const auto &pair : proto_readers_) { + + std::map::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 diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 503fc0d58cd..acbeb7f1811 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -19,7 +19,11 @@ struct ImportSettings; namespace blender::io::usd { -typedef std::map> ProtoReaderMap; +/** + * Map a USD prototype prim path to the list of readers that convert + * the prototype data. + */ +using ProtoReaderMap = std::map>; class USDStageReader { @@ -34,6 +38,9 @@ class USDStageReader { * traversal, for importing unused materials. */ std::vector material_paths_; + /* Readers for scenegraph instance prototypes. */ + ProtoReaderMap proto_readers_; + public: USDStageReader(pxr::UsdStageRefPtr stage, const USDImportParams ¶ms, @@ -89,6 +96,8 @@ class USDStageReader { void clear_readers(); + void clear_proto_readers(); + const std::vector &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 &r_readers); /** * Returns true if the given prim should be included in the diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 46161d322c6..fd0af551029 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -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(); diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 77de234c6bb..a4425566f5d 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -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;