From ffe45ad87a564fd2cb7d53f222785ecb608a6e30 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Wed, 25 Jan 2023 10:46:07 -0500 Subject: [PATCH] USD import unused materials. Added a new Import All Materials USD import option. When this option is enabled, USD materials not used by any geometry will be included in the import. Imported materials with no users will have a fake user assigned. Maniphest Tasks: T97195 Differential Revision: https://developer.blender.org/D16172 --- source/blender/editors/io/io_usd.c | 14 +++- .../blender/io/usd/intern/usd_capi_import.cc | 8 ++ .../io/usd/intern/usd_reader_material.cc | 41 ++++++++++ .../io/usd/intern/usd_reader_material.h | 30 ++++++++ .../blender/io/usd/intern/usd_reader_mesh.cc | 54 +------------ .../blender/io/usd/intern/usd_reader_stage.cc | 77 +++++++++++++++++++ .../blender/io/usd/intern/usd_reader_stage.h | 15 ++++ source/blender/io/usd/usd.h | 1 + 8 files changed, 187 insertions(+), 53 deletions(-) diff --git a/source/blender/editors/io/io_usd.c b/source/blender/editors/io/io_usd.c index acd60bbd40a..99d4e84cfd4 100644 --- a/source/blender/editors/io/io_usd.c +++ b/source/blender/editors/io/io_usd.c @@ -381,6 +381,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) const bool import_proxy = RNA_boolean_get(op->ptr, "import_proxy"); const bool import_render = RNA_boolean_get(op->ptr, "import_render"); + const bool import_all_materials = RNA_boolean_get(op->ptr, "import_all_materials"); + const bool import_usd_preview = RNA_boolean_get(op->ptr, "import_usd_preview"); const bool set_material_blend = RNA_boolean_get(op->ptr, "set_material_blend"); @@ -427,7 +429,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) .import_usd_preview = import_usd_preview, .set_material_blend = set_material_blend, .light_intensity_scale = light_intensity_scale, - .mtl_name_collision_mode = mtl_name_collision_mode}; + .mtl_name_collision_mode = mtl_name_collision_mode, + .import_all_materials = import_all_materials}; STRNCPY(params.prim_path_mask, prim_path_mask); @@ -480,6 +483,7 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op) box = uiLayoutBox(layout); col = uiLayoutColumnWithHeading(box, true, IFACE_("Materials")); + uiItemR(col, ptr, "import_all_materials", 0, NULL, ICON_NONE); uiItemR(col, ptr, "import_usd_preview", 0, NULL, ICON_NONE); uiLayoutSetEnabled(col, RNA_boolean_get(ptr, "import_materials")); uiLayout *row = uiLayoutRow(col, true); @@ -579,6 +583,14 @@ void WM_OT_usd_import(struct wmOperatorType *ot) RNA_def_boolean(ot->srna, "import_render", true, "Render", "Import final render geometry"); + RNA_def_boolean(ot->srna, + "import_all_materials", + false, + "Import All Materials", + "Also import materials that are not used by any geometry. " + "Note that when this option is false, materials referenced " + "by geometry will still be imported"); + RNA_def_boolean(ot->srna, "import_usd_preview", true, diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index 600d1f0a9eb..66319a7f04e 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -227,6 +227,10 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float archive->collect_readers(data->bmain); + if (data->params.import_materials && data->params.import_all_materials) { + archive->import_all_materials(data->bmain); + } + *data->do_update = true; *data->progress = 0.2f; @@ -352,6 +356,10 @@ static void import_endjob(void *customdata) DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); DEG_relations_tag_update(data->bmain); + + if (data->params.import_materials && data->params.import_all_materials) { + data->archive->fake_users_for_unused_materials(); + } } WM_set_locked_interface(data->wm, false); diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc index d1af4553083..351f9bc3438 100644 --- a/source/blender/io/usd/intern/usd_reader_material.cc +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -757,4 +757,45 @@ void USDMaterialReader::convert_usd_primvar_reader_float2( link_nodes(ntree, uv_map, "UV", dest_node, dest_socket_name); } +void build_material_map(const Main *bmain, std::map *r_mat_map) +{ + BLI_assert_msg(r_mat_map, "..."); + + LISTBASE_FOREACH (Material *, material, &bmain->materials) { + std::string usd_name = pxr::TfMakeValidIdentifier(material->id.name + 2); + (*r_mat_map)[usd_name] = material; + } +} + +Material *find_existing_material(const pxr::SdfPath &usd_mat_path, + const USDImportParams ¶ms, + const std::map &mat_map, + const std::map &usd_path_to_mat_name) +{ + if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) { + /* Check if we've already created the Blender material with a modified name. */ + std::map::const_iterator path_to_name_iter = + usd_path_to_mat_name.find(usd_mat_path.GetAsString()); + + if (path_to_name_iter == usd_path_to_mat_name.end()) { + return nullptr; + } + + std::string mat_name = path_to_name_iter->second; + std::map::const_iterator mat_iter = mat_map.find(mat_name); + BLI_assert_msg(mat_iter != mat_map.end(), + "Previously created material cannot be found any more"); + return mat_iter->second; + } + + std::string mat_name = usd_mat_path.GetName(); + std::map::const_iterator mat_iter = mat_map.find(mat_name); + + if (mat_iter == mat_map.end()) { + return nullptr; + } + + return mat_iter->second; +} + } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_material.h b/source/blender/io/usd/intern/usd_reader_material.h index 24d80e99c38..6fd6d2f1213 100644 --- a/source/blender/io/usd/intern/usd_reader_material.h +++ b/source/blender/io/usd/intern/usd_reader_material.h @@ -6,6 +6,8 @@ #include +#include + struct Main; struct Material; struct bNode; @@ -129,4 +131,32 @@ class USDMaterialReader { NodePlacementContext *r_ctx) const; }; +/* Utility functions. */ + +/** + * Returns a map containing all the Blender materials which allows a fast + * lookup of the material by name. Note that the material name key + * might be modified to be a valid USD identifier, to match material + * names in the imported USD. + */ +void build_material_map(const Main *bmain, std::map *r_mat_map); + +/** + * Returns an existing Blender material that corresponds to the USD material with the given path. + * Returns null if no such material exists. + * + * \param mat_map Map a material name to a Blender material. Note that the name key + * might be the Blender material name modified to be a valid USD identifier, + * to match the material names in the imported USD. + * \param usd_path_to_mat_name Map a USD material path to the imported Blender material name. + * + * The usd_path_to_mat_name is needed to determine the name of the Blender + * material imported from a USD path in the case when a unique name was generated + * for the material due to a name collision. + */ +Material *find_existing_material(const pxr::SdfPath &usd_mat_path, + const USDImportParams ¶ms, + const std::map &mat_map, + const std::map &usd_path_to_mat_name); + } // 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 f961fa64a05..fd4b80b9137 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -49,20 +49,6 @@ static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal); } // namespace usdtokens namespace utils { -/* Very similar to #blender::io::alembic::utils. */ -static void build_mat_map(const Main *bmain, std::map *r_mat_map) -{ - if (r_mat_map == nullptr) { - return; - } - - Material *material = static_cast(bmain->materials.first); - - for (; material; material = static_cast(material->id.next)) { - /* We have to do this because the stored material name is coming directly from USD. */ - (*r_mat_map)[pxr::TfMakeValidIdentifier(material->id.name + 2)] = material; - } -} static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim) { @@ -84,42 +70,6 @@ static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim) return mtl; } -/* Returns an existing Blender material that corresponds to the USD material with the given path. - * Returns null if no such material exists. */ -static Material *find_existing_material( - const pxr::SdfPath &usd_mat_path, - const USDImportParams ¶ms, - const std::map &mat_map, - const std::map &usd_path_to_mat_name) -{ - if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) { - /* Check if we've already created the Blender material with a modified name. */ - std::map::const_iterator path_to_name_iter = - usd_path_to_mat_name.find(usd_mat_path.GetAsString()); - - if (path_to_name_iter != usd_path_to_mat_name.end()) { - std::string mat_name = path_to_name_iter->second; - std::map::const_iterator mat_iter = mat_map.find(mat_name); - if (mat_iter != mat_map.end()) { - return mat_iter->second; - } - /* We can't find the Blender material which was previously created for this USD - * material, which should never happen. */ - BLI_assert_unreachable(); - } - } - else { - std::string mat_name = usd_mat_path.GetName(); - std::map::const_iterator mat_iter = mat_map.find(mat_name); - - if (mat_iter != mat_map.end()) { - return mat_iter->second; - } - } - - return nullptr; -} - static void assign_materials(Main *bmain, Object *ob, const std::map &mat_index_map, @@ -142,7 +92,7 @@ static void assign_materials(Main *bmain, it != mat_index_map.end(); ++it) { - Material *assigned_mat = find_existing_material( + Material *assigned_mat = blender::io::usd::find_existing_material( it->first, params, mat_name_to_mat, usd_path_to_mat_name); if (!assigned_mat) { /* Blender material doesn't exist, so create it now. */ @@ -805,7 +755,7 @@ void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double mot material_indices.finish(); /* Build material name map if it's not built yet. */ if (this->settings_->mat_name_to_mat.empty()) { - utils::build_mat_map(bmain, &this->settings_->mat_name_to_mat); + build_material_map(bmain, &this->settings_->mat_name_to_mat); } utils::assign_materials(bmain, object_, diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index df75be849e2..0c179ceae48 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_camera.h" #include "usd_reader_curve.h" #include "usd_reader_light.h" +#include "usd_reader_material.h" #include "usd_reader_mesh.h" #include "usd_reader_nurbs.h" #include "usd_reader_prim.h" @@ -12,12 +13,14 @@ #include "usd_reader_xform.h" #include +#include #include #include #include #include #include #include +#include #if PXR_VERSION >= 2111 # include @@ -31,6 +34,10 @@ #include "BLI_sort.hh" #include "BLI_string.h" +#include "BKE_lib_id.h" + +#include "DNA_material_types.h" + namespace blender::io::usd { USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage, @@ -249,6 +256,15 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim & } } + if (prim.IsA()) { + /* Record material path for later processing, if needed, + * e.g., when importing all materials. */ + material_paths_.push_back(prim.GetPath().GetAsString()); + + /* We don't create readers for materials, so return early. */ + return nullptr; + } + USDPrimReader *reader = create_reader_if_allowed(prim); if (!reader) { @@ -294,6 +310,67 @@ void USDStageReader::collect_readers(Main *bmain) collect_readers(bmain, root); } +void USDStageReader::import_all_materials(Main *bmain) +{ + BLI_assert(valid()); + + /* Build the material name map if it's not built yet. */ + if (settings_.mat_name_to_mat.empty()) { + build_material_map(bmain, &settings_.mat_name_to_mat); + } + + USDMaterialReader mtl_reader(params_, bmain); + + for (const std::string &mtl_path : material_paths_) { + pxr::UsdPrim prim = stage_->GetPrimAtPath(pxr::SdfPath(mtl_path)); + + pxr::UsdShadeMaterial usd_mtl(prim); + if (!usd_mtl) { + continue; + } + + if (blender::io::usd::find_existing_material( + prim.GetPath(), params_, settings_.mat_name_to_mat, settings_.usd_path_to_mat_name)) { + /* The material already exists. */ + continue; + } + + /* Add the material now. */ + Material *new_mtl = mtl_reader.add_material(usd_mtl); + BLI_assert_msg(new_mtl, "Failed to create material"); + + const std::string mtl_name = pxr::TfMakeValidIdentifier(new_mtl->id.name + 2); + settings_.mat_name_to_mat[mtl_name] = new_mtl; + + if (params_.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) { + /* Record the unique name of the Blender material we created for the USD material + * with the given path, so we don't import the material again when assigning + * materials to objects elsewhere in the code. */ + settings_.usd_path_to_mat_name[prim.GetPath().GetAsString()] = mtl_name; + } + } +} + +void USDStageReader::fake_users_for_unused_materials() +{ + /* Iterate over the imported materials and set a fake user for any unused + * materials. */ + for (const std::pair &path_mat_pair : settings_.usd_path_to_mat_name) { + + std::map::iterator mat_it = settings_.mat_name_to_mat.find( + path_mat_pair.second); + + if (mat_it == settings_.mat_name_to_mat.end()) { + continue; + } + + Material *mat = mat_it->second; + if (mat->id.us == 0) { + id_fake_user_set(&mat->id); + } + } +} + void USDStageReader::clear_readers() { for (USDPrimReader *reader : readers_) { diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 5f4a343f874..9b2c37132bc 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -27,6 +27,10 @@ class USDStageReader { std::vector readers_; + /* USD material prim paths encountered during stage + * traversal, for importing unused materials. */ + std::vector material_paths_; + public: USDStageReader(pxr::UsdStageRefPtr stage, const USDImportParams ¶ms, @@ -40,6 +44,17 @@ class USDStageReader { void collect_readers(struct Main *bmain); + /* Convert every material prim on the stage to a Blender + * material, including materials not used by any geometry. + * Note that collect_readers() must be called before calling + * import_all_materials(). */ + void import_all_materials(struct Main *bmain); + + /* Add fake users for any imported materials with no + * users. This is typically required when importing all + * materials. */ + void fake_users_for_unused_materials(); + bool valid() const; pxr::UsdStageRefPtr stage() diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 9d5cda64424..ffd3bd6df4c 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -64,6 +64,7 @@ struct USDImportParams { bool set_material_blend; float light_intensity_scale; eUSDMtlNameCollisionMode mtl_name_collision_mode; + bool import_all_materials; }; /* The USD_export takes a as_background_job parameter, and returns a boolean.