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