Files
test/source/blender/io/usd/intern/usd_reader_material.cc
илья _ 119fc054f8 Cleanup: BKE: Nodes: Pass-by-reference
Restriction of the nodes api to clearly define never-null function arguments.
Side effects: some assertions and null-check (with early return) were removed.
On the caller side is ensured to never derefer null to pass argument (mainly in RNA).
In addition, one pointer argument now actually a return type.

By-reference return types instead of pointers going to be separate kind of
change since also imply of cleaning up variables created from reference.

Also good future improvement would be to mark a copy-constructor as
explicit for DNA node types.

Pull Request: https://projects.blender.org/blender/blender/pulls/134627
2025-02-19 13:44:11 +01:00

1513 lines
53 KiB
C++

/* SPDX-FileCopyrightText: 2021 NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_reader_material.hh"
#include "usd_asset_utils.hh"
#include "usd_hash_types.hh"
#include "usd_reader_utils.hh"
#include "usd_utils.hh"
#include "BKE_image.hh"
#include "BKE_lib_id.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_report.hh"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_vector.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "DNA_material_types.h"
#include <pxr/base/gf/vec3f.h>
#include <pxr/usd/ar/packageUtils.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/shader.h>
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.usd"};
namespace usdtokens {
/* Parameter names. */
static const pxr::TfToken a("a", pxr::TfToken::Immortal);
static const pxr::TfToken b("b", pxr::TfToken::Immortal);
static const pxr::TfToken bias("bias", pxr::TfToken::Immortal);
static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal);
static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal);
static const pxr::TfToken diffuseColor("diffuseColor", pxr::TfToken::Immortal);
static const pxr::TfToken displacement("displacement", pxr::TfToken::Immortal);
static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal);
static const pxr::TfToken file("file", pxr::TfToken::Immortal);
static const pxr::TfToken g("g", pxr::TfToken::Immortal);
static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
static const pxr::TfToken in("in", pxr::TfToken::Immortal);
static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
static const pxr::TfToken occlusion("occlusion", pxr::TfToken::Immortal);
static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal);
static const pxr::TfToken opacityThreshold("opacityThreshold", pxr::TfToken::Immortal);
static const pxr::TfToken r("r", pxr::TfToken::Immortal);
static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal);
static const pxr::TfToken rgba("rgba", pxr::TfToken::Immortal);
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
static const pxr::TfToken scale("scale", pxr::TfToken::Immortal);
static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
static const pxr::TfToken specularColor("specularColor", pxr::TfToken::Immortal);
static const pxr::TfToken st("st", pxr::TfToken::Immortal);
static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
/* Color space names. */
static const pxr::TfToken auto_("auto", pxr::TfToken::Immortal);
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
/* Wrap mode names. */
static const pxr::TfToken black("black", pxr::TfToken::Immortal);
static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
static const pxr::TfToken mirror("mirror", pxr::TfToken::Immortal);
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
/* Transform 2d names. */
static const pxr::TfToken rotation("rotation", pxr::TfToken::Immortal);
static const pxr::TfToken translation("translation", pxr::TfToken::Immortal);
/* USD shader names. */
static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
pxr::TfToken::Immortal);
static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal);
static const pxr::TfToken UsdTransform2d("UsdTransform2d", pxr::TfToken::Immortal);
} // namespace usdtokens
using blender::io::usd::ShaderToNodeMap;
/**
* Generate a key for caching a Blender node created for a given USD shader
* by returning the shader prim path with an optional tag suffix. The tag can
* be specified in order to generate a unique key when more than one Blender
* node is created for the USD shader.
*/
static std::string get_key(const pxr::UsdShadeShader &usd_shader, const char *tag)
{
std::string key = usd_shader.GetPath().GetAsString();
if (tag) {
key += ":";
key += tag;
}
return key;
}
/* Returns the Blender node previously cached for
* the given USD shader in the given map. Returns
* null if no cached shader was found. */
static bNode *get_cached_node(const ShaderToNodeMap &node_cache,
const pxr::UsdShadeShader &usd_shader,
const char *tag = nullptr)
{
return node_cache.lookup_default(get_key(usd_shader, tag), nullptr);
}
/* Cache the Blender node translated from the given USD shader
* in the given map. */
static void cache_node(ShaderToNodeMap &node_cache,
const pxr::UsdShadeShader &usd_shader,
bNode *node,
const char *tag = nullptr)
{
node_cache.add(get_key(usd_shader, tag), node);
}
/* Add a node of the given type at the given location coordinates. */
static bNode *add_node(
const bContext *C, bNodeTree *ntree, const int type, const float locx, const float locy)
{
bNode *new_node = blender::bke::node_add_static_node(C, *ntree, type);
if (new_node) {
new_node->location[0] = locx;
new_node->location[1] = locy;
}
return new_node;
}
/* Connect the output socket of node 'source' to the input socket of node 'dest'. */
static void link_nodes(
bNodeTree *ntree, bNode *source, const char *sock_out, bNode *dest, const char *sock_in)
{
bNodeSocket *source_socket = blender::bke::node_find_socket(*source, SOCK_OUT, sock_out);
if (!source_socket) {
CLOG_ERROR(&LOG, "Couldn't find output socket %s", sock_out);
return;
}
bNodeSocket *dest_socket = blender::bke::node_find_socket(*dest, SOCK_IN, sock_in);
if (!dest_socket) {
CLOG_ERROR(&LOG, "Couldn't find input socket %s", sock_in);
return;
}
/* Only add the link if this is the first one to be connected. */
if (blender::bke::node_count_socket_links(*ntree, *dest_socket) == 0) {
blender::bke::node_add_link(*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 (const auto &PropertySpec : attribute.GetPropertyStack(pxr::UsdTimeCode::EarliestTime())) {
if (PropertySpec->HasDefaultValue() ||
PropertySpec->GetLayer()->GetNumTimeSamplesForPath(PropertySpec->GetPath()) > 0)
{
return PropertySpec->GetLayer();
}
}
return pxr::SdfLayerHandle();
}
/* For the given UDIM path (assumed to contain the UDIM token), returns an array
* containing valid tile indices. */
static blender::Vector<int> get_udim_tiles(const std::string &file_path)
{
char base_udim_path[FILE_MAX];
STRNCPY(base_udim_path, file_path.c_str());
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);
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
* on heuristics. */
static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
{
if (!usd_shader) {
return false;
}
bool needs_blend = false;
if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) {
if (opacity_input.HasConnectedSource()) {
needs_blend = true;
}
else {
pxr::VtValue val;
if (opacity_input.GetAttr().HasAuthoredValue() && opacity_input.GetAttr().Get(&val)) {
float opacity = val.Get<float>();
needs_blend = opacity < 1.0f;
}
}
}
return needs_blend;
}
/* Returns the given shader's opacityThreshold input value, if this input has an
* authored value. Otherwise, returns the given default value. */
static float get_opacity_threshold(const pxr::UsdShadeShader &usd_shader,
float default_value = 0.0f)
{
if (!usd_shader) {
return default_value;
}
pxr::UsdShadeInput opacity_threshold_input = usd_shader.GetInput(usdtokens::opacityThreshold);
if (!opacity_threshold_input) {
return default_value;
}
pxr::VtValue val;
if (opacity_threshold_input.GetAttr().HasAuthoredValue() &&
opacity_threshold_input.GetAttr().Get(&val))
{
return val.Get<float>();
}
return default_value;
}
static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader)
{
if (!usd_shader) {
return pxr::TfToken();
}
pxr::UsdShadeInput color_space_input = usd_shader.GetInput(usdtokens::sourceColorSpace);
if (!color_space_input) {
return pxr::TfToken();
}
pxr::VtValue color_space_val;
if (color_space_input.Get(&color_space_val) && color_space_val.IsHolding<pxr::TfToken>()) {
return color_space_val.Get<pxr::TfToken>();
}
return pxr::TfToken();
}
static int get_image_extension(const pxr::UsdShadeShader &usd_shader, const int default_value)
{
pxr::UsdShadeInput wrap_input = usd_shader.GetInput(usdtokens::wrapS);
if (!wrap_input) {
wrap_input = usd_shader.GetInput(usdtokens::wrapT);
}
if (!wrap_input) {
return default_value;
}
pxr::VtValue wrap_input_val;
if (!(wrap_input.Get(&wrap_input_val) && wrap_input_val.IsHolding<pxr::TfToken>())) {
return default_value;
}
pxr::TfToken wrap_val = wrap_input_val.Get<pxr::TfToken>();
if (wrap_val == usdtokens::repeat) {
return SHD_IMAGE_EXTENSION_REPEAT;
}
if (wrap_val == usdtokens::clamp) {
return SHD_IMAGE_EXTENSION_EXTEND;
}
if (wrap_val == usdtokens::black) {
return SHD_IMAGE_EXTENSION_CLIP;
}
if (wrap_val == usdtokens::mirror) {
return SHD_IMAGE_EXTENSION_MIRROR;
}
return default_value;
}
/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source
* of the given material. Returns true if a UsdPreviewSurface source was found
* and returns false otherwise. */
static bool get_usd_preview_surface(const pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeShader &r_preview_surface)
{
if (!usd_material) {
return false;
}
if (pxr::UsdShadeShader surf_shader = usd_material.ComputeSurfaceSource()) {
/* Check if we have a UsdPreviewSurface shader. */
pxr::TfToken shader_id;
if (surf_shader.GetShaderId(&shader_id) && shader_id == usdtokens::UsdPreviewSurface) {
r_preview_surface = surf_shader;
return true;
}
}
return false;
}
/* Set the Blender material's viewport display color, metallic and roughness
* properties from the given USD preview surface shader's inputs. */
static void set_viewport_material_props(Material *mtl, const pxr::UsdShadeShader &usd_preview)
{
if (!(mtl && usd_preview)) {
return;
}
if (pxr::UsdShadeInput diffuse_color_input = usd_preview.GetInput(usdtokens::diffuseColor)) {
pxr::VtValue val;
if (diffuse_color_input.GetAttr().HasAuthoredValue() &&
diffuse_color_input.GetAttr().Get(&val) && val.IsHolding<pxr::GfVec3f>())
{
pxr::GfVec3f color = val.UncheckedGet<pxr::GfVec3f>();
mtl->r = color[0];
mtl->g = color[1];
mtl->b = color[2];
}
}
if (pxr::UsdShadeInput metallic_input = usd_preview.GetInput(usdtokens::metallic)) {
pxr::VtValue val;
if (metallic_input.GetAttr().HasAuthoredValue() && metallic_input.GetAttr().Get(&val) &&
val.IsHolding<float>())
{
mtl->metallic = val.Get<float>();
}
}
if (pxr::UsdShadeInput roughness_input = usd_preview.GetInput(usdtokens::roughness)) {
pxr::VtValue val;
if (roughness_input.GetAttr().HasAuthoredValue() && roughness_input.GetAttr().Get(&val) &&
val.IsHolding<float>())
{
mtl->roughness = val.Get<float>();
}
}
}
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader,
const pxr::TfToken &input_name)
{
pxr::UsdShadeInput input = usd_shader.GetInput(input_name);
/* Check if the shader's input is connected to another source,
* and use that instead if so. */
if (input) {
for (const pxr::UsdShadeConnectionSourceInfo &source_info : input.GetConnectedSources()) {
pxr::UsdShadeShader shader = pxr::UsdShadeShader(source_info.source.GetPrim());
pxr::UsdShadeInput secondary_input = shader.GetInput(source_info.sourceName);
if (secondary_input) {
input = secondary_input;
break;
}
}
}
return input;
}
static bNodeSocket *get_input_socket(bNode *node, const char *identifier, ReportList *reports)
{
bNodeSocket *sock = blender::bke::node_find_socket(*node, SOCK_IN, identifier);
if (!sock) {
BKE_reportf(reports,
RPT_ERROR,
"%s: Error: Couldn't get input socket %s for node %s",
__func__,
identifier,
node->idname);
}
return sock;
}
namespace blender::io::usd {
namespace {
/* Compute the x- and y-coordinates for placing a new node in an unoccupied region of
* the column with the given index. Returns the coordinates in r_locx and r_locy and
* updates the column-occupancy information in r_ctx. */
void compute_node_loc(const int column, float *r_locx, float *r_locy, NodePlacementContext *r_ctx)
{
if (!(r_locx && r_locy && r_ctx)) {
return;
}
(*r_locx) = r_ctx->origx - column * r_ctx->horizontal_step;
if (column >= r_ctx->column_offsets.size()) {
r_ctx->column_offsets.append(0.0f);
}
(*r_locy) = r_ctx->origy - r_ctx->column_offsets[column];
/* Record the y-offset of the occupied region in
* the column, including padding. */
r_ctx->column_offsets[column] += r_ctx->vertical_step + 10.0f;
}
} // namespace
USDMaterialReader::USDMaterialReader(const USDImportParams &params, Main *bmain)
: params_(params), bmain_(bmain)
{
}
Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_material,
const bool read_usd_preview) const
{
if (!(bmain_ && usd_material)) {
return nullptr;
}
std::string mtl_name = usd_material.GetPrim().GetName().GetString();
/* Create the material. */
Material *mtl = BKE_material_add(bmain_, mtl_name.c_str());
id_us_min(&mtl->id);
if (read_usd_preview) {
import_usd_preview(mtl, usd_material);
}
/* Load custom properties directly from the Material's prim. */
set_id_props_from_prim(&mtl->id, usd_material.GetPrim());
return mtl;
}
void USDMaterialReader::import_usd_preview(Material *mtl,
const pxr::UsdShadeMaterial &usd_material) const
{
/* Get the UsdPreviewSurface shader source for the material,
* if there is one. */
pxr::UsdShadeShader usd_preview;
if (get_usd_preview_surface(usd_material, usd_preview)) {
set_viewport_material_props(mtl, usd_preview);
/* Optionally, create shader nodes to represent a UsdPreviewSurface. */
if (params_.import_usd_preview) {
import_usd_preview_nodes(mtl, usd_preview);
}
}
}
void USDMaterialReader::import_usd_preview_nodes(Material *mtl,
const pxr::UsdShadeShader &usd_shader) const
{
if (!(bmain_ && mtl && usd_shader)) {
return;
}
/* Create the Material's node tree containing the principled BSDF
* and output shaders. */
/* Add the node tree. */
bNodeTree *ntree = blender::bke::node_tree_add_tree_embedded(
nullptr, &mtl->id, "Shader Nodetree", "ShaderNodeTree");
mtl->use_nodes = true;
/* Create the Principled BSDF shader node. */
bNode *principled = add_node(nullptr, ntree, SH_NODE_BSDF_PRINCIPLED, 0.0f, 300.0f);
if (!principled) {
CLOG_ERROR(&LOG,
"Couldn't create SH_NODE_BSDF_PRINCIPLED node for USD shader %s",
usd_shader.GetPath().GetAsString().c_str());
return;
}
/* Create the material output node. */
bNode *output = add_node(nullptr, ntree, SH_NODE_OUTPUT_MATERIAL, 300.0f, 300.0f);
if (!output) {
CLOG_ERROR(&LOG,
"Couldn't create SH_NODE_OUTPUT_MATERIAL node for USD shader %s",
usd_shader.GetPath().GetAsString().c_str());
return;
}
/* Connect the Principled BSDF node to the output node. */
link_nodes(ntree, principled, "BSDF", output, "Surface");
/* Recursively create the principled shader input networks. */
set_principled_node_inputs(principled, ntree, usd_shader);
if (set_displacement_node_inputs(ntree, output, usd_shader)) {
mtl->displacement_method = MA_DISPLACEMENT_BOTH;
}
blender::bke::node_set_active(*ntree, *output);
BKE_ntree_update_after_single_tree_change(*bmain_, *ntree);
/* Optionally, set the material blend mode. */
if (params_.set_material_blend) {
if (needs_blend(usd_shader)) {
mtl->surface_render_method = MA_SURFACE_METHOD_FORWARD;
}
}
}
void USDMaterialReader::set_principled_node_inputs(bNode *principled,
bNodeTree *ntree,
const pxr::UsdShadeShader &usd_shader) const
{
/* The context struct keeps track of the locations for adding
* input nodes. */
NodePlacementContext context(0.0f, 300.0);
/* The column index (from right to left relative to the principled
* node) where we're adding the nodes. */
int column = 0;
/* Recursively set the principled shader inputs. */
if (pxr::UsdShadeInput diffuse_input = usd_shader.GetInput(usdtokens::diffuseColor)) {
ExtraLinkInfo extra;
extra.is_color_corrected = true;
set_node_input(diffuse_input, principled, "Base Color", ntree, column, &context, extra);
}
float emission_strength = 0.0f;
if (pxr::UsdShadeInput emissive_input = usd_shader.GetInput(usdtokens::emissiveColor)) {
ExtraLinkInfo extra;
extra.is_color_corrected = true;
if (set_node_input(
emissive_input, principled, "Emission Color", ntree, column, &context, extra))
{
emission_strength = 1.0f;
}
}
bNodeSocket *emission_strength_sock = blender::bke::node_find_socket(
*principled, SOCK_IN, "Emission Strength");
((bNodeSocketValueFloat *)emission_strength_sock->default_value)->value = emission_strength;
if (pxr::UsdShadeInput specular_input = usd_shader.GetInput(usdtokens::specularColor)) {
set_node_input(specular_input, principled, "Specular Tint", ntree, column, &context);
}
if (pxr::UsdShadeInput metallic_input = usd_shader.GetInput(usdtokens::metallic)) {
set_node_input(metallic_input, principled, "Metallic", ntree, column, &context);
}
if (pxr::UsdShadeInput roughness_input = usd_shader.GetInput(usdtokens::roughness)) {
set_node_input(roughness_input, principled, "Roughness", ntree, column, &context);
}
if (pxr::UsdShadeInput coat_input = usd_shader.GetInput(usdtokens::clearcoat)) {
set_node_input(coat_input, principled, "Coat Weight", ntree, column, &context);
}
if (pxr::UsdShadeInput coat_roughness_input = usd_shader.GetInput(usdtokens::clearcoatRoughness))
{
set_node_input(coat_roughness_input, principled, "Coat Roughness", ntree, column, &context);
}
if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) {
ExtraLinkInfo extra;
extra.opacity_threshold = get_opacity_threshold(usd_shader, 0.0f);
set_node_input(opacity_input, principled, "Alpha", ntree, column, &context, extra);
}
if (pxr::UsdShadeInput ior_input = usd_shader.GetInput(usdtokens::ior)) {
set_node_input(ior_input, principled, "IOR", ntree, column, &context);
}
if (pxr::UsdShadeInput normal_input = usd_shader.GetInput(usdtokens::normal)) {
set_node_input(normal_input, principled, "Normal", ntree, column, &context);
}
}
bool USDMaterialReader::set_displacement_node_inputs(bNodeTree *ntree,
bNode *output,
const pxr::UsdShadeShader &usd_shader) const
{
/* Only continue if this UsdPreviewSurface has displacement to process. */
pxr::UsdShadeInput displacement_input = usd_shader.GetInput(usdtokens::displacement);
if (!displacement_input) {
return false;
}
bNode *displacement_node = add_node(nullptr, ntree, SH_NODE_DISPLACEMENT, 0.0f, -100.0f);
if (!displacement_node) {
CLOG_ERROR(&LOG,
"Couldn't create SH_NODE_DISPLACEMENT node for USD shader %s",
usd_shader.GetPath().GetAsString().c_str());
return false;
}
/* The context struct keeps track of the locations for adding
* input nodes. */
NodePlacementContext context(0.0f, -100.0f);
/* The column index, from right to left relative to the output node. */
int column = 0;
const char *sock_name = "Height";
ExtraLinkInfo extra;
extra.is_color_corrected = false;
set_node_input(displacement_input, displacement_node, sock_name, ntree, column, &context, extra);
/* If the displacement input is not connected, then this is "constant" displacement.
* We need to adjust the Height input by our default Midlevel value of 0.5. */
if (!displacement_input.HasConnectedSource()) {
bNodeSocket *sock = blender::bke::node_find_socket(*displacement_node, SOCK_IN, sock_name);
if (!sock) {
CLOG_ERROR(&LOG, "Couldn't get destination node socket %s", sock_name);
return false;
}
((bNodeSocketValueFloat *)sock->default_value)->value += 0.5f;
}
/* Connect the Displacement node to the output node. */
link_nodes(ntree, displacement_node, "Displacement", output, "Displacement");
return true;
}
bool USDMaterialReader::set_node_input(const pxr::UsdShadeInput &usd_input,
bNode *dest_node,
const char *dest_socket_name,
bNodeTree *ntree,
const int column,
NodePlacementContext *r_ctx,
const ExtraLinkInfo &extra) const
{
if (!(usd_input && dest_node && r_ctx)) {
return false;
}
if (usd_input.HasConnectedSource()) {
/* The USD shader input has a connected source shader. Follow the connection
* and attempt to convert the connected USD shader to a Blender node. */
return follow_connection(usd_input, dest_node, dest_socket_name, ntree, column, r_ctx, extra);
}
/* Set the destination node socket value from the USD shader input value. */
bNodeSocket *sock = blender::bke::node_find_socket(*dest_node, SOCK_IN, dest_socket_name);
if (!sock) {
CLOG_ERROR(&LOG, "Couldn't get destination node socket %s", dest_socket_name);
return false;
}
pxr::VtValue val;
if (!usd_input.Get(&val)) {
CLOG_ERROR(&LOG,
"Couldn't get value for usd shader input %s",
usd_input.GetPrim().GetPath().GetAsString().c_str());
return false;
}
switch (sock->type) {
case SOCK_FLOAT:
if (val.IsHolding<float>()) {
((bNodeSocketValueFloat *)sock->default_value)->value = val.UncheckedGet<float>();
return true;
}
else if (val.IsHolding<pxr::GfVec3f>()) {
pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
float average = (v3f[0] + v3f[1] + v3f[2]) / 3.0f;
((bNodeSocketValueFloat *)sock->default_value)->value = average;
return true;
}
break;
case SOCK_RGBA:
if (val.IsHolding<pxr::GfVec3f>()) {
pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, v3f.data());
return true;
}
break;
case SOCK_VECTOR:
if (val.IsHolding<pxr::GfVec3f>()) {
pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
copy_v3_v3(((bNodeSocketValueVector *)sock->default_value)->value, v3f.data());
return true;
}
else if (val.IsHolding<pxr::GfVec2f>()) {
pxr::GfVec2f v2f = val.UncheckedGet<pxr::GfVec2f>();
copy_v2_v2(((bNodeSocketValueVector *)sock->default_value)->value, v2f.data());
return true;
}
break;
default:
CLOG_WARN(&LOG,
"Unexpected type %s for destination node socket %s",
sock->idname,
dest_socket_name);
break;
}
return false;
}
struct IntermediateNode {
bNode *node;
const char *sock_input_name;
const char *sock_output_name;
};
static IntermediateNode add_normal_map(bNodeTree *ntree, int column, NodePlacementContext *r_ctx)
{
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
/* Currently, the Normal Map node has Tangent Space as the default,
* which is what we need, so we don't need to explicitly set it. */
IntermediateNode normal_map{};
normal_map.node = add_node(nullptr, ntree, SH_NODE_NORMAL_MAP, locx, locy);
normal_map.sock_input_name = "Color";
normal_map.sock_output_name = "Normal";
return normal_map;
}
static IntermediateNode add_scale_bias(const pxr::UsdShadeShader &usd_shader,
bNodeTree *ntree,
int column,
bool feeds_normal_map,
NodePlacementContext *r_ctx)
{
/* Handle the scale-bias inputs if present. */
pxr::UsdShadeInput scale_input = usd_shader.GetInput(usdtokens::scale);
pxr::UsdShadeInput bias_input = usd_shader.GetInput(usdtokens::bias);
pxr::GfVec4f scale(1.0f, 1.0f, 1.0f, 1.0f);
pxr::GfVec4f bias(0.0f, 0.0f, 0.0f, 0.0f);
pxr::VtValue val;
if (scale_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
scale = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
}
if (bias_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
bias = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
}
/* Nothing to be done if the values match their defaults. */
if (scale == pxr::GfVec4f{1.0f, 1.0f, 1.0f, 1.0f} &&
bias == pxr::GfVec4f{0.0f, 0.0f, 0.0f, 0.0f})
{
return {};
}
/* Nothing to be done if this feeds a Normal Map and the values match those defaults. */
if (feeds_normal_map && (scale[0] == 2.0f && scale[1] == 2.0f && scale[2] == 2.0f) &&
(bias[0] == -1.0f && bias[1] == -1.0f && bias[2] == -1.0f))
{
return {};
}
float locx = 0.0f;
float locy = 0.0f;
/* If we know a Normal Map node will be involved, leave room for the another
* adjustment node which will be added later. */
compute_node_loc(feeds_normal_map ? column + 1 : column, &locx, &locy, r_ctx);
IntermediateNode scale_bias{};
const char *tag = "scale_bias";
bNode *node = get_cached_node(r_ctx->node_cache, usd_shader, tag);
if (!node) {
node = add_node(nullptr, ntree, SH_NODE_VECTOR_MATH, locx, locy);
cache_node(r_ctx->node_cache, usd_shader, node, tag);
}
scale_bias.node = node;
scale_bias.node->custom1 = NODE_VECTOR_MATH_MULTIPLY_ADD;
scale_bias.sock_input_name = "Vector";
scale_bias.sock_output_name = "Vector";
bNodeSocket *sock_scale = blender::bke::node_find_socket(
*scale_bias.node, SOCK_IN, "Vector_001");
bNodeSocket *sock_bias = blender::bke::node_find_socket(*scale_bias.node, SOCK_IN, "Vector_002");
copy_v3_v3(((bNodeSocketValueVector *)sock_scale->default_value)->value, scale.data());
copy_v3_v3(((bNodeSocketValueVector *)sock_bias->default_value)->value, bias.data());
return scale_bias;
}
static IntermediateNode add_scale_bias_adjust(bNodeTree *ntree,
int column,
NodePlacementContext *r_ctx)
{
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
IntermediateNode adjust{};
adjust.node = add_node(nullptr, ntree, SH_NODE_VECTOR_MATH, locx, locy);
adjust.node->custom1 = NODE_VECTOR_MATH_MULTIPLY_ADD;
adjust.sock_input_name = "Vector";
adjust.sock_output_name = "Vector";
bNodeSocket *sock_scale = blender::bke::node_find_socket(*adjust.node, SOCK_IN, "Vector_001");
bNodeSocket *sock_bias = blender::bke::node_find_socket(*adjust.node, SOCK_IN, "Vector_002");
copy_v3_fl3(((bNodeSocketValueVector *)sock_scale->default_value)->value, 0.5f, 0.5f, 0.5f);
copy_v3_fl3(((bNodeSocketValueVector *)sock_bias->default_value)->value, 0.5f, 0.5f, 0.5f);
return adjust;
}
static IntermediateNode add_separate_color(const pxr::UsdShadeShader &usd_shader,
const pxr::TfToken &usd_source_name,
bNodeTree *ntree,
int column,
NodePlacementContext *r_ctx)
{
IntermediateNode separate_color{};
if (usd_source_name == usdtokens::r || usd_source_name == usdtokens::g ||
usd_source_name == usdtokens::b)
{
const char *tag = "separate_color";
bNode *node = get_cached_node(r_ctx->node_cache, usd_shader, tag);
if (!node) {
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
node = add_node(nullptr, ntree, SH_NODE_SEPARATE_COLOR, locx, locy);
cache_node(r_ctx->node_cache, usd_shader, node, tag);
}
separate_color.node = node;
separate_color.sock_input_name = "Color";
if (usd_source_name == usdtokens::r) {
separate_color.sock_output_name = "Red";
}
if (usd_source_name == usdtokens::g) {
separate_color.sock_output_name = "Green";
}
if (usd_source_name == usdtokens::b) {
separate_color.sock_output_name = "Blue";
}
}
return separate_color;
}
static IntermediateNode add_lessthan(bNodeTree *ntree,
float threshold,
int column,
NodePlacementContext *r_ctx)
{
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
IntermediateNode lessthan{};
lessthan.node = add_node(nullptr, ntree, SH_NODE_MATH, locx, locy);
lessthan.node->custom1 = NODE_MATH_LESS_THAN;
lessthan.sock_input_name = "Value";
lessthan.sock_output_name = "Value";
bNodeSocket *thresh_sock = blender::bke::node_find_socket(*lessthan.node, SOCK_IN, "Value_001");
((bNodeSocketValueFloat *)thresh_sock->default_value)->value = threshold;
return lessthan;
}
static IntermediateNode add_oneminus(bNodeTree *ntree, int column, NodePlacementContext *r_ctx)
{
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
/* An "invert" node : 1.0f - Value_001 */
IntermediateNode oneminus{};
oneminus.node = add_node(nullptr, ntree, SH_NODE_MATH, locx, locy);
oneminus.node->custom1 = NODE_MATH_SUBTRACT;
oneminus.sock_input_name = "Value_001";
oneminus.sock_output_name = "Value";
bNodeSocket *val_sock = blender::bke::node_find_socket(*oneminus.node, SOCK_IN, "Value");
((bNodeSocketValueFloat *)val_sock->default_value)->value = 1.0f;
return oneminus;
}
static void configure_displacement(const pxr::UsdShadeShader &usd_shader, bNode *displacement_node)
{
/* Transform the scale-bias values into something that the Displacement node
* can understand. */
pxr::UsdShadeInput scale_input = usd_shader.GetInput(usdtokens::scale);
pxr::UsdShadeInput bias_input = usd_shader.GetInput(usdtokens::bias);
pxr::GfVec4f scale(1.0f, 1.0f, 1.0f, 1.0f);
pxr::GfVec4f bias(0.0f, 0.0f, 0.0f, 0.0f);
pxr::VtValue val;
if (scale_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
scale = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
}
if (bias_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
bias = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
}
const float scale_avg = (scale[0] + scale[1] + scale[2]) / 3.0f;
const float bias_avg = (bias[0] + bias[1] + bias[2]) / 3.0f;
bNodeSocket *sock_mid = blender::bke::node_find_socket(*displacement_node, SOCK_IN, "Midlevel");
bNodeSocket *sock_scale = blender::bke::node_find_socket(*displacement_node, SOCK_IN, "Scale");
((bNodeSocketValueFloat *)sock_mid->default_value)->value = -1.0f * (bias_avg / scale_avg);
((bNodeSocketValueFloat *)sock_scale->default_value)->value = scale_avg;
}
bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input,
bNode *dest_node,
const char *dest_socket_name,
bNodeTree *ntree,
int column,
NodePlacementContext *r_ctx,
const ExtraLinkInfo &extra) const
{
if (!(usd_input && dest_node && dest_socket_name && ntree && r_ctx)) {
return false;
}
pxr::UsdShadeConnectableAPI source;
pxr::TfToken source_name;
pxr::UsdShadeAttributeType source_type;
usd_input.GetConnectedSource(&source, &source_name, &source_type);
if (!(source && source.GetPrim().IsA<pxr::UsdShadeShader>())) {
return false;
}
pxr::UsdShadeShader source_shader(source.GetPrim());
if (!source_shader) {
return false;
}
pxr::TfToken shader_id;
if (!source_shader.GetShaderId(&shader_id)) {
CLOG_ERROR(&LOG,
"Couldn't get shader id for source shader %s",
source_shader.GetPath().GetAsString().c_str());
return false;
}
/* For now, only convert UsdUVTexture, UsdTransform2d and UsdPrimvarReader_float2 inputs. */
if (shader_id == usdtokens::UsdUVTexture) {
int shift = 1;
/* Create a Normal Map node if the source is flowing into a 'Normal' socket. */
IntermediateNode normal_map{};
const bool is_normal_map = STREQ(dest_socket_name, "Normal");
if (is_normal_map) {
normal_map = add_normal_map(ntree, column + shift, r_ctx);
shift++;
}
/* Create a Separate Color node if necessary. */
IntermediateNode separate_color = add_separate_color(
source_shader, source_name, ntree, column + shift, r_ctx);
if (separate_color.node) {
shift++;
}
/* Create a Scale-Bias adjustment node or fill in Displacement settings if necessary. */
IntermediateNode scale_bias{};
if (STREQ(dest_socket_name, "Height")) {
configure_displacement(source_shader, dest_node);
}
else {
scale_bias = add_scale_bias(source_shader, ntree, column + shift, is_normal_map, r_ctx);
}
/* Wire up any intermediate nodes that are present. Keep track of the
* final "target" destination for the Image link. */
bNode *target_node = dest_node;
const char *target_sock_name = dest_socket_name;
if (normal_map.node) {
/* If a scale-bias node is required, we need to re-adjust the output
* so it can be passed into the NormalMap node properly. */
if (scale_bias.node) {
IntermediateNode re_adjust = add_scale_bias_adjust(ntree, column + shift, r_ctx);
link_nodes(ntree,
scale_bias.node,
scale_bias.sock_output_name,
re_adjust.node,
re_adjust.sock_input_name);
link_nodes(ntree,
re_adjust.node,
re_adjust.sock_output_name,
normal_map.node,
normal_map.sock_input_name);
target_node = scale_bias.node;
target_sock_name = scale_bias.sock_input_name;
shift += 2;
}
else {
target_node = normal_map.node;
target_sock_name = normal_map.sock_input_name;
}
link_nodes(ntree, normal_map.node, normal_map.sock_output_name, dest_node, dest_socket_name);
}
else if (scale_bias.node) {
if (separate_color.node) {
link_nodes(ntree,
separate_color.node,
separate_color.sock_output_name,
dest_node,
dest_socket_name);
link_nodes(ntree,
scale_bias.node,
scale_bias.sock_output_name,
separate_color.node,
separate_color.sock_input_name);
}
else {
link_nodes(
ntree, scale_bias.node, scale_bias.sock_output_name, dest_node, dest_socket_name);
}
target_node = scale_bias.node;
target_sock_name = scale_bias.sock_input_name;
shift++;
}
else if (separate_color.node) {
if (extra.opacity_threshold == 0.0f || !STREQ(dest_socket_name, "Alpha")) {
link_nodes(ntree,
separate_color.node,
separate_color.sock_output_name,
dest_node,
dest_socket_name);
}
target_node = separate_color.node;
target_sock_name = separate_color.sock_input_name;
}
/* Handle opacity threshold if necessary. */
if (extra.opacity_threshold > 0.0f) {
/* USD defines the threshold as >= but Blender does not have that operation. Use < instead
* and then invert it. */
IntermediateNode lessthan = add_lessthan(ntree, extra.opacity_threshold, column + 1, r_ctx);
IntermediateNode invert = add_oneminus(ntree, column + 1, r_ctx);
link_nodes(
ntree, lessthan.node, lessthan.sock_output_name, invert.node, invert.sock_input_name);
link_nodes(ntree, invert.node, invert.sock_output_name, dest_node, dest_socket_name);
if (separate_color.node) {
link_nodes(ntree,
separate_color.node,
separate_color.sock_output_name,
lessthan.node,
lessthan.sock_input_name);
}
else {
target_node = lessthan.node;
target_sock_name = lessthan.sock_input_name;
}
}
convert_usd_uv_texture(source_shader,
source_name,
target_node,
target_sock_name,
ntree,
column + shift,
r_ctx,
extra);
}
else if (shader_id == usdtokens::UsdPrimvarReader_float2) {
convert_usd_primvar_reader_float2(
source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, r_ctx);
}
else if (shader_id == usdtokens::UsdTransform2d) {
convert_usd_transform_2d(source_shader, dest_node, dest_socket_name, ntree, column + 1, r_ctx);
}
return true;
}
void USDMaterialReader::convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader,
const pxr::TfToken &usd_source_name,
bNode *dest_node,
const char *dest_socket_name,
bNodeTree *ntree,
const int column,
NodePlacementContext *r_ctx,
const ExtraLinkInfo &extra) const
{
if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) {
return;
}
bNode *tex_image = get_cached_node(r_ctx->node_cache, usd_shader);
if (tex_image == nullptr) {
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
/* Create the Texture Image node. */
tex_image = add_node(nullptr, ntree, SH_NODE_TEX_IMAGE, locx, locy);
if (!tex_image) {
CLOG_ERROR(&LOG, "Couldn't create SH_NODE_TEX_IMAGE for node input %s", dest_socket_name);
return;
}
/* Cache newly created node. */
cache_node(r_ctx->node_cache, usd_shader, tex_image);
/* Load the texture image. */
load_tex_image(usd_shader, tex_image, extra);
}
/* Connect to destination node input. */
/* Get the source socket name. */
std::string source_socket_name = usd_source_name == usdtokens::a ? "Alpha" : "Color";
link_nodes(ntree, tex_image, source_socket_name.c_str(), dest_node, dest_socket_name);
/* Connect the texture image node "Vector" input. */
if (pxr::UsdShadeInput st_input = usd_shader.GetInput(usdtokens::st)) {
set_node_input(st_input, tex_image, "Vector", ntree, column, r_ctx);
}
}
void USDMaterialReader::convert_usd_transform_2d(const pxr::UsdShadeShader &usd_shader,
bNode *dest_node,
const char *dest_socket_name,
bNodeTree *ntree,
int column,
NodePlacementContext *r_ctx) const
{
if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) {
return;
}
bNode *mapping = get_cached_node(r_ctx->node_cache, usd_shader);
if (mapping == nullptr) {
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
/* Create the MAPPING node. */
mapping = add_node(nullptr, ntree, SH_NODE_MAPPING, locx, locy);
if (!mapping) {
BKE_reportf(reports(),
RPT_WARNING,
"%s: Couldn't create SH_NODE_MAPPING for node input %s",
__func__,
dest_socket_name);
return;
}
/* Cache newly created node. */
cache_node(r_ctx->node_cache, usd_shader, mapping);
mapping->custom1 = TEXMAP_TYPE_POINT;
if (bNodeSocket *scale_socket = get_input_socket(mapping, "Scale", reports())) {
if (pxr::UsdShadeInput scale_input = get_input(usd_shader, usdtokens::scale)) {
pxr::VtValue val;
if (scale_input.Get(&val) && val.CanCast<pxr::GfVec2f>()) {
pxr::GfVec2f scale_val = val.Cast<pxr::GfVec2f>().UncheckedGet<pxr::GfVec2f>();
float scale[3] = {scale_val[0], scale_val[1], 1.0f};
copy_v3_v3(((bNodeSocketValueVector *)scale_socket->default_value)->value, scale);
}
}
}
if (bNodeSocket *loc_socket = get_input_socket(mapping, "Location", reports())) {
if (pxr::UsdShadeInput trans_input = get_input(usd_shader, usdtokens::translation)) {
pxr::VtValue val;
if (trans_input.Get(&val) && val.CanCast<pxr::GfVec2f>()) {
pxr::GfVec2f trans_val = val.Cast<pxr::GfVec2f>().UncheckedGet<pxr::GfVec2f>();
float loc[3] = {trans_val[0], trans_val[1], 0.0f};
copy_v3_v3(((bNodeSocketValueVector *)loc_socket->default_value)->value, loc);
}
}
}
if (bNodeSocket *rot_socket = get_input_socket(mapping, "Rotation", reports())) {
if (pxr::UsdShadeInput rot_input = get_input(usd_shader, usdtokens::rotation)) {
pxr::VtValue val;
if (rot_input.Get(&val) && val.CanCast<float>()) {
float rot_val = val.Cast<float>().UncheckedGet<float>() * M_PI / 180.0f;
float rot[3] = {0.0f, 0.0f, rot_val};
copy_v3_v3(((bNodeSocketValueVector *)rot_socket->default_value)->value, rot);
}
}
}
}
/* Connect to destination node input. */
link_nodes(ntree, mapping, "Vector", dest_node, dest_socket_name);
/* Connect the mapping node "Vector" input. */
if (pxr::UsdShadeInput in_input = usd_shader.GetInput(usdtokens::in)) {
set_node_input(in_input, mapping, "Vector", ntree, column, r_ctx);
}
}
void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
bNode *tex_image,
const ExtraLinkInfo &extra) const
{
if (!(usd_shader && tex_image && tex_image->type_legacy == SH_NODE_TEX_IMAGE)) {
return;
}
/* Try to load the texture image. */
pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file);
if (!file_input) {
CLOG_WARN(&LOG,
"Couldn't get file input property for USD shader %s",
usd_shader.GetPath().GetAsString().c_str());
return;
}
/* File input may have a connected source, e.g., if it's been overridden by
* an input on the material. */
if (file_input.HasConnectedSource()) {
pxr::UsdShadeConnectableAPI source;
pxr::TfToken source_name;
pxr::UsdShadeAttributeType source_type;
if (file_input.GetConnectedSource(&source, &source_name, &source_type)) {
file_input = source.GetInput(source_name);
}
else {
CLOG_WARN(&LOG,
"Couldn't get connected source for file input %s (%s)\n",
file_input.GetPrim().GetPath().GetText(),
file_input.GetFullName().GetText());
}
}
pxr::VtValue file_val;
if (!file_input.Get(&file_val) || !file_val.IsHolding<pxr::SdfAssetPath>()) {
CLOG_WARN(&LOG,
"Couldn't get file input value for USD shader %s",
usd_shader.GetPath().GetAsString().c_str());
return;
}
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();
if (!file_path.empty() && is_udim_path(file_path)) {
/* 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()) {
CLOG_WARN(&LOG,
"Couldn't resolve image asset '%s' for Texture Image node",
asset_path.GetAssetPath().c_str());
return;
}
/* Optionally copy the asset if it's inside a USDZ package. */
const bool is_relative = pxr::ArIsPackageRelativePath(file_path);
const bool import_textures = params_.import_textures_mode != USD_TEX_IMPORT_NONE && is_relative;
std::string imported_file_source_path;
if (import_textures) {
imported_file_source_path = file_path;
/* If we are packing the imported textures, we first write them
* to a temporary directory. */
const char *textures_dir = params_.import_textures_mode == USD_TEX_IMPORT_PACK ?
temp_textures_dir() :
params_.import_textures_dir;
const eUSDTexNameCollisionMode name_collision_mode = params_.import_textures_mode ==
USD_TEX_IMPORT_PACK ?
USD_TEX_NAME_COLLISION_OVERWRITE :
params_.tex_name_collision_mode;
file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode, reports());
}
/* If this is a UDIM texture, this will store the
* UDIM tile indices. */
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();
Image *image = BKE_image_load_exists(bmain_, im_file);
if (!image) {
CLOG_WARN(&LOG, "Couldn't open image file '%s' for Texture Image node", im_file);
return;
}
if (!udim_tiles.is_empty()) {
add_udim_tiles(image, udim_tiles);
}
tex_image->id = &image->id;
/* Set texture color space.
* TODO(makowalski): For now, just checking for RAW color space,
* assuming sRGB otherwise, but more complex logic might be
* required if the color space is "auto". */
pxr::TfToken color_space = get_source_color_space(usd_shader);
if (color_space.IsEmpty()) {
color_space = file_input.GetAttr().GetColorSpace();
}
if (color_space.IsEmpty()) {
/* At this point, assume the "auto" space and translate accordingly. */
color_space = usdtokens::auto_;
}
if (color_space == usdtokens::auto_) {
/* If it's auto, determine whether to apply color correction based
* on incoming connection (passed in from outer functions). */
STRNCPY(image->colorspace_settings.name, extra.is_color_corrected ? "sRGB" : "Non-Color");
}
else if (color_space == usdtokens::sRGB) {
STRNCPY(image->colorspace_settings.name, "sRGB");
}
/*
* Due to there being a lot of non-compliant USD assets out there, this is
* a special case where we need to check for different spellings here.
* On write, we are *only* using the correct, lower-case "raw" token.
*/
else if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
STRNCPY(image->colorspace_settings.name, "Non-Color");
}
NodeTexImage *storage = static_cast<NodeTexImage *>(tex_image->storage);
storage->extension = get_image_extension(usd_shader, storage->extension);
if (import_textures && imported_file_source_path != file_path) {
ensure_usd_source_path_prop(imported_file_source_path, &image->id);
}
if (import_textures && params_.import_textures_mode == USD_TEX_IMPORT_PACK &&
!BKE_image_has_packedfile(image))
{
BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(bmain_, &image->id));
if (BLI_is_dir(temp_textures_dir())) {
BLI_delete(temp_textures_dir(), true, true);
}
}
}
void USDMaterialReader::convert_usd_primvar_reader_float2(const pxr::UsdShadeShader &usd_shader,
const pxr::TfToken & /*usd_source_name*/,
bNode *dest_node,
const char *dest_socket_name,
bNodeTree *ntree,
const int column,
NodePlacementContext *r_ctx) const
{
if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) {
return;
}
bNode *uv_map = get_cached_node(r_ctx->node_cache, usd_shader);
if (uv_map == nullptr) {
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
/* Create the UV Map node. */
uv_map = add_node(nullptr, ntree, SH_NODE_UVMAP, locx, locy);
if (!uv_map) {
CLOG_ERROR(&LOG, "Couldn't create SH_NODE_UVMAP for node input %s", dest_socket_name);
return;
}
/* Cache newly created node. */
cache_node(r_ctx->node_cache, usd_shader, uv_map);
/* Set the texmap name. */
pxr::UsdShadeInput varname_input = usd_shader.GetInput(usdtokens::varname);
/* First check if the shader's "varname" input is connected to another source,
* and use that instead if so. */
if (varname_input) {
for (const pxr::UsdShadeConnectionSourceInfo &source_info :
varname_input.GetConnectedSources())
{
pxr::UsdShadeShader shader = pxr::UsdShadeShader(source_info.source.GetPrim());
pxr::UsdShadeInput secondary_varname_input = shader.GetInput(source_info.sourceName);
if (secondary_varname_input) {
varname_input = secondary_varname_input;
break;
}
}
}
if (varname_input) {
pxr::VtValue varname_val;
/* The varname input may be a string or TfToken, so just cast it to a string.
* The Cast function is defined to provide an empty result if it fails. */
if (varname_input.Get(&varname_val) && varname_val.CanCastToTypeid(typeid(std::string))) {
std::string varname = varname_val.Cast<std::string>().Get<std::string>();
if (!varname.empty()) {
NodeShaderUVMap *storage = (NodeShaderUVMap *)uv_map->storage;
STRNCPY(storage->uv_map, varname.c_str());
}
}
}
}
/* Connect to destination node input. */
link_nodes(ntree, uv_map, "UV", dest_node, dest_socket_name);
}
void build_material_map(const Main *bmain, blender::Map<std::string, Material *> &r_mat_map)
{
BLI_assert_msg(r_mat_map.is_empty(), "The incoming material map should be empty");
LISTBASE_FOREACH (Material *, material, &bmain->materials) {
std::string usd_name = make_safe_name(material->id.name + 2, true);
r_mat_map.add_new(usd_name, material);
}
}
Material *find_existing_material(const pxr::SdfPath &usd_mat_path,
const USDImportParams &params,
const blender::Map<std::string, Material *> &mat_map,
const blender::Map<pxr::SdfPath, Material *> &usd_path_to_mat)
{
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. */
return usd_path_to_mat.lookup_default(usd_mat_path, nullptr);
}
return mat_map.lookup_default(usd_mat_path.GetName(), nullptr);
}
} // namespace blender::io::usd