USD import: Handle material name collisions

This is a partial fix for T90535.

Added Material Name Collision USD import menu option, to specify
the behavior when USD materials in different namespaces have the
same name.

The Material Name Collision menu options are

- Make Unique: Import each USD material as a unique Blender material.
- Reference Existing: If a material with the same name already
exists, reference that instead of importing.

Previously, the default behavior was to always keep the existing
material. This was causing an issue in the ALab scene, where
dozens of different USD materials all have the same name,
usdpreviewsurface1, so that only one instance of these materials
would be imported.

Reviewed by: Sybren

Differential Revision: https://developer.blender.org/D14869
This commit is contained in:
Michael Kowalski 2022-06-10 10:12:41 -04:00
parent 717ab5aeae
commit f6268f921a
4 changed files with 112 additions and 21 deletions

View File

@ -57,6 +57,20 @@ const EnumPropertyItem rna_enum_usd_export_evaluation_mode_items[] = {
{0, NULL, 0, NULL, NULL},
};
const EnumPropertyItem rna_enum_usd_mtl_name_collision_mode_items[] = {
{USD_MTL_NAME_COLLISION_MAKE_UNIQUE,
"MAKE_UNIQUE",
0,
"Make Unique",
"Import each USD material as a unique Blender material"},
{USD_MTL_NAME_COLLISION_REFERENCE_EXISTING,
"REFERENCE_EXISTING",
0,
"Reference Existing",
"If a material with the same name already exists, reference that instead of importing"},
{0, NULL, 0, NULL, NULL},
};
/* Stored in the wmOperator's customdata field to indicate it should run as a background job.
* This is set when the operator is invoked, and not set when it is only executed. */
enum { AS_BACKGROUND_JOB = 1 };
@ -371,6 +385,9 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const float light_intensity_scale = RNA_float_get(op->ptr, "light_intensity_scale");
const eUSDMtlNameCollisionMode mtl_name_collision_mode = RNA_enum_get(op->ptr,
"mtl_name_collision_mode");
/* TODO(makowalski): Add support for sequences. */
const bool is_sequence = false;
int offset = 0;
@ -409,7 +426,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
.use_instancing = use_instancing,
.import_usd_preview = import_usd_preview,
.set_material_blend = set_material_blend,
.light_intensity_scale = light_intensity_scale};
.light_intensity_scale = light_intensity_scale,
.mtl_name_collision_mode = mtl_name_collision_mode};
const bool ok = USD_import(C, filename, &params, as_background_job);
@ -452,6 +470,7 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(col, ptr, "relative_path", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "create_collection", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "light_intensity_scale", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "mtl_name_collision_mode", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Experimental"));
@ -575,6 +594,14 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
"Scale for the intensity of imported lights",
0.0001f,
1000.0f);
RNA_def_enum(
ot->srna,
"mtl_name_collision_mode",
rna_enum_usd_mtl_name_collision_mode_items,
USD_MTL_NAME_COLLISION_MAKE_UNIQUE,
"Material Name Collision",
"Behavior when the name of an imported material conflicts with an existing material");
}
#endif /* WITH_USD */

View File

@ -62,11 +62,56 @@ static void build_mat_map(const Main *bmain, std::map<std::string, Material *> *
}
}
static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim)
{
return pxr::UsdShadeMaterialBindingAPI(prim).ComputeBoundMaterial();
}
/* Returns an existing Blender material that corresponds to the USD
* material with 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;
}
else {
/* 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,
const USDImportParams &params,
pxr::UsdStageRefPtr stage)
pxr::UsdStageRefPtr stage,
std::map<std::string, std::string> &usd_path_to_mat_name)
{
if (!(stage && bmain && ob)) {
return;
@ -94,13 +139,10 @@ static void assign_materials(Main *bmain,
blender::io::usd::USDMaterialReader mat_reader(params, bmain);
for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) {
std::string mat_name = it->first.GetName();
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
Material *assigned_mat = nullptr;
if (mat_iter == mat_map.end()) {
Material *assigned_mat = find_existing_material(
it->first, params, mat_map, usd_path_to_mat_name);
if (!assigned_mat) {
/* Blender material doesn't exist, so create it now. */
/* Look up the USD material. */
@ -122,11 +164,14 @@ static void assign_materials(Main *bmain,
continue;
}
const std::string mat_name = pxr::TfMakeValidIdentifier(assigned_mat->id.name + 2);
mat_map[mat_name] = assigned_mat;
}
else {
/* We found an existing Blender material. */
assigned_mat = mat_iter->second;
if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) {
/* Record the name of the Blender material we created for the USD material
* with the given path. */
usd_path_to_mat_name[it->first.GetAsString()] = mat_name;
}
}
if (assigned_mat) {
@ -134,7 +179,7 @@ static void assign_materials(Main *bmain,
}
else {
/* This shouldn't happen. */
std::cout << "WARNING: Couldn't assign material " << mat_name << std::endl;
std::cout << "WARNING: Couldn't assign material " << it->first << std::endl;
}
}
}
@ -710,11 +755,8 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime,
int current_mat = 0;
if (!subsets.empty()) {
for (const pxr::UsdGeomSubset &subset : subsets) {
pxr::UsdShadeMaterialBindingAPI subset_api = pxr::UsdShadeMaterialBindingAPI(
subset.GetPrim());
pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial();
pxr::UsdShadeMaterial subset_mtl = utils::compute_bound_material(subset.GetPrim());
if (!subset_mtl) {
continue;
}
@ -743,10 +785,9 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime,
}
if (r_mat_map->empty()) {
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim_);
if (pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial()) {
pxr::UsdShadeMaterial mtl = utils::compute_bound_material(prim_);
if (mtl) {
pxr::SdfPath mtl_path = mtl.GetPath();
if (!mtl_path.IsEmpty()) {
@ -764,7 +805,12 @@ void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double mot
std::map<pxr::SdfPath, int> mat_map;
assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map);
utils::assign_materials(bmain, object_, mat_map, this->import_params_, this->prim_.GetStage());
utils::assign_materials(bmain,
object_,
mat_map,
this->import_params_,
this->prim_.GetStage(),
this->settings_->usd_path_to_mat_name);
}
Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,

View File

@ -7,6 +7,9 @@
#include <pxr/usd/usd/prim.h>
#include <map>
#include <string>
struct Main;
struct Object;
@ -33,6 +36,13 @@ struct ImportSettings {
CacheFile *cache_file;
/* Map a USD material prim path to a Blender material name.
* This map is updated by readers during stage traversal.
* This field is mutable because it is used to keep track
* of what the importer is doing. This is necessary even
* when all the other import settings are to remain const. */
mutable std::map<std::string, std::string> usd_path_to_mat_name;
ImportSettings()
: do_convert_mat(false),
from_up(0),

View File

@ -15,6 +15,13 @@ struct CacheReader;
struct Object;
struct bContext;
/* Behavior when the name of an imported material
* conflicts with an existing material. */
typedef enum eUSDMtlNameCollisionMode {
USD_MTL_NAME_COLLISION_MAKE_UNIQUE = 0,
USD_MTL_NAME_COLLISION_REFERENCE_EXISTING = 1,
} eUSDMtlNameCollisionMode;
struct USDExportParams {
bool export_animation;
bool export_hair;
@ -57,6 +64,7 @@ struct USDImportParams {
bool import_usd_preview;
bool set_material_blend;
float light_intensity_scale;
eUSDMtlNameCollisionMode mtl_name_collision_mode;
};
/* The USD_export takes a as_background_job parameter, and returns a boolean.