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