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
This commit is contained in:
Michael Kowalski 2023-01-25 10:46:07 -05:00
parent 69288daa74
commit ffe45ad87a
8 changed files with 187 additions and 53 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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<std::string, Material *> *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 &params,
const std::map<std::string, Material *> &mat_map,
const std::map<std::string, std::string> &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<std::string, std::string>::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<std::string, Material *>::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<std::string, Material *>::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

View File

@ -6,6 +6,8 @@
#include <pxr/usd/usdShade/material.h>
#include <map>
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<std::string, Material *> *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 &params,
const std::map<std::string, Material *> &mat_map,
const std::map<std::string, std::string> &usd_path_to_mat_name);
} // namespace blender::io::usd

View File

@ -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<std::string, Material *> *r_mat_map)
{
if (r_mat_map == nullptr) {
return;
}
Material *material = static_cast<Material *>(bmain->materials.first);
for (; material; material = static_cast<Material *>(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 &params,
const std::map<std::string, Material *> &mat_map,
const std::map<std::string, std::string> &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<std::string, std::string>::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<std::string, Material *>::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<std::string, Material *>::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<pxr::SdfPath, int> &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_,

View File

@ -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 <pxr/pxr.h>
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usdGeom/camera.h>
#include <pxr/usd/usdGeom/curves.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/nurbsCurves.h>
#include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdShade/material.h>
#if PXR_VERSION >= 2111
# include <pxr/usd/usdLux/boundableLightBase.h>
@ -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<pxr::UsdShadeMaterial>()) {
/* 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<std::string, std::string> &path_mat_pair : settings_.usd_path_to_mat_name) {
std::map<std::string, Material *>::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_) {

View File

@ -27,6 +27,10 @@ class USDStageReader {
std::vector<USDPrimReader *> readers_;
/* USD material prim paths encountered during stage
* traversal, for importing unused materials. */
std::vector<std::string> material_paths_;
public:
USDStageReader(pxr::UsdStageRefPtr stage,
const USDImportParams &params,
@ -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()

View File

@ -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.