USD IO: material import improvements

UDIM texture support on UsdPreviewSurface import.
New Material Name Collision option for sepcifying behavior when
an imported material name conflicts with the name of an
existing material. Also includes format fixes.
This commit is contained in:
Michael Kowalski 2021-08-30 14:09:17 -04:00
parent baeeb1488e
commit 2e32a0871f
8 changed files with 215 additions and 53 deletions

View File

@ -100,8 +100,16 @@ const EnumPropertyItem rna_enum_usd_import_shaders_mode_items_no_umm[] = {
};
const EnumPropertyItem rna_enum_usd_xform_op_mode_items[] = {
{USD_XFORM_OP_SRT, "SRT", 0, "Scale, Rotate, Translate", "Export scale, rotate, and translate Xform operators"},
{USD_XFORM_OP_SOT, "SOT", 0, "Scale, Orient, Translate", "Export scale, orient, and translate Xform operators"},
{USD_XFORM_OP_SRT,
"SRT",
0,
"Scale, Rotate, Translate",
"Export scale, rotate, and translate Xform operators"},
{USD_XFORM_OP_SOT,
"SOT",
0,
"Scale, Orient, Translate",
"Export scale, orient, and translate Xform operators"},
{USD_XFORM_OP_MAT, "MAT", 0, "Matrix", "Export matrix operator"},
{0, NULL, 0, NULL, NULL},
};
@ -126,6 +134,20 @@ const EnumPropertyItem prop_usd_export_global_up[] = {
{0, NULL, 0, NULL, NULL},
};
const EnumPropertyItem rna_enum_usd_mtl_name_collision_mode_items[] = {
{USD_MTL_NAME_COLLISION_MODIFY,
"MODIFY",
0,
"Modify",
"Create a unique name for the imported material"},
{USD_MTL_NAME_COLLISION_SKIP,
"SKIP",
0,
"Skip",
"Keep the existing material and discard the imported material"},
{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 };
@ -813,11 +835,11 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
"connected to a background shader, with an optional vector multiply of the texture color.");
RNA_def_enum(ot->srna,
"xform_op_mode",
rna_enum_usd_xform_op_mode_items,
USD_XFORM_OP_SRT,
"Xform Ops",
"The type of transform operators to write");
"xform_op_mode",
rna_enum_usd_xform_op_mode_items,
USD_XFORM_OP_SRT,
"Xform Ops",
"The type of transform operators to write");
}
/* ====== USD Import ====== */
@ -901,6 +923,9 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const bool create_background_shader = RNA_boolean_get(op->ptr, "create_background_shader");
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;
@ -942,7 +967,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
.apply_unit_conversion_scale = apply_unit_conversion_scale,
.convert_light_from_nits = convert_light_from_nits,
.scale_light_radius = scale_light_radius,
.create_background_shader = create_background_shader};
.create_background_shader = create_background_shader,
.mtl_name_collision_mode = mtl_name_collision_mode};
const bool ok = USD_import(C, filename, &params, as_background_job);
@ -989,6 +1015,7 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(box, ptr, "convert_light_from_nits", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "scale_light_radius", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "create_background_shader", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "mtl_name_collision_mode", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Experimental"));
@ -1161,6 +1188,14 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
true,
"Create Background Shader",
"Convert USD dome lights to world background shaders");
RNA_def_enum(
ot->srna,
"mtl_name_collision_mode",
rna_enum_usd_mtl_name_collision_mode_items,
USD_MTL_NAME_COLLISION_MODIFY,
"Material Name Collision",
"Behavior when the name of an imported material conflicts with an existing material");
}
#endif /* WITH_USD */

View File

@ -39,9 +39,9 @@
#include "BKE_node.h"
#include "BKE_scene.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_math.h"
#include "DNA_light_types.h"
#include "DNA_scene_types.h"
#include "DNA_world_types.h"

View File

@ -26,12 +26,15 @@
#include "BKE_material.h"
#include "BKE_node.h"
#include "BLI_fileops.h"
#include "BLI_math_vector.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "DNA_material_types.h"
#include <pxr/base/gf/vec3f.h>
#include <pxr/usd/ar/resolver.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/shader.h>
@ -111,6 +114,62 @@ static void link_nodes(
nodeAddLink(ntree, source, source_socket, dest, dest_socket);
}
static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &Attribute)
{
for (auto PropertySpec : Attribute.GetPropertyStack(pxr::UsdTimeCode::EarliestTime())) {
if (PropertySpec->HasDefaultValue() ||
PropertySpec->GetLayer()->GetNumTimeSamplesForPath(PropertySpec->GetPath()) > 0) {
return PropertySpec->GetLayer();
}
}
return pxr::SdfLayerHandle();
}
static void add_udim_tiles(Image *image)
{
if (!image || strlen(image->filepath) == 0) {
return;
}
char filename[FILE_MAX], dirname[FILE_MAXDIR];
BLI_split_dirfile(image->filepath, dirname, filename, sizeof(dirname), sizeof(filename));
ushort digits;
char base_head[FILE_MAX], base_tail[FILE_MAX];
int id = BLI_path_sequence_decode(filename, base_head, base_tail, &digits);
if (id != 1001) {
return;
}
image->source = IMA_SRC_TILED;
struct direntry *dir;
uint totfile = BLI_filelist_dir_contents(dirname, &dir);
for (int i = 0; i < totfile; i++) {
if (!(dir[i].type & S_IFREG)) {
continue;
}
char head[FILE_MAX], tail[FILE_MAX];
id = BLI_path_sequence_decode(dir[i].relname, head, tail, &digits);
if (digits > 4 || !(STREQLEN(base_head, head, FILE_MAX)) ||
!(STREQLEN(base_tail, tail, FILE_MAX))) {
continue;
}
if (id < 1001 || id >= IMA_UDIM_MAX) {
continue;
}
BKE_image_add_tile(image, id, nullptr);
}
BLI_filelist_free(dir, totfile);
}
/* Returns true if the given shader may have opacity < 1.0, based
* on heuristics. */
static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
@ -627,7 +686,26 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
}
const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>();
std::string file_path = asset_path.GetResolvedPath();
bool have_udim = false;
if (file_path.empty()) {
file_path = asset_path.GetAssetPath();
/* Check if we have a UDIM path. */
std::size_t udim_token_offset = file_path.find("<UDIM>");
if (udim_token_offset != std::string::npos) {
have_udim = true;
file_path.replace(udim_token_offset, 6, "1001");
if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
file_path = layer_handle->ComputeAbsolutePath(file_path);
}
}
}
if (file_path.empty()) {
std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path
<< "' for Texture Image node." << std::endl;
@ -635,13 +713,19 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
}
const char *im_file = file_path.c_str();
Image *image = BKE_image_load_exists(bmain_, im_file);
if (!image) {
std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node."
<< std::endl;
return;
}
if (have_udim) {
add_udim_tiles(image);
}
tex_image->id = &image->id;
/* Set texture color space.

View File

@ -80,41 +80,61 @@ 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;
}
bool can_assign = true;
std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin();
int matcount = 0;
for (; it != mat_index_map.end(); ++it, matcount++) {
for (; it != mat_index_map.end(); ++it) {
if (!BKE_object_material_slot_add(bmain, ob)) {
can_assign = false;
break;
std::cout << "WARNING: couldn't create slot for material " << it->first << " on object "
<< ob->id.name << std::endl;
return;
}
}
if (!can_assign) {
return;
}
/* TODO(kevin): use global map? */
/* TODO(makowalski): use global map? */
std::map<std::string, Material *> mat_map;
build_mat_map(bmain, &mat_map);
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()) {
if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MODIFY) {
/* 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(it->first.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 *>::iterator mat_iter = mat_map.find(mat_name);
if (mat_iter != mat_map.end()) {
assigned_mat = mat_iter->second;
}
else {
std::cout
<< "WARNING: Couldn't find previously assigned Blender material for USD material "
<< it->first << std::endl;
}
}
}
else {
std::string mat_name = it->first.GetName();
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
if (mat_iter != mat_map.end()) {
assigned_mat = mat_iter->second;
}
}
if (!assigned_mat) {
/* Blender material doesn't exist, so create it now. */
/* Look up the USD material. */
@ -136,11 +156,14 @@ static void assign_materials(Main *bmain,
continue;
}
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_MODIFY) {
/* 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) {
@ -148,7 +171,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;
}
}
}
@ -749,7 +772,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

@ -24,6 +24,9 @@
#include <pxr/usd/usd/prim.h>
#include <map>
#include <string>
struct CacheFile;
struct Main;
struct Object;
@ -49,6 +52,14 @@ struct ImportSettings {
bool validate_meshes;
/* Map a USD matrial prim path to a Blender material name.
* This map might be updated by readers during stage traversal.
* TODO(makowalski): Is the ImportSettings struct the best place
* to store this map? Maybe we should define an ImportContext
* struct that stores USDImportParams, ImportSettings and
* mutable values such as this. */
mutable std::map<std::string, std::string> usd_path_to_mat_name;
ImportSettings()
: do_convert_mat(false),
from_up(0),

View File

@ -147,7 +147,7 @@ static void export_in_memory_texture(Image *ima, const std::string &export_dir)
return;
}
char file_name[FILE_MAX] = { 0 };
char file_name[FILE_MAX] = {0};
if (strlen(ima->filepath) > 0) {
BLI_split_file_part(ima->filepath, file_name, FILE_MAX);
@ -2204,10 +2204,8 @@ void create_usd_cycles_material(pxr::UsdStageRefPtr a_stage,
usd_material.GetPath().AppendChild(cyclestokens::cycles),
&output,
export_params);
link_cycles_nodes(a_stage,
usd_material,
localtree,
usd_material.GetPath().AppendChild(cyclestokens::cycles));
link_cycles_nodes(
a_stage, usd_material, localtree, usd_material.GetPath().AppendChild(cyclestokens::cycles));
ntreeFreeLocalTree(localtree);
MEM_freeN(localtree);

View File

@ -194,7 +194,7 @@ bool USDTransformWriter::check_is_animated(const HierarchyContext &context) cons
return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
}
void USDTransformWriter::set_xform_ops(float xf_matrix[4][4], pxr::UsdGeomXformable &xf)
void USDTransformWriter::set_xform_ops(float xf_matrix[4][4], pxr::UsdGeomXformable &xf)
{
if (!xf) {
return;
@ -204,24 +204,24 @@ void USDTransformWriter::set_xform_ops(float xf_matrix[4][4], pxr::UsdGeomXform
if (xformOps_.empty()) {
switch (xfOpMode) {
case USD_XFORM_OP_SRT:
xformOps_.push_back(xf.AddTranslateOp());
xformOps_.push_back(xf.AddRotateXYZOp());
xformOps_.push_back(xf.AddScaleOp());
case USD_XFORM_OP_SRT:
xformOps_.push_back(xf.AddTranslateOp());
xformOps_.push_back(xf.AddRotateXYZOp());
xformOps_.push_back(xf.AddScaleOp());
break;
case USD_XFORM_OP_SOT:
xformOps_.push_back(xf.AddTranslateOp());
xformOps_.push_back(xf.AddOrientOp());
xformOps_.push_back(xf.AddScaleOp());
break;
case USD_XFORM_OP_MAT:
xformOps_.push_back(xf.AddTransformOp());
break;
default:
printf("Warning: unknown XformOp type\n");
xformOps_.push_back(xf.AddTransformOp());
break;
break;
case USD_XFORM_OP_SOT:
xformOps_.push_back(xf.AddTranslateOp());
xformOps_.push_back(xf.AddOrientOp());
xformOps_.push_back(xf.AddScaleOp());
break;
case USD_XFORM_OP_MAT:
xformOps_.push_back(xf.AddTransformOp());
break;
default:
printf("Warning: unknown XformOp type\n");
xformOps_.push_back(xf.AddTransformOp());
break;
}
}

View File

@ -64,6 +64,11 @@ typedef enum eUSDXformOpMode {
static const USD_global_forward_axis USD_DEFAULT_FORWARD = USD_GLOBAL_FORWARD_MINUS_Z;
static const USD_global_up_axis USD_DEFAULT_UP = USD_GLOBAL_UP_Y;
typedef enum eUSDMtlNameCollisionMode {
USD_MTL_NAME_COLLISION_MODIFY = 0,
USD_MTL_NAME_COLLISION_SKIP = 1,
} eUSDMtlNameCollisionMode;
struct USDExportParams {
double frame_start;
double frame_end;
@ -152,6 +157,7 @@ struct USDImportParams {
bool convert_light_from_nits;
bool scale_light_radius;
bool create_background_shader;
eUSDMtlNameCollisionMode mtl_name_collision_mode;
};
/* The USD_export takes a as_background_job parameter, and returns a boolean.