Blender V4.5
usd_reader_material.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 NVIDIA Corporation. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6#include "usd_asset_utils.hh"
7#include "usd_hash_types.hh"
8#include "usd_reader_utils.hh"
9#include "usd_utils.hh"
10
11#include "BKE_image.hh"
12#include "BKE_lib_id.hh"
13#include "BKE_library.hh"
14#include "BKE_main.hh"
15#include "BKE_material.hh"
16#include "BKE_node.hh"
19#include "BKE_report.hh"
20
21#include "BLI_fileops.h"
22#include "BLI_listbase.h"
23#include "BLI_map.hh"
24#include "BLI_math_vector.h"
26#include "BLI_path_utils.hh"
27#include "BLI_string.h"
28#include "BLI_string_ref.hh"
29#include "BLI_vector.hh"
30
31#include "DNA_material_types.h"
32
34
35#include <pxr/base/gf/vec3f.h>
36#include <pxr/usd/ar/packageUtils.h>
37#include <pxr/usd/usdShade/material.h>
38#include <pxr/usd/usdShade/shader.h>
39
40#include "CLG_log.h"
41static CLG_LogRef LOG = {"io.usd"};
42
43namespace usdtokens {
44
45/* Parameter names. */
46static const pxr::TfToken a("a", pxr::TfToken::Immortal);
47static const pxr::TfToken b("b", pxr::TfToken::Immortal);
48static const pxr::TfToken bias("bias", pxr::TfToken::Immortal);
49static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal);
50static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal);
51static const pxr::TfToken diffuseColor("diffuseColor", pxr::TfToken::Immortal);
52static const pxr::TfToken displacement("displacement", pxr::TfToken::Immortal);
53static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal);
54static const pxr::TfToken file("file", pxr::TfToken::Immortal);
55static const pxr::TfToken g("g", pxr::TfToken::Immortal);
56static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
57static const pxr::TfToken in("in", pxr::TfToken::Immortal);
58static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
59static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
60static const pxr::TfToken occlusion("occlusion", pxr::TfToken::Immortal);
61static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal);
62static const pxr::TfToken opacityThreshold("opacityThreshold", pxr::TfToken::Immortal);
63static const pxr::TfToken r("r", pxr::TfToken::Immortal);
64static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal);
65static const pxr::TfToken rgba("rgba", pxr::TfToken::Immortal);
66static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
67static const pxr::TfToken scale("scale", pxr::TfToken::Immortal);
68static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
69static const pxr::TfToken specularColor("specularColor", pxr::TfToken::Immortal);
70static const pxr::TfToken st("st", pxr::TfToken::Immortal);
71static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
72
73/* Color space names. */
74static const pxr::TfToken auto_("auto", pxr::TfToken::Immortal);
75static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
76static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
77static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
78
79/* Wrap mode names. */
80static const pxr::TfToken black("black", pxr::TfToken::Immortal);
81static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
82static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
83static const pxr::TfToken mirror("mirror", pxr::TfToken::Immortal);
84static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
85static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
86
87/* Transform 2d names. */
88static const pxr::TfToken rotation("rotation", pxr::TfToken::Immortal);
89static const pxr::TfToken translation("translation", pxr::TfToken::Immortal);
90
91/* USD shader names. */
92static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal);
93static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
94 pxr::TfToken::Immortal);
95static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal);
96static const pxr::TfToken UsdTransform2d("UsdTransform2d", pxr::TfToken::Immortal);
97} // namespace usdtokens
98
100
101/* Add a node of the given type at the given location coordinates. */
102static bNode *add_node(bNodeTree *ntree, const int type, const blender::float2 loc)
103{
104 bNode *new_node = blender::bke::node_add_static_node(nullptr, *ntree, type);
105 new_node->location[0] = loc.x;
106 new_node->location[1] = loc.y;
107
108 return new_node;
109}
110
111/* Connect the output socket of node 'source' to the input socket of node 'dest'. */
112static void link_nodes(bNodeTree *ntree,
113 bNode *source,
114 const blender::StringRefNull sock_out,
115 bNode *dest,
116 const blender::StringRefNull sock_in)
117{
118 bNodeSocket *source_socket = blender::bke::node_find_socket(*source, SOCK_OUT, sock_out);
119 if (!source_socket) {
120 CLOG_ERROR(&LOG, "Couldn't find output socket %s", sock_out.c_str());
121 return;
122 }
123
124 bNodeSocket *dest_socket = blender::bke::node_find_socket(*dest, SOCK_IN, sock_in);
125 if (!dest_socket) {
126 CLOG_ERROR(&LOG, "Couldn't find input socket %s", sock_in.c_str());
127 return;
128 }
129
130 /* Only add the link if this is the first one to be connected. */
131 if (blender::bke::node_count_socket_links(*ntree, *dest_socket) == 0) {
132 blender::bke::node_add_link(*ntree, *source, *source_socket, *dest, *dest_socket);
133 }
134}
135
136/* Returns a layer handle retrieved from the given attribute's property specs.
137 * Note that the returned handle may be invalid if no layer could be found. */
138static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &attribute)
139{
140 for (const auto &PropertySpec : attribute.GetPropertyStack(pxr::UsdTimeCode::EarliestTime())) {
141 if (PropertySpec->HasDefaultValue() ||
142 PropertySpec->GetLayer()->GetNumTimeSamplesForPath(PropertySpec->GetPath()) > 0)
143 {
144 return PropertySpec->GetLayer();
145 }
146 }
147
148 return pxr::SdfLayerHandle();
149}
150
151/* For the given UDIM path (assumed to contain the UDIM token), returns an array
152 * containing valid tile indices. */
153static blender::Vector<int> get_udim_tiles(const std::string &file_path)
154{
155 char base_udim_path[FILE_MAX];
156 STRNCPY(base_udim_path, file_path.c_str());
157
158 blender::Vector<int> udim_tiles;
159
160 /* Extract the tile numbers from all files on disk. */
161 ListBase tiles = {nullptr, nullptr};
162 int tile_start, tile_range;
163 bool result = BKE_image_get_tile_info(base_udim_path, &tiles, &tile_start, &tile_range);
164 if (result) {
166 int tile_number = POINTER_AS_INT(tile->data);
167 udim_tiles.append(tile_number);
168 }
169 }
170
172
173 return udim_tiles;
174}
175
176/* Add tiles with the given indices to the given image. */
178{
179 image->source = IMA_SRC_TILED;
180
181 for (int tile_number : indices) {
182 BKE_image_add_tile(image, tile_number, nullptr);
183 }
184}
185
186/* Returns true if the given shader may have opacity < 1.0, based
187 * on heuristics. */
188static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
189{
190 if (!usd_shader) {
191 return false;
192 }
193
194 bool needs_blend = false;
195
196 if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) {
197
198 if (opacity_input.HasConnectedSource()) {
199 needs_blend = true;
200 }
201 else {
202 pxr::VtValue val;
203 if (opacity_input.GetAttr().HasAuthoredValue() && opacity_input.GetAttr().Get(&val)) {
204 float opacity = val.Get<float>();
205 needs_blend = opacity < 1.0f;
206 }
207 }
208 }
209
210 return needs_blend;
211}
212
213/* Returns the given shader's opacityThreshold input value, if this input has an
214 * authored value. Otherwise, returns the given default value. */
215static float get_opacity_threshold(const pxr::UsdShadeShader &usd_shader,
216 float default_value = 0.0f)
217{
218 if (!usd_shader) {
219 return default_value;
220 }
221
222 pxr::UsdShadeInput opacity_threshold_input = usd_shader.GetInput(usdtokens::opacityThreshold);
223
224 if (!opacity_threshold_input) {
225 return default_value;
226 }
227
228 pxr::VtValue val;
229 if (opacity_threshold_input.GetAttr().HasAuthoredValue() &&
230 opacity_threshold_input.GetAttr().Get(&val))
231 {
232 return val.Get<float>();
233 }
234
235 return default_value;
236}
237
238static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader)
239{
240 if (!usd_shader) {
241 return pxr::TfToken();
242 }
243
244 pxr::UsdShadeInput color_space_input = usd_shader.GetInput(usdtokens::sourceColorSpace);
245
246 if (!color_space_input) {
247 return pxr::TfToken();
248 }
249
250 pxr::VtValue color_space_val;
251 if (color_space_input.Get(&color_space_val) && color_space_val.IsHolding<pxr::TfToken>()) {
252 return color_space_val.Get<pxr::TfToken>();
253 }
254
255 return pxr::TfToken();
256}
257
258static int get_image_extension(const pxr::UsdShadeShader &usd_shader, const int default_value)
259{
260 pxr::UsdShadeInput wrap_input = usd_shader.GetInput(usdtokens::wrapS);
261
262 if (!wrap_input) {
263 wrap_input = usd_shader.GetInput(usdtokens::wrapT);
264 }
265
266 if (!wrap_input) {
267 return default_value;
268 }
269
270 pxr::VtValue wrap_input_val;
271 if (!(wrap_input.Get(&wrap_input_val) && wrap_input_val.IsHolding<pxr::TfToken>())) {
272 return default_value;
273 }
274
275 pxr::TfToken wrap_val = wrap_input_val.Get<pxr::TfToken>();
276
277 if (wrap_val == usdtokens::repeat) {
279 }
280
281 if (wrap_val == usdtokens::clamp) {
283 }
284
285 if (wrap_val == usdtokens::black) {
287 }
288
289 if (wrap_val == usdtokens::mirror) {
291 }
292
293 return default_value;
294}
295
296/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source
297 * of the given material. Returns true if a UsdPreviewSurface source was found
298 * and returns false otherwise. */
299static bool get_usd_preview_surface(const pxr::UsdShadeMaterial &usd_material,
300 pxr::UsdShadeShader &r_preview_surface)
301{
302 if (!usd_material) {
303 return false;
304 }
305
306 if (pxr::UsdShadeShader surf_shader = usd_material.ComputeSurfaceSource()) {
307 /* Check if we have a UsdPreviewSurface shader. */
308 pxr::TfToken shader_id;
309 if (surf_shader.GetShaderId(&shader_id) && shader_id == usdtokens::UsdPreviewSurface) {
310 r_preview_surface = surf_shader;
311 return true;
312 }
313 }
314
315 return false;
316}
317
318/* Set the Blender material's viewport display color, metallic and roughness
319 * properties from the given USD preview surface shader's inputs. */
320static void set_viewport_material_props(Material *mtl, const pxr::UsdShadeShader &usd_preview)
321{
322 if (!(mtl && usd_preview)) {
323 return;
324 }
325
326 if (pxr::UsdShadeInput diffuse_color_input = usd_preview.GetInput(usdtokens::diffuseColor)) {
327 pxr::VtValue val;
328 if (diffuse_color_input.GetAttr().HasAuthoredValue() &&
329 diffuse_color_input.GetAttr().Get(&val) && val.IsHolding<pxr::GfVec3f>())
330 {
331 pxr::GfVec3f color = val.UncheckedGet<pxr::GfVec3f>();
332 mtl->r = color[0];
333 mtl->g = color[1];
334 mtl->b = color[2];
335 }
336 }
337
338 if (pxr::UsdShadeInput metallic_input = usd_preview.GetInput(usdtokens::metallic)) {
339 pxr::VtValue val;
340 if (metallic_input.GetAttr().HasAuthoredValue() && metallic_input.GetAttr().Get(&val) &&
341 val.IsHolding<float>())
342 {
343 mtl->metallic = val.Get<float>();
344 }
345 }
346
347 if (pxr::UsdShadeInput roughness_input = usd_preview.GetInput(usdtokens::roughness)) {
348 pxr::VtValue val;
349 if (roughness_input.GetAttr().HasAuthoredValue() && roughness_input.GetAttr().Get(&val) &&
350 val.IsHolding<float>())
351 {
352 mtl->roughness = val.Get<float>();
353 }
354 }
355}
356
357static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader,
358 const pxr::TfToken &input_name)
359{
360 pxr::UsdShadeInput input = usd_shader.GetInput(input_name);
361
362 /* Check if the shader's input is connected to another source,
363 * and use that instead if so. */
364 if (input) {
365 for (const pxr::UsdShadeConnectionSourceInfo &source_info : input.GetConnectedSources()) {
366 pxr::UsdShadeShader shader = pxr::UsdShadeShader(source_info.source.GetPrim());
367 pxr::UsdShadeInput secondary_input = shader.GetInput(source_info.sourceName);
368 if (secondary_input) {
369 input = secondary_input;
370 break;
371 }
372 }
373 }
374
375 return input;
376}
377
379 const blender::StringRefNull identifier,
381{
382 bNodeSocket *sock = blender::bke::node_find_socket(*node, SOCK_IN, identifier);
383 if (!sock) {
385 RPT_ERROR,
386 "%s: Error: Couldn't get input socket %s for node %s",
387 __func__,
388 identifier.c_str(),
389 node->idname);
390 }
391
392 return sock;
393}
394
395namespace blender::io::usd {
396
398{
399 if (column >= column_offsets_.size()) {
400 /* UsdPreviewSurface graphs are all very tiny due to their constrained nature. It is unlikely
401 * we need to grow at all but, if we do, do so by small chunks at a time. */
402 column_offsets_.resize(column + 4);
403 }
404
405 float2 loc;
406 loc.x = origx_ - column * horizontal_step_;
407 loc.y = origy_ - column_offsets_[column];
408
409 /* Record the y-offset of the occupied region in
410 * the column, including padding. */
411 column_offsets_[column] += vertical_step_ + 10.0f;
412
413 return loc;
414}
415
416std::string NodePlacementContext::get_key(const pxr::UsdShadeShader &usd_shader,
417 const blender::StringRef tag) const
418{
419 std::string key = usd_shader.GetPath().GetAsString();
420 if (!tag.is_empty()) {
421 key += ":";
422 key += tag;
423 }
424 return key;
425}
426
427bNode *NodePlacementContext::get_cached_node(const pxr::UsdShadeShader &usd_shader,
428 const blender::StringRef tag) const
429{
430 return node_cache_.lookup_default(get_key(usd_shader, tag), nullptr);
431}
432
433void NodePlacementContext::cache_node(const pxr::UsdShadeShader &usd_shader,
434 bNode *node,
435 const blender::StringRef tag)
436{
437 node_cache_.add_new(get_key(usd_shader, tag), node);
438}
439
441 : params_(params), bmain_(bmain)
442{
443}
444
445Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_material,
446 const bool read_usd_preview) const
447{
448 if (!usd_material) {
449 return nullptr;
450 }
451
452 std::string mtl_name = usd_material.GetPrim().GetName().GetString();
453
454 /* Create the material. */
455 Material *mtl = BKE_material_add(&bmain_, mtl_name.c_str());
456 id_us_min(&mtl->id);
457
458 if (read_usd_preview) {
459 import_usd_preview(mtl, usd_material);
460 }
461
462 /* Load custom properties directly from the Material's prim. */
463 set_id_props_from_prim(&mtl->id, usd_material.GetPrim());
464
465 return mtl;
466}
467
469 const pxr::UsdShadeMaterial &usd_material) const
470{
471 /* Get the UsdPreviewSurface shader source for the material,
472 * if there is one. */
473 pxr::UsdShadeShader usd_preview;
474 if (get_usd_preview_surface(usd_material, usd_preview)) {
475
476 set_viewport_material_props(mtl, usd_preview);
477
478 /* Optionally, create shader nodes to represent a UsdPreviewSurface. */
479 if (params_.import_usd_preview) {
480 import_usd_preview_nodes(mtl, usd_material, usd_preview);
481 }
482 }
483}
484
486 const pxr::UsdShadeMaterial &usd_material,
487 const pxr::UsdShadeShader &usd_shader) const
488{
489 if (!(mtl && usd_shader)) {
490 return;
491 }
492
493 /* Create the Material's node tree containing the principled BSDF
494 * and output shaders. */
495
496 /* Add the node tree. */
498 nullptr, &mtl->id, "Shader Nodetree", "ShaderNodeTree");
499 mtl->use_nodes = true;
500
501 /* Create the Principled BSDF shader node. */
502 bNode *principled = add_node(ntree, SH_NODE_BSDF_PRINCIPLED, {0.0f, 300.0f});
503
504 /* Create the material output node. */
505 bNode *output = add_node(ntree, SH_NODE_OUTPUT_MATERIAL, {300.0f, 300.0f});
506
507 /* Connect the Principled BSDF node to the output node. */
508 link_nodes(ntree, principled, "BSDF", output, "Surface");
509
510 /* Recursively create the principled shader input networks. */
511 set_principled_node_inputs(principled, ntree, usd_shader);
512
513 /* Process displacement if we have a valid displacement source. */
514 if (pxr::UsdShadeShader disp_shader = usd_material.ComputeDisplacementSource()) {
515 if (set_displacement_node_inputs(ntree, output, disp_shader)) {
517 }
518 }
519
521
523
524 /* Optionally, set the material blend mode. */
525 if (params_.set_material_blend) {
526 if (needs_blend(usd_shader)) {
528 }
529 }
530}
531
533 bNodeTree *ntree,
534 const pxr::UsdShadeShader &usd_shader) const
535{
536 /* The context struct keeps track of the locations for adding
537 * input nodes. */
538 NodePlacementContext context(0.0f, 300.0);
539
540 /* The column index (from right to left relative to the principled
541 * node) where we're adding the nodes. */
542 int column = 0;
543
544 /* Recursively set the principled shader inputs. */
545
546 if (pxr::UsdShadeInput diffuse_input = usd_shader.GetInput(usdtokens::diffuseColor)) {
547 ExtraLinkInfo extra;
548 extra.is_color_corrected = true;
549 set_node_input(diffuse_input, principled, "Base Color", ntree, column, context, extra);
550 }
551
552 float emission_strength = 0.0f;
553 if (pxr::UsdShadeInput emissive_input = usd_shader.GetInput(usdtokens::emissiveColor)) {
554 ExtraLinkInfo extra;
555 extra.is_color_corrected = true;
556 if (set_node_input(
557 emissive_input, principled, "Emission Color", ntree, column, context, extra))
558 {
559 emission_strength = 1.0f;
560 }
561 }
562
563 bNodeSocket *emission_strength_sock = blender::bke::node_find_socket(
564 *principled, SOCK_IN, "Emission Strength");
565 ((bNodeSocketValueFloat *)emission_strength_sock->default_value)->value = emission_strength;
566
567 if (pxr::UsdShadeInput specular_input = usd_shader.GetInput(usdtokens::specularColor)) {
568 set_node_input(specular_input, principled, "Specular Tint", ntree, column, context);
569 }
570
571 if (pxr::UsdShadeInput metallic_input = usd_shader.GetInput(usdtokens::metallic)) {
572 set_node_input(metallic_input, principled, "Metallic", ntree, column, context);
573 }
574
575 if (pxr::UsdShadeInput roughness_input = usd_shader.GetInput(usdtokens::roughness)) {
576 set_node_input(roughness_input, principled, "Roughness", ntree, column, context);
577 }
578
579 if (pxr::UsdShadeInput coat_input = usd_shader.GetInput(usdtokens::clearcoat)) {
580 set_node_input(coat_input, principled, "Coat Weight", ntree, column, context);
581 }
582
583 if (pxr::UsdShadeInput coat_roughness_input = usd_shader.GetInput(usdtokens::clearcoatRoughness))
584 {
585 set_node_input(coat_roughness_input, principled, "Coat Roughness", ntree, column, context);
586 }
587
588 if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) {
589 ExtraLinkInfo extra;
590 extra.opacity_threshold = get_opacity_threshold(usd_shader, 0.0f);
591 set_node_input(opacity_input, principled, "Alpha", ntree, column, context, extra);
592 }
593
594 if (pxr::UsdShadeInput ior_input = usd_shader.GetInput(usdtokens::ior)) {
595 set_node_input(ior_input, principled, "IOR", ntree, column, context);
596 }
597
598 if (pxr::UsdShadeInput normal_input = usd_shader.GetInput(usdtokens::normal)) {
599 set_node_input(normal_input, principled, "Normal", ntree, column, context);
600 }
601}
602
604 bNode *output,
605 const pxr::UsdShadeShader &usd_shader) const
606{
607 /* Only continue if this UsdPreviewSurface has displacement to process. */
608 pxr::UsdShadeInput displacement_input = usd_shader.GetInput(usdtokens::displacement);
609 if (!displacement_input) {
610 return false;
611 }
612
613 bNode *displacement_node = add_node(ntree, SH_NODE_DISPLACEMENT, {0.0f, -100.0f});
614
615 /* The context struct keeps track of the locations for adding
616 * input nodes. */
617 NodePlacementContext context(0.0f, -100.0f);
618
619 /* The column index, from right to left relative to the output node. */
620 int column = 0;
621
622 const StringRefNull height = "Height";
623 ExtraLinkInfo extra;
624 extra.is_color_corrected = false;
625 set_node_input(displacement_input, displacement_node, height, ntree, column, context, extra);
626
627 /* If the displacement input is not connected, then this is "constant" displacement which is
628 * a lossy conversion from the UsdPreviewSurface. We adjust the Height input assuming a
629 * Midlevel of 0.5 and Scale of 1 as that closely matches the scene in `usdview`. */
630 if (!displacement_input.HasConnectedSource()) {
631 bNodeSocket *sock_height = blender::bke::node_find_socket(*displacement_node, SOCK_IN, height);
633 *displacement_node, SOCK_IN, "Midlevel");
634 bNodeSocket *sock_scale = blender::bke::node_find_socket(*displacement_node, SOCK_IN, "Scale");
635
636 ((bNodeSocketValueFloat *)sock_height->default_value)->value += 0.5f;
637 ((bNodeSocketValueFloat *)sock_mid->default_value)->value = 0.5f;
638 ((bNodeSocketValueFloat *)sock_scale->default_value)->value = 1.0f;
639 }
640
641 /* Connect the Displacement node to the output node. */
642 link_nodes(ntree, displacement_node, "Displacement", output, "Displacement");
643 return true;
644}
645
646bool USDMaterialReader::set_node_input(const pxr::UsdShadeInput &usd_input,
647 bNode *dest_node,
648 const StringRefNull dest_socket_name,
649 bNodeTree *ntree,
650 const int column,
652 const ExtraLinkInfo &extra) const
653{
654 if (!(usd_input && dest_node)) {
655 return false;
656 }
657
658 if (usd_input.HasConnectedSource()) {
659 /* The USD shader input has a connected source shader. Follow the connection
660 * and attempt to convert the connected USD shader to a Blender node. */
661 return follow_connection(usd_input, dest_node, dest_socket_name, ntree, column, ctx, extra);
662 }
663
664 /* Set the destination node socket value from the USD shader input value. */
665
666 bNodeSocket *sock = blender::bke::node_find_socket(*dest_node, SOCK_IN, dest_socket_name);
667 if (!sock) {
668 CLOG_ERROR(&LOG, "Couldn't get destination node socket %s", dest_socket_name.c_str());
669 return false;
670 }
671
672 pxr::VtValue val;
673 if (!usd_input.Get(&val)) {
675 "Couldn't get value for usd shader input %s",
676 usd_input.GetPrim().GetPath().GetAsString().c_str());
677 return false;
678 }
679
680 switch (sock->type) {
681 case SOCK_FLOAT:
682 if (val.IsHolding<float>()) {
683 ((bNodeSocketValueFloat *)sock->default_value)->value = val.UncheckedGet<float>();
684 return true;
685 }
686 else if (val.IsHolding<pxr::GfVec3f>()) {
687 pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
688 float average = (v3f[0] + v3f[1] + v3f[2]) / 3.0f;
689 ((bNodeSocketValueFloat *)sock->default_value)->value = average;
690 return true;
691 }
692 break;
693 case SOCK_RGBA:
694 if (val.IsHolding<pxr::GfVec3f>()) {
695 pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
696 copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, v3f.data());
697 return true;
698 }
699 break;
700 case SOCK_VECTOR:
701 if (val.IsHolding<pxr::GfVec3f>()) {
702 pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
703 copy_v3_v3(((bNodeSocketValueVector *)sock->default_value)->value, v3f.data());
704 return true;
705 }
706 else if (val.IsHolding<pxr::GfVec2f>()) {
707 pxr::GfVec2f v2f = val.UncheckedGet<pxr::GfVec2f>();
708 copy_v2_v2(((bNodeSocketValueVector *)sock->default_value)->value, v2f.data());
709 return true;
710 }
711 break;
712 default:
713 CLOG_WARN(&LOG,
714 "Unexpected type %s for destination node socket %s",
715 sock->idname,
716 dest_socket_name.c_str());
717 break;
718 }
719
720 return false;
721}
722
728
730{
731 const float2 loc = ctx.compute_node_loc(column);
732
733 /* Currently, the Normal Map node has Tangent Space as the default,
734 * which is what we need, so we don't need to explicitly set it. */
735 IntermediateNode normal_map{};
736 normal_map.node = add_node(ntree, SH_NODE_NORMAL_MAP, loc);
737 normal_map.sock_input_name = "Color";
738 normal_map.sock_output_name = "Normal";
739
740 return normal_map;
741}
742
743static IntermediateNode add_scale_bias(const pxr::UsdShadeShader &usd_shader,
744 bNodeTree *ntree,
745 int column,
746 bool feeds_normal_map,
748{
749 /* Handle the scale-bias inputs if present. */
750 pxr::UsdShadeInput scale_input = usd_shader.GetInput(usdtokens::scale);
751 pxr::UsdShadeInput bias_input = usd_shader.GetInput(usdtokens::bias);
752 pxr::GfVec4f scale(1.0f, 1.0f, 1.0f, 1.0f);
753 pxr::GfVec4f bias(0.0f, 0.0f, 0.0f, 0.0f);
754
755 pxr::VtValue val;
756 if (scale_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
757 scale = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
758 }
759 if (bias_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
760 bias = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
761 }
762
763 /* Nothing to be done if the values match their defaults. */
764 if (scale == pxr::GfVec4f{1.0f, 1.0f, 1.0f, 1.0f} &&
765 bias == pxr::GfVec4f{0.0f, 0.0f, 0.0f, 0.0f})
766 {
767 return {};
768 }
769
770 /* Nothing to be done if this feeds a Normal Map and the values match those defaults. */
771 if (feeds_normal_map && (scale[0] == 2.0f && scale[1] == 2.0f && scale[2] == 2.0f) &&
772 (bias[0] == -1.0f && bias[1] == -1.0f && bias[2] == -1.0f))
773 {
774 return {};
775 }
776
777 /* If we know a Normal Map node will be involved, leave room for the another
778 * adjustment node which will be added later. */
779 const float2 loc = ctx.compute_node_loc(feeds_normal_map ? column + 1 : column);
780
781 IntermediateNode scale_bias{};
782
783 const StringRefNull tag = "scale_bias";
784 bNode *node = ctx.get_cached_node(usd_shader, tag);
785
786 if (!node) {
787 node = add_node(ntree, SH_NODE_VECTOR_MATH, loc);
788 ctx.cache_node(usd_shader, node, tag);
789 }
790
791 scale_bias.node = node;
793 scale_bias.sock_input_name = "Vector";
794 scale_bias.sock_output_name = "Vector";
795
797 *scale_bias.node, SOCK_IN, "Vector_001");
798 bNodeSocket *sock_bias = blender::bke::node_find_socket(*scale_bias.node, SOCK_IN, "Vector_002");
799 copy_v3_v3(((bNodeSocketValueVector *)sock_scale->default_value)->value, scale.data());
800 copy_v3_v3(((bNodeSocketValueVector *)sock_bias->default_value)->value, bias.data());
801
802 return scale_bias;
803}
804
806 int column,
808{
809 const float2 loc = ctx.compute_node_loc(column);
810
811 IntermediateNode adjust{};
812 adjust.node = add_node(ntree, SH_NODE_VECTOR_MATH, loc);
814 adjust.sock_input_name = "Vector";
815 adjust.sock_output_name = "Vector";
816
817 bNodeSocket *sock_scale = blender::bke::node_find_socket(*adjust.node, SOCK_IN, "Vector_001");
818 bNodeSocket *sock_bias = blender::bke::node_find_socket(*adjust.node, SOCK_IN, "Vector_002");
819 copy_v3_fl3(((bNodeSocketValueVector *)sock_scale->default_value)->value, 0.5f, 0.5f, 0.5f);
820 copy_v3_fl3(((bNodeSocketValueVector *)sock_bias->default_value)->value, 0.5f, 0.5f, 0.5f);
821
822 return adjust;
823}
824
825static IntermediateNode add_separate_color(const pxr::UsdShadeShader &usd_shader,
826 const pxr::TfToken &usd_source_name,
827 bNodeTree *ntree,
828 int column,
830{
831 IntermediateNode separate_color{};
832
833 if (usd_source_name == usdtokens::r || usd_source_name == usdtokens::g ||
834 usd_source_name == usdtokens::b)
835 {
836 const StringRefNull tag = "separate_color";
837 bNode *node = ctx.get_cached_node(usd_shader, tag);
838
839 if (!node) {
840 const float2 loc = ctx.compute_node_loc(column);
841
842 node = add_node(ntree, SH_NODE_SEPARATE_COLOR, loc);
843 ctx.cache_node(usd_shader, node, tag);
844 }
845
846 separate_color.node = node;
847 separate_color.sock_input_name = "Color";
848
849 if (usd_source_name == usdtokens::r) {
850 separate_color.sock_output_name = "Red";
851 }
852 if (usd_source_name == usdtokens::g) {
853 separate_color.sock_output_name = "Green";
854 }
855 if (usd_source_name == usdtokens::b) {
856 separate_color.sock_output_name = "Blue";
857 }
858 }
859
860 return separate_color;
861}
862
864 float threshold,
865 int column,
867{
868 const float2 loc = ctx.compute_node_loc(column);
869
870 IntermediateNode lessthan{};
871 lessthan.node = add_node(ntree, SH_NODE_MATH, loc);
872 lessthan.node->custom1 = NODE_MATH_LESS_THAN;
873 lessthan.sock_input_name = "Value";
874 lessthan.sock_output_name = "Value";
875
876 bNodeSocket *thresh_sock = blender::bke::node_find_socket(*lessthan.node, SOCK_IN, "Value_001");
877 ((bNodeSocketValueFloat *)thresh_sock->default_value)->value = threshold;
878
879 return lessthan;
880}
881
883{
884 const float2 loc = ctx.compute_node_loc(column);
885
886 /* An "invert" node : 1.0f - Value_001 */
887 IntermediateNode oneminus{};
888 oneminus.node = add_node(ntree, SH_NODE_MATH, loc);
889 oneminus.node->custom1 = NODE_MATH_SUBTRACT;
890 oneminus.sock_input_name = "Value_001";
891 oneminus.sock_output_name = "Value";
892
893 bNodeSocket *val_sock = blender::bke::node_find_socket(*oneminus.node, SOCK_IN, "Value");
894 ((bNodeSocketValueFloat *)val_sock->default_value)->value = 1.0f;
895
896 return oneminus;
897}
898
899static void configure_displacement(const pxr::UsdShadeShader &usd_shader, bNode *displacement_node)
900{
901 /* Transform the scale-bias values into something that the Displacement node
902 * can understand. */
903 pxr::UsdShadeInput scale_input = usd_shader.GetInput(usdtokens::scale);
904 pxr::UsdShadeInput bias_input = usd_shader.GetInput(usdtokens::bias);
905 pxr::GfVec4f scale(1.0f, 1.0f, 1.0f, 1.0f);
906 pxr::GfVec4f bias(0.0f, 0.0f, 0.0f, 0.0f);
907
908 pxr::VtValue val;
909 if (scale_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
910 scale = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
911 }
912 if (bias_input.Get(&val) && val.CanCast<pxr::GfVec4f>()) {
913 bias = pxr::VtValue::Cast<pxr::GfVec4f>(val).UncheckedGet<pxr::GfVec4f>();
914 }
915
916 const float scale_avg = (scale[0] + scale[1] + scale[2]) / 3.0f;
917 const float bias_avg = (bias[0] + bias[1] + bias[2]) / 3.0f;
918
919 bNodeSocket *sock_mid = blender::bke::node_find_socket(*displacement_node, SOCK_IN, "Midlevel");
920 bNodeSocket *sock_scale = blender::bke::node_find_socket(*displacement_node, SOCK_IN, "Scale");
921 ((bNodeSocketValueFloat *)sock_mid->default_value)->value = -1.0f * (bias_avg / scale_avg);
922 ((bNodeSocketValueFloat *)sock_scale->default_value)->value = scale_avg;
923}
924
925static pxr::UsdShadeShader node_graph_output_source(const pxr::UsdShadeNodeGraph &node_graph,
926 const pxr::TfToken &output_name)
927{
928 // Check that we have a legit output
929 pxr::UsdShadeOutput output = node_graph.GetOutput(output_name);
930 if (!output) {
931 return pxr::UsdShadeShader();
932 }
933
934 pxr::UsdShadeAttributeVector attrs = pxr::UsdShadeUtils::GetValueProducingAttributes(output);
935 if (attrs.empty()) {
936 return pxr::UsdShadeShader();
937 }
938
939 pxr::UsdAttribute attr = attrs[0];
940
941 std::pair<pxr::TfToken, pxr::UsdShadeAttributeType> name_and_type =
942 pxr::UsdShadeUtils::GetBaseNameAndType(attr.GetName());
943
944 pxr::UsdShadeShader shader(attr.GetPrim());
945 if (name_and_type.second != pxr::UsdShadeAttributeType::Output || !shader) {
946 return pxr::UsdShadeShader();
947 }
948
949 return shader;
950}
951
952bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input,
953 bNode *dest_node,
954 const StringRefNull dest_socket_name,
955 bNodeTree *ntree,
956 int column,
958 const ExtraLinkInfo &extra) const
959{
960 if (!(usd_input && dest_node && !dest_socket_name.is_empty() && ntree)) {
961 return false;
962 }
963
964 pxr::UsdShadeConnectableAPI source;
965 pxr::TfToken source_name;
966 pxr::UsdShadeAttributeType source_type;
967
968 usd_input.GetConnectedSource(&source, &source_name, &source_type);
969
970 if (!source) {
971 return false;
972 }
973
974 const pxr::UsdPrim source_prim = source.GetPrim();
975 pxr::UsdShadeShader source_shader;
976 if (source_prim.IsA<pxr::UsdShadeShader>()) {
977 source_shader = pxr::UsdShadeShader(source_prim);
978 }
979 else if (source_prim.IsA<pxr::UsdShadeNodeGraph>()) {
980 pxr::UsdShadeNodeGraph node_graph(source_prim);
981 source_shader = node_graph_output_source(node_graph, source_name);
982 }
983
984 if (!source_shader) {
985 return false;
986 }
987
988 pxr::TfToken shader_id;
989 if (!source_shader.GetShaderId(&shader_id)) {
990 CLOG_WARN(&LOG,
991 "Couldn't get shader id for source shader %s",
992 source_shader.GetPath().GetAsString().c_str());
993 return false;
994 }
995
996 /* For now, only convert UsdUVTexture, UsdTransform2d and UsdPrimvarReader_float2 inputs. */
997 if (shader_id == usdtokens::UsdUVTexture) {
998 int shift = 1;
999
1000 /* Create a Normal Map node if the source is flowing into a 'Normal' socket. */
1001 IntermediateNode normal_map{};
1002 const bool is_normal_map = dest_socket_name == "Normal";
1003 if (is_normal_map) {
1004 normal_map = add_normal_map(ntree, column + shift, ctx);
1005 shift++;
1006 }
1007
1008 /* Create a Separate Color node if necessary. */
1009 IntermediateNode separate_color = add_separate_color(
1010 source_shader, source_name, ntree, column + shift, ctx);
1011 if (separate_color.node) {
1012 shift++;
1013 }
1014
1015 /* Create a Scale-Bias adjustment node or fill in Displacement settings if necessary. */
1016 IntermediateNode scale_bias{};
1017 if (dest_socket_name == "Height") {
1018 configure_displacement(source_shader, dest_node);
1019 }
1020 else {
1021 scale_bias = add_scale_bias(source_shader, ntree, column + shift, is_normal_map, ctx);
1022 }
1023
1024 /* Wire up any intermediate nodes that are present. Keep track of the
1025 * final "target" destination for the Image link. */
1026 bNode *target_node = dest_node;
1027 StringRefNull target_sock_name = dest_socket_name;
1028 if (normal_map.node) {
1029 /* If a scale-bias node is required, we need to re-adjust the output
1030 * so it can be passed into the NormalMap node properly. */
1031 if (scale_bias.node) {
1032 IntermediateNode re_adjust = add_scale_bias_adjust(ntree, column + shift, ctx);
1033 link_nodes(ntree,
1034 scale_bias.node,
1035 scale_bias.sock_output_name,
1036 re_adjust.node,
1037 re_adjust.sock_input_name);
1038 link_nodes(ntree,
1039 re_adjust.node,
1040 re_adjust.sock_output_name,
1041 normal_map.node,
1042 normal_map.sock_input_name);
1043
1044 target_node = scale_bias.node;
1045 target_sock_name = scale_bias.sock_input_name;
1046 shift += 2;
1047 }
1048 else {
1049 target_node = normal_map.node;
1050 target_sock_name = normal_map.sock_input_name;
1051 }
1052
1053 link_nodes(ntree, normal_map.node, normal_map.sock_output_name, dest_node, dest_socket_name);
1054 }
1055 else if (scale_bias.node) {
1056 if (separate_color.node) {
1057 link_nodes(ntree,
1058 separate_color.node,
1059 separate_color.sock_output_name,
1060 dest_node,
1061 dest_socket_name);
1062 link_nodes(ntree,
1063 scale_bias.node,
1064 scale_bias.sock_output_name,
1065 separate_color.node,
1066 separate_color.sock_input_name);
1067 }
1068 else {
1069 link_nodes(
1070 ntree, scale_bias.node, scale_bias.sock_output_name, dest_node, dest_socket_name);
1071 }
1072 target_node = scale_bias.node;
1073 target_sock_name = scale_bias.sock_input_name;
1074 shift++;
1075 }
1076 else if (separate_color.node) {
1077 if (extra.opacity_threshold == 0.0f || dest_socket_name != "Alpha") {
1078 link_nodes(ntree,
1079 separate_color.node,
1080 separate_color.sock_output_name,
1081 dest_node,
1082 dest_socket_name);
1083 }
1084 target_node = separate_color.node;
1085 target_sock_name = separate_color.sock_input_name;
1086 }
1087
1088 /* Handle opacity threshold if necessary. */
1089 if (extra.opacity_threshold > 0.0f) {
1090 /* USD defines the threshold as >= but Blender does not have that operation. Use < instead
1091 * and then invert it. */
1092 IntermediateNode lessthan = add_lessthan(ntree, extra.opacity_threshold, column + 1, ctx);
1093 IntermediateNode invert = add_oneminus(ntree, column + 1, ctx);
1094 link_nodes(
1095 ntree, lessthan.node, lessthan.sock_output_name, invert.node, invert.sock_input_name);
1096 link_nodes(ntree, invert.node, invert.sock_output_name, dest_node, dest_socket_name);
1097 if (separate_color.node) {
1098 link_nodes(ntree,
1099 separate_color.node,
1100 separate_color.sock_output_name,
1101 lessthan.node,
1102 lessthan.sock_input_name);
1103 }
1104 else {
1105 target_node = lessthan.node;
1106 target_sock_name = lessthan.sock_input_name;
1107 }
1108 }
1109
1110 convert_usd_uv_texture(source_shader,
1111 source_name,
1112 target_node,
1113 target_sock_name,
1114 ntree,
1115 column + shift,
1116 ctx,
1117 extra);
1118 }
1119 else if (shader_id == usdtokens::UsdPrimvarReader_float2) {
1121 source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, ctx);
1122 }
1123 else if (shader_id == usdtokens::UsdTransform2d) {
1124 convert_usd_transform_2d(source_shader, dest_node, dest_socket_name, ntree, column + 1, ctx);
1125 }
1126 else {
1127 /* Handle any remaining "generic" primvar readers. */
1128 StringRef shader_id_name(shader_id.GetString());
1129 if (shader_id_name.startswith("UsdPrimvarReader_")) {
1130 int64_t type_offset = shader_id_name.rfind('_');
1131 if (type_offset >= 0) {
1132 StringRef output_type = shader_id_name.drop_prefix(type_offset + 1);
1134 source_shader, output_type, dest_node, dest_socket_name, ntree, column + 1, ctx);
1135 }
1136 }
1137 }
1138
1139 return true;
1140}
1141
1142void USDMaterialReader::convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader,
1143 const pxr::TfToken &usd_source_name,
1144 bNode *dest_node,
1145 const StringRefNull dest_socket_name,
1146 bNodeTree *ntree,
1147 const int column,
1149 const ExtraLinkInfo &extra) const
1150{
1151 if (!usd_shader || !dest_node || !ntree || dest_socket_name.is_empty()) {
1152 return;
1153 }
1154
1155 bNode *tex_image = ctx.get_cached_node(usd_shader);
1156
1157 if (tex_image == nullptr) {
1158 const float2 loc = ctx.compute_node_loc(column);
1159
1160 /* Create the Texture Image node. */
1161 tex_image = add_node(ntree, SH_NODE_TEX_IMAGE, loc);
1162
1163 /* Cache newly created node. */
1164 ctx.cache_node(usd_shader, tex_image);
1165
1166 /* Load the texture image. */
1167 load_tex_image(usd_shader, tex_image, extra);
1168 }
1169
1170 /* Connect to destination node input. */
1171
1172 /* Get the source socket name. */
1173 const StringRefNull source_socket_name = usd_source_name == usdtokens::a ? "Alpha" : "Color";
1174
1175 link_nodes(ntree, tex_image, source_socket_name, dest_node, dest_socket_name);
1176
1177 /* Connect the texture image node "Vector" input. */
1178 if (pxr::UsdShadeInput st_input = usd_shader.GetInput(usdtokens::st)) {
1179 set_node_input(st_input, tex_image, "Vector", ntree, column, ctx);
1180 }
1181}
1182
1183void USDMaterialReader::convert_usd_transform_2d(const pxr::UsdShadeShader &usd_shader,
1184 bNode *dest_node,
1185 const StringRefNull dest_socket_name,
1186 bNodeTree *ntree,
1187 int column,
1188 NodePlacementContext &ctx) const
1189{
1190 if (!usd_shader || !dest_node || !ntree || dest_socket_name.is_empty()) {
1191 return;
1192 }
1193
1194 bNode *mapping = ctx.get_cached_node(usd_shader);
1195
1196 if (mapping == nullptr) {
1197 const float2 loc = ctx.compute_node_loc(column);
1198
1199 /* Create the MAPPING node. */
1200 mapping = add_node(ntree, SH_NODE_MAPPING, loc);
1201
1202 /* Cache newly created node. */
1203 ctx.cache_node(usd_shader, mapping);
1204
1205 mapping->custom1 = TEXMAP_TYPE_POINT;
1206
1207 if (bNodeSocket *scale_socket = get_input_socket(mapping, "Scale", reports())) {
1208 if (pxr::UsdShadeInput scale_input = get_input(usd_shader, usdtokens::scale)) {
1209 pxr::VtValue val;
1210 if (scale_input.Get(&val) && val.CanCast<pxr::GfVec2f>()) {
1211 pxr::GfVec2f scale_val = val.Cast<pxr::GfVec2f>().UncheckedGet<pxr::GfVec2f>();
1212 float scale[3] = {scale_val[0], scale_val[1], 1.0f};
1213 copy_v3_v3(((bNodeSocketValueVector *)scale_socket->default_value)->value, scale);
1214 }
1215 }
1216 }
1217
1218 if (bNodeSocket *loc_socket = get_input_socket(mapping, "Location", reports())) {
1219 if (pxr::UsdShadeInput trans_input = get_input(usd_shader, usdtokens::translation)) {
1220 pxr::VtValue val;
1221 if (trans_input.Get(&val) && val.CanCast<pxr::GfVec2f>()) {
1222 pxr::GfVec2f trans_val = val.Cast<pxr::GfVec2f>().UncheckedGet<pxr::GfVec2f>();
1223 float location[3] = {trans_val[0], trans_val[1], 0.0f};
1224 copy_v3_v3(((bNodeSocketValueVector *)loc_socket->default_value)->value, location);
1225 }
1226 }
1227 }
1228
1229 if (bNodeSocket *rot_socket = get_input_socket(mapping, "Rotation", reports())) {
1230 if (pxr::UsdShadeInput rot_input = get_input(usd_shader, usdtokens::rotation)) {
1231 pxr::VtValue val;
1232 if (rot_input.Get(&val) && val.CanCast<float>()) {
1233 float rot_val = val.Cast<float>().UncheckedGet<float>() * M_PI / 180.0f;
1234 float rot[3] = {0.0f, 0.0f, rot_val};
1235 copy_v3_v3(((bNodeSocketValueVector *)rot_socket->default_value)->value, rot);
1236 }
1237 }
1238 }
1239 }
1240
1241 /* Connect to destination node input. */
1242 link_nodes(ntree, mapping, "Vector", dest_node, dest_socket_name);
1243
1244 /* Connect the mapping node "Vector" input. */
1245 if (pxr::UsdShadeInput in_input = usd_shader.GetInput(usdtokens::in)) {
1246 set_node_input(in_input, mapping, "Vector", ntree, column, ctx);
1247 }
1248}
1249
1250void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
1251 bNode *tex_image,
1252 const ExtraLinkInfo &extra) const
1253{
1254 if (!(usd_shader && tex_image && tex_image->type_legacy == SH_NODE_TEX_IMAGE)) {
1255 return;
1256 }
1257
1258 /* Try to load the texture image. */
1259 pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file);
1260
1261 if (!file_input) {
1262 CLOG_WARN(&LOG,
1263 "Couldn't get file input property for USD shader %s",
1264 usd_shader.GetPath().GetAsString().c_str());
1265 return;
1266 }
1267
1268 /* File input may have a connected source, e.g., if it's been overridden by
1269 * an input on the material. */
1270 if (file_input.HasConnectedSource()) {
1271 pxr::UsdShadeConnectableAPI source;
1272 pxr::TfToken source_name;
1273 pxr::UsdShadeAttributeType source_type;
1274
1275 if (file_input.GetConnectedSource(&source, &source_name, &source_type)) {
1276 file_input = source.GetInput(source_name);
1277 }
1278 else {
1279 CLOG_WARN(&LOG,
1280 "Couldn't get connected source for file input %s (%s)\n",
1281 file_input.GetPrim().GetPath().GetText(),
1282 file_input.GetFullName().GetText());
1283 }
1284 }
1285
1286 pxr::VtValue file_val;
1287 if (!file_input.Get(&file_val) || !file_val.IsHolding<pxr::SdfAssetPath>()) {
1288 CLOG_WARN(&LOG,
1289 "Couldn't get file input value for USD shader %s",
1290 usd_shader.GetPath().GetAsString().c_str());
1291 return;
1292 }
1293
1294 const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>();
1295 std::string file_path = asset_path.GetResolvedPath();
1296
1297 if (file_path.empty()) {
1298 /* No resolved path, so use the asset path (usually necessary for UDIM paths). */
1299 file_path = asset_path.GetAssetPath();
1300
1301 if (!file_path.empty() && is_udim_path(file_path)) {
1302 /* Texture paths are frequently relative to the USD, so get the absolute path. */
1303 if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
1304 file_path = layer_handle->ComputeAbsolutePath(file_path);
1305 }
1306 }
1307 }
1308
1309 if (file_path.empty()) {
1310 CLOG_WARN(&LOG,
1311 "Couldn't resolve image asset '%s' for Texture Image node",
1312 asset_path.GetAssetPath().c_str());
1313 return;
1314 }
1315
1316 /* Optionally copy the asset if it's inside a USDZ package. */
1317 const bool is_relative = pxr::ArIsPackageRelativePath(file_path);
1318 const bool import_textures = params_.import_textures_mode != USD_TEX_IMPORT_NONE && is_relative;
1319
1320 std::string imported_file_source_path;
1321
1322 if (import_textures) {
1323 imported_file_source_path = file_path;
1324
1325 /* If we are packing the imported textures, we first write them
1326 * to a temporary directory. */
1327 const char *textures_dir = params_.import_textures_mode == USD_TEX_IMPORT_PACK ?
1329 params_.import_textures_dir;
1330
1331 const eUSDTexNameCollisionMode name_collision_mode = params_.import_textures_mode ==
1334 params_.tex_name_collision_mode;
1335
1336 file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode, reports());
1337 }
1338
1339 /* If this is a UDIM texture, this will store the
1340 * UDIM tile indices. */
1341 blender::Vector<int> udim_tiles;
1342
1343 if (is_udim_path(file_path)) {
1344 udim_tiles = get_udim_tiles(file_path);
1345 }
1346
1347 const char *im_file = file_path.c_str();
1348 Image *image = BKE_image_load_exists(&bmain_, im_file);
1349 if (!image) {
1350 CLOG_WARN(&LOG, "Couldn't open image file '%s' for Texture Image node", im_file);
1351 return;
1352 }
1353
1354 if (!udim_tiles.is_empty()) {
1355 add_udim_tiles(image, udim_tiles);
1356 }
1357
1358 tex_image->id = &image->id;
1359
1360 /* Set texture color space.
1361 * TODO(makowalski): For now, just checking for RAW color space,
1362 * assuming sRGB otherwise, but more complex logic might be
1363 * required if the color space is "auto". */
1364
1365 pxr::TfToken color_space = get_source_color_space(usd_shader);
1366
1367 if (color_space.IsEmpty()) {
1368 color_space = file_input.GetAttr().GetColorSpace();
1369 }
1370
1371 if (color_space.IsEmpty()) {
1372 /* At this point, assume the "auto" space and translate accordingly. */
1373 color_space = usdtokens::auto_;
1374 }
1375
1376 if (color_space == usdtokens::auto_) {
1377 /* If it's auto, determine whether to apply color correction based
1378 * on incoming connection (passed in from outer functions). */
1382 }
1383
1384 else if (color_space == usdtokens::sRGB) {
1386 }
1387
1388 /*
1389 * Due to there being a lot of non-compliant USD assets out there, this is
1390 * a special case where we need to check for different spellings here.
1391 * On write, we are *only* using the correct, lower-case "raw" token.
1392 */
1393 else if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
1396 }
1397
1398 NodeTexImage *storage = static_cast<NodeTexImage *>(tex_image->storage);
1399 storage->extension = get_image_extension(usd_shader, storage->extension);
1400
1401 if (import_textures && imported_file_source_path != file_path) {
1402 ensure_usd_source_path_prop(imported_file_source_path, &image->id);
1403 }
1404
1405 if (import_textures && params_.import_textures_mode == USD_TEX_IMPORT_PACK &&
1407 {
1408 BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(&bmain_, &image->id));
1410 BLI_delete(temp_textures_dir(), true, true);
1411 }
1412 }
1413}
1414
1415void USDMaterialReader::convert_usd_primvar_reader_float2(const pxr::UsdShadeShader &usd_shader,
1416 const pxr::TfToken & /*usd_source_name*/,
1417 bNode *dest_node,
1418 const StringRefNull dest_socket_name,
1419 bNodeTree *ntree,
1420 const int column,
1421 NodePlacementContext &ctx) const
1422{
1423 if (!usd_shader || !dest_node || !ntree || dest_socket_name.is_empty()) {
1424 return;
1425 }
1426
1427 bNode *uv_map = ctx.get_cached_node(usd_shader);
1428
1429 if (uv_map == nullptr) {
1430 const float2 loc = ctx.compute_node_loc(column);
1431
1432 /* Create the UV Map node. */
1433 uv_map = add_node(ntree, SH_NODE_UVMAP, loc);
1434
1435 /* Cache newly created node. */
1436 ctx.cache_node(usd_shader, uv_map);
1437
1438 /* Set the texmap name. */
1439 pxr::UsdShadeInput varname_input = usd_shader.GetInput(usdtokens::varname);
1440
1441 /* First check if the shader's "varname" input is connected to another source,
1442 * and use that instead if so. */
1443 if (varname_input) {
1444 for (const pxr::UsdShadeConnectionSourceInfo &source_info :
1445 varname_input.GetConnectedSources())
1446 {
1447 pxr::UsdShadeShader shader = pxr::UsdShadeShader(source_info.source.GetPrim());
1448 pxr::UsdShadeInput secondary_varname_input = shader.GetInput(source_info.sourceName);
1449 if (secondary_varname_input) {
1450 varname_input = secondary_varname_input;
1451 break;
1452 }
1453 }
1454 }
1455
1456 if (varname_input) {
1457 pxr::VtValue varname_val;
1458 /* The varname input may be a string or TfToken, so just cast it to a string.
1459 * The Cast function is defined to provide an empty result if it fails. */
1460 if (varname_input.Get(&varname_val) && varname_val.CanCastToTypeid(typeid(std::string))) {
1461 std::string varname = varname_val.Cast<std::string>().Get<std::string>();
1462 if (!varname.empty()) {
1463 NodeShaderUVMap *storage = (NodeShaderUVMap *)uv_map->storage;
1464 STRNCPY(storage->uv_map, varname.c_str());
1465 }
1466 }
1467 }
1468 }
1469
1470 /* Connect to destination node input. */
1471 link_nodes(ntree, uv_map, "UV", dest_node, dest_socket_name);
1472}
1473
1474void USDMaterialReader::convert_usd_primvar_reader_generic(const pxr::UsdShadeShader &usd_shader,
1475 const StringRef output_type,
1476 bNode *dest_node,
1477 const StringRefNull dest_socket_name,
1478 bNodeTree *ntree,
1479 const int column,
1480 NodePlacementContext &ctx) const
1481{
1482 if (!usd_shader || !dest_node || !ntree) {
1483 return;
1484 }
1485
1486 bNode *attribute = ctx.get_cached_node(usd_shader);
1487
1488 if (attribute == nullptr) {
1489 const float2 loc = ctx.compute_node_loc(column);
1490
1491 /* Create the attribute node. */
1492 attribute = add_node(ntree, SH_NODE_ATTRIBUTE, loc);
1493
1494 /* Cache newly created node. */
1495 ctx.cache_node(usd_shader, attribute);
1496
1497 /* Set the attribute name. */
1498 pxr::UsdShadeInput varname_input = usd_shader.GetInput(usdtokens::varname);
1499
1500 /* First check if the shader's "varname" input is connected to another source,
1501 * and use that instead if so. */
1502 if (varname_input) {
1503 for (const pxr::UsdShadeConnectionSourceInfo &source_info :
1504 varname_input.GetConnectedSources())
1505 {
1506 pxr::UsdShadeShader shader = pxr::UsdShadeShader(source_info.source.GetPrim());
1507 pxr::UsdShadeInput secondary_varname_input = shader.GetInput(source_info.sourceName);
1508 if (secondary_varname_input) {
1509 varname_input = secondary_varname_input;
1510 break;
1511 }
1512 }
1513 }
1514
1515 if (varname_input) {
1516 pxr::VtValue varname_val;
1517 /* The varname input may be a string or TfToken, so just cast it to a string.
1518 * The Cast function is defined to provide an empty result if it fails. */
1519 if (varname_input.Get(&varname_val) && varname_val.CanCastToTypeid(typeid(std::string))) {
1520 std::string varname = varname_val.Cast<std::string>().Get<std::string>();
1521 if (!varname.empty()) {
1522 NodeShaderAttribute *storage = (NodeShaderAttribute *)attribute->storage;
1523 STRNCPY(storage->name, varname.c_str());
1524 }
1525 }
1526 }
1527 }
1528
1529 /* Connect to destination node input. */
1530 if (ELEM(output_type, "float", "int")) {
1531 link_nodes(ntree, attribute, "Fac", dest_node, dest_socket_name);
1532 }
1533 else if (ELEM(output_type, "float3", "float4")) {
1534 link_nodes(ntree, attribute, "Color", dest_node, dest_socket_name);
1535 }
1536 else if (ELEM(output_type, "vector", "normal", "point")) {
1537 link_nodes(ntree, attribute, "Vector", dest_node, dest_socket_name);
1538 }
1539}
1540
1542{
1543 BLI_assert_msg(r_mat_map.is_empty(), "The incoming material map should be empty");
1544
1545 LISTBASE_FOREACH (Material *, material, &bmain->materials) {
1546 std::string usd_name = make_safe_name(material->id.name + 2, true);
1547 r_mat_map.add_new(usd_name, material);
1548 }
1549}
1550
1551Material *find_existing_material(const pxr::SdfPath &usd_mat_path,
1552 const USDImportParams &params,
1554 const blender::Map<pxr::SdfPath, Material *> &usd_path_to_mat)
1555{
1556 if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) {
1557 /* Check if we've already created the Blender material with a modified name. */
1558 return usd_path_to_mat.lookup_default(usd_mat_path, nullptr);
1559 }
1560
1561 return mat_map.lookup_default(usd_mat_path.GetName(), nullptr);
1562}
1563
1564} // namespace blender::io::usd
void BKE_image_packfiles(ReportList *reports, Image *ima, const char *basepath)
Image * BKE_image_load_exists(Main *bmain, const char *filepath, bool *r_exists=nullptr)
bool BKE_image_get_tile_info(char *filepath, ListBase *tiles, int *r_tile_start, int *r_tile_range)
bool BKE_image_has_packedfile(const Image *image)
ImageTile * BKE_image_add_tile(Image *ima, int tile_number, const char *label)
void id_us_min(ID *id)
Definition lib_id.cc:361
General operations, lookup, etc. for materials.
Material * BKE_material_add(Main *bmain, const char *name)
#define SH_NODE_UVMAP
#define SH_NODE_TEX_IMAGE
#define SH_NODE_BSDF_PRINCIPLED
#define SH_NODE_SEPARATE_COLOR
#define SH_NODE_VECTOR_MATH
#define SH_NODE_NORMAL_MAP
#define SH_NODE_MATH
#define SH_NODE_OUTPUT_MATERIAL
#define SH_NODE_MAPPING
#define SH_NODE_DISPLACEMENT
#define SH_NODE_ATTRIBUTE
void BKE_ntree_update_after_single_tree_change(Main &bmain, bNodeTree &modified_tree, const NodeTreeUpdateExtraParams &params={})
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
File and directory operations.
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:456
#define LISTBASE_FOREACH(type, var, list)
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
#define M_PI
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void copy_v3_fl3(float v[3], float x, float y, float z)
#define FILE_MAX
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
#define POINTER_AS_INT(i)
#define ELEM(...)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
@ IMA_SRC_TILED
@ MA_SURFACE_METHOD_FORWARD
@ MA_DISPLACEMENT_BOTH
@ NODE_VECTOR_MATH_MULTIPLY_ADD
@ NODE_MATH_LESS_THAN
@ NODE_MATH_SUBTRACT
@ SOCK_OUT
@ SOCK_IN
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
@ SHD_IMAGE_EXTENSION_MIRROR
@ SHD_IMAGE_EXTENSION_CLIP
@ SHD_IMAGE_EXTENSION_REPEAT
@ SHD_IMAGE_EXTENSION_EXTEND
@ TEXMAP_TYPE_POINT
const char * IMB_colormanagement_srgb_colorspace_name_get()
@ COLOR_ROLE_DATA
@ COLOR_ROLE_DEFAULT_BYTE
const char * IMB_colormanagement_role_colorspace_name_get(int role)
ReportList * reports
Definition WM_types.hh:1025
long long int int64_t
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
bool is_empty() const
Definition BLI_map.hh:986
constexpr int64_t rfind(char c, int64_t pos=INT64_MAX) const
constexpr bool is_empty() const
constexpr bool startswith(StringRef prefix) const
constexpr const char * c_str() const
constexpr StringRef drop_prefix(int64_t n) const
void append(const T &value)
bool is_empty() const
bool follow_connection(const pxr::UsdShadeInput &usd_input, bNode *dest_node, const StringRefNull dest_socket_name, bNodeTree *ntree, int column, NodePlacementContext &ctx, const ExtraLinkInfo &extra={}) const
void convert_usd_transform_2d(const pxr::UsdShadeShader &usd_shader, bNode *dest_node, const StringRefNull dest_socket_name, bNodeTree *ntree, int column, NodePlacementContext &ctx) const
Material * add_material(const pxr::UsdShadeMaterial &usd_material, bool read_usd_preview=true) const
void import_usd_preview_nodes(Material *mtl, const pxr::UsdShadeMaterial &usd_material, const pxr::UsdShadeShader &usd_shader) const
USDMaterialReader(const USDImportParams &params, Main &bmain)
bool set_displacement_node_inputs(bNodeTree *ntree, bNode *output, const pxr::UsdShadeShader &usd_shader) const
bool set_node_input(const pxr::UsdShadeInput &usd_input, bNode *dest_node, const StringRefNull dest_socket_name, bNodeTree *ntree, int column, NodePlacementContext &ctx, const ExtraLinkInfo &extra={}) const
void convert_usd_primvar_reader_generic(const pxr::UsdShadeShader &usd_shader, StringRef output_type, bNode *dest_node, const StringRefNull dest_socket_name, bNodeTree *ntree, int column, NodePlacementContext &ctx) const
void convert_usd_primvar_reader_float2(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &usd_source_name, bNode *dest_node, const StringRefNull dest_socket_name, bNodeTree *ntree, int column, NodePlacementContext &ctx) const
void convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &usd_source_name, bNode *dest_node, const StringRefNull dest_socket_name, bNodeTree *ntree, int column, NodePlacementContext &ctx, const ExtraLinkInfo &extra={}) const
void set_principled_node_inputs(bNode *principled_node, bNodeTree *ntree, const pxr::UsdShadeShader &usd_shader) const
void import_usd_preview(Material *mtl, const pxr::UsdShadeMaterial &usd_material) const
void load_tex_image(const pxr::UsdShadeShader &usd_shader, bNode *tex_image, const ExtraLinkInfo &extra={}) const
#define rot(x, k)
static ushort indices[]
#define input
#define in
#define output
#define ID_BLEND_PATH(_bmain, _id)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
CCL_NAMESPACE_BEGIN ccl_device float invert(const float color, const float factor)
Definition invert.h:11
ccl_gpu_kernel_postfix ccl_global KernelWorkTile * tiles
const ccl_global KernelWorkTile * tile
#define LOG(severity)
Definition log.h:32
ccl_device_inline float average(const float2 a)
bNodeTree * node_tree_add_tree_embedded(Main *bmain, ID *owner_id, StringRefNull name, StringRefNull idname)
Definition node.cc:4375
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2864
int node_count_socket_links(const bNodeTree &ntree, const bNodeSocket &sock)
Definition node.cc:4946
bNode * node_add_static_node(const bContext *C, bNodeTree &ntree, int type)
Definition node.cc:3804
bNodeLink & node_add_link(bNodeTree &ntree, bNode &fromnode, bNodeSocket &fromsock, bNode &tonode, bNodeSocket &tosock)
Definition node.cc:4087
void node_set_active(bNodeTree &ntree, bNode &node)
Definition node.cc:4996
const char * temp_textures_dir()
static IntermediateNode add_separate_color(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &usd_source_name, bNodeTree *ntree, int column, NodePlacementContext &ctx)
Map< std::string, bNode * > ShaderToNodeMap
std::string import_asset(const char *src, const char *import_dir, eUSDTexNameCollisionMode name_collision_mode, ReportList *reports)
@ USD_TEX_IMPORT_NONE
Definition usd.hh:65
@ USD_TEX_IMPORT_PACK
Definition usd.hh:66
bool is_udim_path(const std::string &path)
static IntermediateNode add_scale_bias_adjust(bNodeTree *ntree, int column, NodePlacementContext &ctx)
void set_id_props_from_prim(ID *id, const pxr::UsdPrim &prim, const eUSDAttrImportMode attr_import_mode, const pxr::UsdTimeCode time_code)
static IntermediateNode add_scale_bias(const pxr::UsdShadeShader &usd_shader, bNodeTree *ntree, int column, bool feeds_normal_map, NodePlacementContext &ctx)
void build_material_map(const Main *bmain, blender::Map< std::string, Material * > &r_mat_map)
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)
static pxr::UsdShadeShader node_graph_output_source(const pxr::UsdShadeNodeGraph &node_graph, const pxr::TfToken &output_name)
eUSDTexNameCollisionMode
Definition usd.hh:74
@ USD_TEX_NAME_COLLISION_OVERWRITE
Definition usd.hh:76
static IntermediateNode add_oneminus(bNodeTree *ntree, int column, NodePlacementContext &ctx)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
static IntermediateNode add_normal_map(bNodeTree *ntree, int column, NodePlacementContext &ctx)
void ensure_usd_source_path_prop(const std::string &path, ID *id)
@ USD_MTL_NAME_COLLISION_MAKE_UNIQUE
Definition usd.hh:36
static void configure_displacement(const pxr::UsdShadeShader &usd_shader, bNode *displacement_node)
static IntermediateNode add_lessthan(bNodeTree *ntree, float threshold, int column, NodePlacementContext &ctx)
VecBase< float, 2 > float2
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal)
static const pxr::TfToken st("st", pxr::TfToken::Immortal)
static const pxr::TfToken bias("bias", pxr::TfToken::Immortal)
static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal)
static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal)
static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal)
static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal)
static const pxr::TfToken varname("varname", pxr::TfToken::Immortal)
static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal)
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal)
static const pxr::TfToken r("r", pxr::TfToken::Immortal)
static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal)
static const pxr::TfToken b("b", pxr::TfToken::Immortal)
static const pxr::TfToken rotation("rotation", pxr::TfToken::Immortal)
static const pxr::TfToken ior("ior", pxr::TfToken::Immortal)
static const pxr::TfToken rgba("rgba", pxr::TfToken::Immortal)
static const pxr::TfToken translation("translation", pxr::TfToken::Immortal)
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal)
static const pxr::TfToken g("g", pxr::TfToken::Immortal)
static const pxr::TfToken in("in", pxr::TfToken::Immortal)
static const pxr::TfToken occlusion("occlusion", pxr::TfToken::Immortal)
static const pxr::TfToken auto_("auto", pxr::TfToken::Immortal)
static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal)
static const pxr::TfToken scale("scale", pxr::TfToken::Immortal)
static const pxr::TfToken file("file", pxr::TfToken::Immortal)
static const pxr::TfToken mirror("mirror", pxr::TfToken::Immortal)
static const pxr::TfToken opacityThreshold("opacityThreshold", pxr::TfToken::Immortal)
static const pxr::TfToken normal("normal", pxr::TfToken::Immortal)
static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal)
static const pxr::TfToken black("black", pxr::TfToken::Immortal)
static const pxr::TfToken UsdTransform2d("UsdTransform2d", pxr::TfToken::Immortal)
static const pxr::TfToken displacement("displacement", pxr::TfToken::Immortal)
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal)
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal)
static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal)
static const pxr::TfToken diffuseColor("diffuseColor", pxr::TfToken::Immortal)
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal)
static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal)
static const pxr::TfToken specularColor("specularColor", pxr::TfToken::Immortal)
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal)
static const pxr::TfToken a("a", pxr::TfToken::Immortal)
static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2", pxr::TfToken::Immortal)
ColorManagedColorspaceSettings colorspace_settings
short source
ListBase materials
Definition BKE_main.hh:251
char surface_render_method
void * default_value
char idname[64]
float location[2]
int16_t custom1
struct ID * id
int16_t type_legacy
void * storage
char idname[64]
bNode * get_cached_node(const pxr::UsdShadeShader &usd_shader, const blender::StringRef tag={}) const
void cache_node(const pxr::UsdShadeShader &usd_shader, bNode *node, const blender::StringRef tag={})
std::string get_key(const pxr::UsdShadeShader &usd_shader, const blender::StringRef tag) const
static float get_opacity_threshold(const pxr::UsdShadeShader &usd_shader, float default_value=0.0f)
static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader)
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)
static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
static blender::Vector< int > get_udim_tiles(const std::string &file_path)
static int get_image_extension(const pxr::UsdShadeShader &usd_shader, const int default_value)
static bNodeSocket * get_input_socket(bNode *node, const blender::StringRefNull identifier, ReportList *reports)
static bool get_usd_preview_surface(const pxr::UsdShadeMaterial &usd_material, pxr::UsdShadeShader &r_preview_surface)
static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &attribute)
static void link_nodes(bNodeTree *ntree, bNode *source, const blender::StringRefNull sock_out, bNode *dest, const blender::StringRefNull sock_in)
static void set_viewport_material_props(Material *mtl, const pxr::UsdShadeShader &usd_preview)
static void add_udim_tiles(Image *image, const blender::Vector< int > &indices)
static bNode * add_node(bNodeTree *ntree, const int type, const blender::float2 loc)