Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Kowalski <makowalski@nvidia.com>2021-11-19 21:48:02 +0300
committerMichael Kowalski <makowalski@nvidia.com>2021-11-19 21:48:02 +0300
commit05c4982f712be287b901713e6d8cfc0cbc208696 (patch)
tree169a052f41038c1d7e9bd4ea68ae64537ca64300
parent4c988eb3e144712b98fddc97b2e669e88a23eaa8 (diff)
USD Preview Surface material import improvements.
Updates to address issues importing USD Preview Surface materials in Animal Logic's ALab scene, as described in T90535: Added support for importing UDIM textures. Added 'Material Name Collision' USD import menu option, to specify the behavior when USD materials in different namespaces have the same name. The options are Modify: Create a unique name for the imported material. Skip: Keep the existing material and discard the imported material. Previously, the default behavior was Skip. 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. Finally, if no materials with purpose "render" are assigned to the USD primitive, the importer will now fall back on converting assigned materials with purpose "preview".
-rw-r--r--source/blender/editors/io/io_usd.c28
-rw-r--r--source/blender/io/usd/intern/usd_reader_material.cc149
-rw-r--r--source/blender/io/usd/intern/usd_reader_mesh.cc84
-rw-r--r--source/blender/io/usd/intern/usd_reader_prim.h7
-rw-r--r--source/blender/io/usd/usd.h6
5 files changed, 251 insertions, 23 deletions
diff --git a/source/blender/editors/io/io_usd.c b/source/blender/editors/io/io_usd.c
index 4e2ccea36ab..39f09014a61 100644
--- a/source/blender/editors/io/io_usd.c
+++ b/source/blender/editors/io/io_usd.c
@@ -73,6 +73,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_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 };
@@ -318,6 +332,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;
@@ -356,7 +373,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);
@@ -399,6 +417,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"));
@@ -519,6 +538,13 @@ 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_MODIFY,
+ "Material Name Collision",
+ "Behavior when the name of an imported material conflicts with an existing material");
}
#endif /* WITH_USD */
diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc
index 317dfd2a62b..c09b8ee1e2d 100644
--- a/source/blender/io/usd/intern/usd_reader_material.cc
+++ b/source/blender/io/usd/intern/usd_reader_material.cc
@@ -24,7 +24,9 @@
#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"
@@ -109,6 +111,132 @@ static void link_nodes(
nodeAddLink(ntree, source, source_socket, dest, dest_socket);
}
+/* Returns a layer handle retrieved from the given attribute's property specs.
+ * Note that the returned handle may be invalid if no layer could be found. */
+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();
+}
+
+/* If the given file path contains a UDIM token, examine files
+ * on disk to determine the indices of the UDIM tiles that are available
+ * to load. Returns the path to the file corresponding to the lowest tile
+ * index and an array containing valid tile indices in 'r_first_tile_path'
+ * and 'r_tile_indices', respctively. The function returns true if the
+ * given arguments are valid, if 'file_path' is a UDIM path and
+ * if any tiles were found on disk; it returns false otherwise. */
+static bool get_udim_tiles(const std::string &file_path,
+ std::string *r_first_tile_path,
+ std::vector<int> *r_tile_indices)
+{
+ if (file_path.empty()) {
+ return false;
+ }
+
+ if (!(r_first_tile_path && r_tile_indices)) {
+ return false;
+ }
+
+ /* Check if we have a UDIM path. */
+ std::size_t udim_token_offset = file_path.find("<UDIM>");
+
+ if (udim_token_offset == std::string::npos) {
+ /* No UDIM token. */
+ return false;
+ }
+
+ /* Create a dummy UDIM path by replacing the '<UDIM>' token
+ * with an arbitrary index, since this is the format expected
+ * as input to the call BLI_path_sequence_decode(). We use the
+ * index 1001, but this will be rplaced by the actual index
+ * of the first tile found on disk. */
+ std::string base_udim_path(file_path);
+ base_udim_path.replace(udim_token_offset, 6, "1001");
+
+ /* Exctract the file and directory names from the path. */
+ char filename[FILE_MAX], dirname[FILE_MAXDIR];
+ BLI_split_dirfile(base_udim_path.c_str(), dirname, filename, sizeof(dirname), sizeof(filename));
+
+ /* Split the base and head portions of the file name. */
+ ushort digits = 0;
+ char base_head[FILE_MAX], base_tail[FILE_MAX];
+ base_head[0] = '\0';
+ base_tail[0] = '\0';
+ int id = BLI_path_sequence_decode(filename, base_head, base_tail, &digits);
+
+ /* As a sanity check, confirm that we got our original index. */
+ if (id != 1001) {
+ return false;
+ }
+
+ /* Iterate over the directory contents to find files
+ * with matching names and with the expected index format
+ * for UDIMS. */
+ struct direntry *dir = nullptr;
+ uint totfile = BLI_filelist_dir_contents(dirname, &dir);
+
+ if (!dir) {
+ return false;
+ }
+
+ for (int i = 0; i < totfile; ++i) {
+ if (!(dir[i].type & S_IFREG)) {
+ continue;
+ }
+
+ char head[FILE_MAX], tail[FILE_MAX];
+ head[0] = '\0';
+ tail[0] = '\0';
+ int id = BLI_path_sequence_decode(dir[i].relname, head, tail, &digits);
+
+ if (digits == 0 || digits > 4 || !(STREQLEN(base_head, head, FILE_MAX)) ||
+ !(STREQLEN(base_tail, tail, FILE_MAX))) {
+ continue;
+ }
+
+ if (id < 1001 || id >= IMA_UDIM_MAX) {
+ continue;
+ }
+
+ r_tile_indices->push_back(id);
+ }
+
+ BLI_filelist_free(dir, totfile);
+
+ if (r_tile_indices->empty()) {
+ return false;
+ }
+
+ std::sort(r_tile_indices->begin(), r_tile_indices->end());
+
+ /* Finally, use the lowest index we found to create the first tile path. */
+ (*r_first_tile_path) = file_path;
+ r_first_tile_path->replace(udim_token_offset, 6, std::to_string(r_tile_indices->front()));
+
+ return true;
+}
+
+/* Add tiles with the given indices to the given image. */
+static void add_udim_tiles(Image *image, const std::vector<int> &indices)
+{
+ if (!image || indices.empty()) {
+ return;
+ }
+
+ image->source = IMA_SRC_TILED;
+
+ for (int i = 0; i < indices.size(); ++i) {
+ BKE_image_add_tile(image, indices[i], nullptr);
+ }
+}
+
/* Returns true if the given shader may have opacity < 1.0, based
* on heuristics. */
static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
@@ -622,6 +750,23 @@ 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();
if (file_path.empty()) {
+ /* No resolved path, so use the asset path (usually
+ * necessary for UDIM paths). */
+ file_path = asset_path.GetAssetPath();
+
+ /* Texture paths are frequently relative to the USD, so get
+ * the absolute path. */
+ if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
+ file_path = layer_handle->ComputeAbsolutePath(file_path);
+ }
+ }
+
+ /* If this is a UDIM texture, this array will store the
+ * UDIM tile indices. */
+ std::vector<int> udim_tile_indices;
+ get_udim_tiles(file_path, &file_path, &udim_tile_indices);
+
+ if (file_path.empty()) {
std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path
<< "' for Texture Image node." << std::endl;
return;
@@ -635,6 +780,10 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
return;
}
+ if (!udim_tile_indices.empty()) {
+ add_udim_tiles(image, udim_tile_indices);
+ }
+
tex_image->id = &image->id;
/* Set texture color space.
diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc
index 5c8bd88e87a..a7cd0d07d72 100644
--- a/source/blender/io/usd/intern/usd_reader_mesh.cc
+++ b/source/blender/io/usd/intern/usd_reader_mesh.cc
@@ -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;
}
}
}
@@ -702,6 +725,11 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime,
pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial();
if (!subset_mtl) {
+ /* Check for a preview material as fallback. */
+ subset_mtl = subset_api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
+ }
+
+ if (!subset_mtl) {
continue;
}
@@ -731,7 +759,14 @@ 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 = api.ComputeBoundMaterial();
+
+ if (!mtl) {
+ /* Check for a preview material as fallback. */
+ mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
+ }
+
+ if (mtl) {
pxr::SdfPath mtl_path = mtl.GetPath();
@@ -750,7 +785,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,
diff --git a/source/blender/io/usd/intern/usd_reader_prim.h b/source/blender/io/usd/intern/usd_reader_prim.h
index 5aff52f011f..b5d795ac3bd 100644
--- a/source/blender/io/usd/intern/usd_reader_prim.h
+++ b/source/blender/io/usd/intern/usd_reader_prim.h
@@ -24,6 +24,9 @@
#include <pxr/usd/usd/prim.h>
+#include <map>
+#include <string>
+
struct Main;
struct Object;
@@ -50,6 +53,10 @@ struct ImportSettings {
CacheFile *cache_file;
+ /* Map a USD matrial prim path to a Blender material name.
+ * This map might be updated by readers during stage traversal. */
+ mutable std::map<std::string, std::string> usd_path_to_mat_name;
+
ImportSettings()
: do_convert_mat(false),
from_up(0),
diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h
index 2a036c3d398..275b7fdc3b6 100644
--- a/source/blender/io/usd/usd.h
+++ b/source/blender/io/usd/usd.h
@@ -31,6 +31,11 @@ struct CacheReader;
struct Object;
struct bContext;
+typedef enum eUSDMtlNameCollisionMode {
+ USD_MTL_NAME_COLLISION_MODIFY = 0,
+ USD_MTL_NAME_COLLISION_SKIP = 1,
+} eUSDMtlNameCollisionMode;
+
struct USDExportParams {
bool export_animation;
bool export_hair;
@@ -69,6 +74,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.