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:
parent
717ab5aeae
commit
f6268f921a
|
@ -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, ¶ms, 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 */
|
||||
|
|
|
@ -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 ¶ms,
|
||||
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 ¶ms,
|
||||
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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue