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.