diff options
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/editors/io/io_usd.c | 28 | ||||
-rw-r--r-- | source/blender/io/usd/intern/usd_reader_material.cc | 149 | ||||
-rw-r--r-- | source/blender/io/usd/intern/usd_reader_mesh.cc | 84 | ||||
-rw-r--r-- | source/blender/io/usd/intern/usd_reader_prim.h | 7 | ||||
-rw-r--r-- | source/blender/io/usd/usd.h | 6 |
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, ¶ms, 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 ¶ms, - 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. |