Changeset View
Standalone View
source/blender/io/usd/intern/usd_reader_material.cc
| /* SPDX-License-Identifier: GPL-2.0-or-later | /* SPDX-License-Identifier: GPL-2.0-or-later | ||||
| * Copyright 2021 NVIDIA Corporation. All rights reserved. */ | * Copyright 2021 NVIDIA Corporation. All rights reserved. */ | ||||
| #include "usd_reader_material.h" | #include "usd_reader_material.h" | ||||
| #include "BKE_image.h" | #include "BKE_image.h" | ||||
| #include "BKE_main.h" | #include "BKE_main.h" | ||||
| #include "BKE_material.h" | #include "BKE_material.h" | ||||
| #include "BKE_node.h" | #include "BKE_node.h" | ||||
| #include "BKE_node_tree_update.h" | #include "BKE_node_tree_update.h" | ||||
| #include "BLI_fileops.h" | |||||
| #include "BLI_math_vector.h" | #include "BLI_math_vector.h" | ||||
| #include "BLI_path_util.h" | |||||
| #include "BLI_string.h" | #include "BLI_string.h" | ||||
| #include "BLI_vector.hh" | |||||
| #include "DNA_material_types.h" | #include "DNA_material_types.h" | ||||
| #include <pxr/base/gf/vec3f.h> | #include <pxr/base/gf/vec3f.h> | ||||
| #include <pxr/usd/usdShade/material.h> | #include <pxr/usd/usdShade/material.h> | ||||
| #include <pxr/usd/usdShade/shader.h> | #include <pxr/usd/usdShade/shader.h> | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <optional> | |||||
| #include <vector> | #include <vector> | ||||
| namespace usdtokens { | namespace usdtokens { | ||||
| /* Parameter names. */ | /* Parameter names. */ | ||||
| static const pxr::TfToken a("a", pxr::TfToken::Immortal); | static const pxr::TfToken a("a", pxr::TfToken::Immortal); | ||||
| static const pxr::TfToken b("b", pxr::TfToken::Immortal); | static const pxr::TfToken b("b", pxr::TfToken::Immortal); | ||||
| static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal); | static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal); | ||||
| ▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | static void link_nodes( | ||||
| if (!dest_socket) { | if (!dest_socket) { | ||||
| std::cerr << "PROGRAMMER ERROR: Couldn't find input socket " << sock_in << std::endl; | std::cerr << "PROGRAMMER ERROR: Couldn't find input socket " << sock_in << std::endl; | ||||
| return; | return; | ||||
| } | } | ||||
| nodeAddLink(ntree, source, source_socket, dest, dest_socket); | 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(); | |||||
| } | |||||
| static bool is_udim_path(const std::string &path) | |||||
| { | |||||
| return path.find("<UDIM>") != std::string::npos; | |||||
| } | |||||
| /* For the given UDIM path (assumed to contain the UDIM token), returns an array | |||||
| * containing valid tile indices. | |||||
| * Returns std::nullopt if no tiles were found. */ | |||||
| static std::optional<blender::Vector<int>> get_udim_tiles(const std::string &file_path) | |||||
| { | |||||
| char base_udim_path[FILE_MAX]; | |||||
| BLI_strncpy(base_udim_path, file_path.c_str(), sizeof(base_udim_path)); | |||||
| blender::Vector<int> udim_tiles; | |||||
| /* Extract the tile numbers from all files on disk. */ | |||||
| ListBase tiles = {nullptr, nullptr}; | |||||
| int tile_start, tile_range; | |||||
| bool result = BKE_image_get_tile_info(base_udim_path, &tiles, &tile_start, &tile_range); | |||||
| if (result) { | |||||
| LISTBASE_FOREACH (LinkData *, tile, &tiles) { | |||||
| int tile_number = POINTER_AS_INT(tile->data); | |||||
| udim_tiles.append(tile_number); | |||||
| } | |||||
| } | |||||
| BLI_freelistN(&tiles); | |||||
| if (udim_tiles.is_empty()) { | |||||
| return std::nullopt; | |||||
| } | |||||
| return udim_tiles; | |||||
| } | |||||
| /* Add tiles with the given indices to the given image. */ | |||||
| static void add_udim_tiles(Image *image, const blender::Vector<int> &indices) | |||||
| { | |||||
| image->source = IMA_SRC_TILED; | |||||
| for (int tile_number : indices) { | |||||
| BKE_image_add_tile(image, tile_number, nullptr); | |||||
| } | |||||
| } | |||||
| /* Returns true if the given shader may have opacity < 1.0, based | /* Returns true if the given shader may have opacity < 1.0, based | ||||
| * on heuristics. */ | * on heuristics. */ | ||||
| static bool needs_blend(const pxr::UsdShadeShader &usd_shader) | static bool needs_blend(const pxr::UsdShadeShader &usd_shader) | ||||
| { | { | ||||
| if (!usd_shader) { | if (!usd_shader) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 491 Lines • ▼ Show 20 Lines | if (!file_input.Get(&file_val) || !file_val.IsHolding<pxr::SdfAssetPath>()) { | ||||
| std::cerr << "WARNING: Couldn't get file input value for USD shader " << usd_shader.GetPath() | std::cerr << "WARNING: Couldn't get file input value for USD shader " << usd_shader.GetPath() | ||||
| << std::endl; | << std::endl; | ||||
| return; | return; | ||||
| } | } | ||||
| const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>(); | const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>(); | ||||
| std::string file_path = asset_path.GetResolvedPath(); | std::string file_path = asset_path.GetResolvedPath(); | ||||
| if (file_path.empty()) { | 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 (file_path.empty()) { | |||||
deadpin: I think this `empty` check should be moved above the `is_udim_path` check since that seems a… | |||||
Done Inline ActionsThanks, Jesse. Good catch! makowalski: Thanks, Jesse. Good catch! | |||||
| std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path | std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path | ||||
| << "' for Texture Image node." << std::endl; | << "' for Texture Image node." << std::endl; | ||||
| return; | return; | ||||
| } | } | ||||
| /* If this is a UDIM texture, this will store the | |||||
| * UDIM tile indices. */ | |||||
| std::optional<blender::Vector<int>> udim_tiles; | |||||
| if (is_udim_path(file_path)) { | |||||
| udim_tiles = get_udim_tiles(file_path); | |||||
| } | |||||
| const char *im_file = file_path.c_str(); | const char *im_file = file_path.c_str(); | ||||
| Image *image = BKE_image_load_exists(bmain_, im_file); | Image *image = BKE_image_load_exists(bmain_, im_file); | ||||
| if (!image) { | if (!image) { | ||||
| std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node." | std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node." | ||||
| << std::endl; | << std::endl; | ||||
| return; | return; | ||||
| } | } | ||||
| if (udim_tiles) { | |||||
Done Inline ActionsIs the use of an optional really gaining us anything here? I'd vote to just return a blender::Vector from get_udim_tiles and make the udim_tiles.is_empty() check here instead (remove use of optional header entirely) deadpin: Is the use of an `optional` really gaining us anything here? I'd vote to just return a blender… | |||||
Done Inline ActionsI would agree that returning blender::vector is more direct and would make the code easier to follow. @Sybren A. Stüvel (sybren) I believe you proposed returning an optional value in the original review, but that was for a more complex return value std::optional<std::pair<std::string, blender::Vector<int>>>, in a previous version of the function. Would you have any objection to having the function simply return blender::vector? makowalski: I would agree that returning `blender::vector` is more direct and would make the code easier to… | |||||
Done Inline ActionsI'd certainly prefer a simpler return type. IMO the only reason to have an std::optional would be to distinguish between hypothetical cases "no data" and "there is data, and the data is empty". sybren: I'd certainly prefer a simpler return type. IMO the only reason to have an `std::optional`… | |||||
| /* Not calling udim_tiles.value(), which is not | |||||
| * supported in macOS versions prior to 10.14.1. */ | |||||
| add_udim_tiles(image, *udim_tiles); | |||||
| } | |||||
| tex_image->id = &image->id; | tex_image->id = &image->id; | ||||
| /* Set texture color space. | /* Set texture color space. | ||||
| * TODO(makowalski): For now, just checking for RAW color space, | * TODO(makowalski): For now, just checking for RAW color space, | ||||
| * assuming sRGB otherwise, but more complex logic might be | * assuming sRGB otherwise, but more complex logic might be | ||||
| * required if the color space is "auto". */ | * required if the color space is "auto". */ | ||||
| pxr::TfToken color_space = get_source_color_space(usd_shader); | pxr::TfToken color_space = get_source_color_space(usd_shader); | ||||
| ▲ Show 20 Lines • Show All 54 Lines • Show Last 20 Lines | |||||
I think this empty check should be moved above the is_udim_path check since that seems a bit more logical.