Blender V5.0
usd_writer_material.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6#include "usd_asset_utils.hh"
8#include "usd_hook.hh"
9#include "usd_utils.hh"
10
11#include "BKE_image.hh"
12#include "BKE_image_format.hh"
13#include "BKE_library.hh"
14#include "BKE_main.hh"
15#include "BKE_node.hh"
17#include "BKE_node_runtime.hh"
18#include "BKE_report.hh"
19
21#include "IMB_imbuf.hh"
22
23#include "BLI_fileops.h"
24#include "BLI_listbase.h"
25#include "BLI_map.hh"
26#include "BLI_path_utils.hh"
27#include "BLI_set.hh"
28#include "BLI_string.h"
29#include "BLI_string_ref.hh"
30#include "BLI_string_utils.hh"
31
32#include "DNA_material_types.h"
33#include "DNA_node_types.h"
35
36#include "MEM_guardedalloc.h"
37
38#include "WM_types.hh"
39
40#include <pxr/base/tf/stringUtils.h>
41
42#ifdef WITH_MATERIALX
44# include <pxr/usd/sdf/copyUtils.h>
45# include <pxr/usd/usdMtlx/materialXConfigAPI.h>
46# include <pxr/usd/usdMtlx/reader.h>
47#endif
48
49#include "CLG_log.h"
50static CLG_LogRef LOG = {"io.usd"};
51
52/* `TfToken` objects are not cheap to construct, so we do it once. */
53namespace usdtokens {
54/* Materials. */
55static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal);
56static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal);
57static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal);
58static const pxr::TfToken emissive_color("emissiveColor", pxr::TfToken::Immortal);
59static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
60static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
61static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
62static const pxr::TfToken UsdTransform2d("UsdTransform2d", pxr::TfToken::Immortal);
63static const pxr::TfToken uv_texture("UsdUVTexture", pxr::TfToken::Immortal);
64static const pxr::TfToken primvar_float("UsdPrimvarReader_float", pxr::TfToken::Immortal);
65static const pxr::TfToken primvar_float2("UsdPrimvarReader_float2", pxr::TfToken::Immortal);
66static const pxr::TfToken primvar_float3("UsdPrimvarReader_float3", pxr::TfToken::Immortal);
67static const pxr::TfToken primvar_vector("UsdPrimvarReader_vector", pxr::TfToken::Immortal);
68static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
69static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
70static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal);
71static const pxr::TfToken opacityThreshold("opacityThreshold", pxr::TfToken::Immortal);
72static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
73static const pxr::TfToken displacement("displacement", pxr::TfToken::Immortal);
74static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal);
75static const pxr::TfToken r("r", pxr::TfToken::Immortal);
76static const pxr::TfToken g("g", pxr::TfToken::Immortal);
77static const pxr::TfToken b("b", pxr::TfToken::Immortal);
78static const pxr::TfToken a("a", pxr::TfToken::Immortal);
79static const pxr::TfToken st("st", pxr::TfToken::Immortal);
80static const pxr::TfToken result("result", pxr::TfToken::Immortal);
81static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
82static const pxr::TfToken out("out", pxr::TfToken::Immortal);
83static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
84static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
85static const pxr::TfToken file("file", pxr::TfToken::Immortal);
86static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
87static const pxr::TfToken scale("scale", pxr::TfToken::Immortal);
88static const pxr::TfToken bias("bias", pxr::TfToken::Immortal);
89static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
90static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
91static const pxr::TfToken black("black", pxr::TfToken::Immortal);
92static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
93static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
94static const pxr::TfToken mirror("mirror", pxr::TfToken::Immortal);
95static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
96static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
97static const pxr::TfToken in("in", pxr::TfToken::Immortal);
98static const pxr::TfToken translation("translation", pxr::TfToken::Immortal);
99static const pxr::TfToken rotation("rotation", pxr::TfToken::Immortal);
100} // namespace usdtokens
101
102namespace blender::io::usd {
103
104/* Preview surface input specification. */
105struct InputSpec {
106 pxr::TfToken input_name;
107 pxr::SdfValueTypeName input_type;
108 /* Whether a default value should be set
109 * if the node socket has not input. Usually
110 * false for the Normal input. */
112};
113
114/* Map Blender socket names to USD Preview Surface InputSpec structs. */
116
117/* Static function forward declarations. */
118static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
119 const pxr::UsdShadeMaterial &material,
120 const StringRef name,
121 int type);
122static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
123 const pxr::UsdShadeMaterial &material,
124 bNode *node);
125static pxr::UsdShadeShader create_primvar_reader_shader(
126 const USDExporterContext &usd_export_context,
127 const pxr::UsdShadeMaterial &material,
128 const pxr::TfToken &primvar_type,
129 const bNode *node);
130static void create_uv_input(const USDExporterContext &usd_export_context,
131 bNodeSocket *input_socket,
132 pxr::UsdShadeMaterial &usd_material,
133 pxr::UsdShadeInput &usd_input,
134 const std::string &active_uvmap_name,
135 ReportList *reports);
136static void export_texture(const USDExporterContext &usd_export_context, bNode *node);
137static bNode *find_bsdf_node(Material *material);
138static bNode *find_displacement_node(Material *material);
139static void get_absolute_path(const Image *ima, char *r_path);
140static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context,
141 bNode *node);
143static bNodeLink *traverse_channel(bNodeSocket *input, short target_type);
144
145void set_normal_texture_range(pxr::UsdShadeShader &usd_shader, const InputSpec &input_spec);
146
147/* Create an input on the given shader with name and type
148 * provided by the InputSpec and assign the given value to the
149 * input. Parameters T1 and T2 indicate the Blender and USD
150 * value types, respectively. */
151template<typename T1, typename T2>
152void create_input(pxr::UsdShadeShader &shader,
153 const InputSpec &spec,
154 const void *value,
155 float scale)
156{
157 const T1 *cast_value = static_cast<const T1 *>(value);
158 shader.CreateInput(spec.input_name, spec.input_type).Set(scale * T2(cast_value->value));
159}
160
161static void set_scale_bias(pxr::UsdShadeShader &usd_shader,
162 const pxr::GfVec4f scale,
163 const pxr::GfVec4f bias)
164{
165 pxr::UsdShadeInput scale_attr = usd_shader.GetInput(usdtokens::scale);
166 if (!scale_attr) {
167 scale_attr = usd_shader.CreateInput(usdtokens::scale, pxr::SdfValueTypeNames->Float4);
168 }
169 scale_attr.Set(scale);
170
171 pxr::UsdShadeInput bias_attr = usd_shader.GetInput(usdtokens::bias);
172 if (!bias_attr) {
173 bias_attr = usd_shader.CreateInput(usdtokens::bias, pxr::SdfValueTypeNames->Float4);
174 }
175 bias_attr.Set(bias);
176}
177
178static void process_inputs(const USDExporterContext &usd_export_context,
179 pxr::UsdShadeMaterial &usd_material,
180 pxr::UsdShadeShader &shader,
181 const bNode *node,
182 const std::string &active_uvmap_name,
183 ReportList *reports)
184{
185 const InputSpecMap &input_map = preview_surface_input_map();
186
187 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
188 /* Check if this socket is mapped to a USD preview shader input. */
189 const InputSpec *spec = input_map.lookup_ptr(sock->name);
190 if (spec == nullptr) {
191 continue;
192 }
193
194 const InputSpec &input_spec = *spec;
195
196 /* Allow scaling inputs. */
197 float input_scale = 1.0;
198
199 /* Don't export emission color if strength is zero. */
200 if (input_spec.input_name == usdtokens::emissive_color) {
201 const bNodeSocket *emission_strength_sock = bke::node_find_socket(
202 *node, SOCK_IN, "Emission Strength");
203 if (!emission_strength_sock) {
204 continue;
205 }
206
207 input_scale = ((bNodeSocketValueFloat *)emission_strength_sock->default_value)->value;
208 if (input_scale == 0.0f) {
209 continue;
210 }
211 }
212
213 bool processed = false;
214
215 /* Check for an upstream Image node. */
216 const bNodeLink *input_link = traverse_channel(sock, SH_NODE_TEX_IMAGE);
217 if (input_link) {
218 /* Convert the texture image node connected to this input. */
219 bNode *input_node = input_link->fromnode;
220 pxr::UsdShadeShader usd_shader = create_usd_preview_shader(
221 usd_export_context, usd_material, input_node);
222
223 /* Create the UsdUVTexture node output attribute that should be connected to this input. */
224 pxr::TfToken source_name;
225 if (input_spec.input_type == pxr::SdfValueTypeNames->Float) {
226 /* If the input is a float, we check if there is also a Separate Color node in between, if
227 * there is use the output channel from that, otherwise connect either the texture alpha or
228 * red channels. */
229 const bNodeLink *input_link_sep_color = traverse_channel(sock, SH_NODE_SEPARATE_COLOR);
230 if (input_link_sep_color) {
231 if (STREQ(input_link_sep_color->fromsock->identifier, "Red")) {
232 source_name = usdtokens::r;
233 }
234 if (STREQ(input_link_sep_color->fromsock->identifier, "Green")) {
235 source_name = usdtokens::g;
236 }
237 if (STREQ(input_link_sep_color->fromsock->identifier, "Blue")) {
238 source_name = usdtokens::b;
239 }
240 }
241 else {
242 source_name = STREQ(input_link->fromsock->identifier, "Alpha") ? usdtokens::a :
244 }
245 usd_shader.CreateOutput(source_name, pxr::SdfValueTypeNames->Float);
246 }
247 else {
248 source_name = usdtokens::rgb;
249 usd_shader.CreateOutput(usdtokens::rgb, pxr::SdfValueTypeNames->Float3);
250 }
251
252 /* Create the preview surface input and connect it to the shader. */
253 pxr::UsdShadeConnectionSourceInfo source_info(
254 usd_shader.ConnectableAPI(), source_name, pxr::UsdShadeAttributeType::Output);
255 shader.CreateInput(input_spec.input_name, input_spec.input_type)
256 .ConnectToSource(source_info);
257
258 set_normal_texture_range(usd_shader, input_spec);
259
260 /* Export the texture, if necessary. */
261 if (usd_export_context.export_params.export_textures) {
262 export_texture(usd_export_context, input_node);
263 }
264
265 /* Scale-Bias processing.
266 * Ordinary: If a Vector Math node was detected ahead of the texture node, and it has
267 * the correct type, NODE_VECTOR_MATH_MULTIPLY_ADD, assume it's meant to be
268 * used for scale-bias.
269 * Displacement: The scale-bias values come from the Midlevel and Scale sockets.
270 */
271 if (input_spec.input_name != usdtokens::displacement) {
273 if (scale_link) {
274 bNode *vector_math_node = scale_link->fromnode;
275 if (vector_math_node->custom1 == NODE_VECTOR_MATH_MULTIPLY_ADD) {
276 /* Attempt one more traversal in case the current node is not the
277 * correct NODE_VECTOR_MATH_MULTIPLY_ADD (see code in usd_reader_material). */
278 bNodeSocket *sock_current = bke::node_find_socket(
279 *vector_math_node, SOCK_IN, "Vector");
280 bNodeLink *temp_link = traverse_channel(sock_current, SH_NODE_VECTOR_MATH);
281 if (temp_link && temp_link->fromnode->custom1 == NODE_VECTOR_MATH_MULTIPLY_ADD) {
282 vector_math_node = temp_link->fromnode;
283 }
284
286 *vector_math_node, SOCK_IN, "Vector_001");
288 *vector_math_node, SOCK_IN, "Vector_002");
289 const float *scale_value =
290 static_cast<bNodeSocketValueVector *>(sock_scale->default_value)->value;
291 const float *bias_value =
292 static_cast<bNodeSocketValueVector *>(sock_bias->default_value)->value;
293
294 const pxr::GfVec4f scale(scale_value[0], scale_value[1], scale_value[2], 1.0f);
295 const pxr::GfVec4f bias(bias_value[0], bias_value[1], bias_value[2], 0.0f);
296 set_scale_bias(usd_shader, scale, bias);
297 }
298 }
299 }
300 else {
301 const bNodeSocket *sock_midlevel = bke::node_find_socket(*node, SOCK_IN, "Midlevel");
302 const bNodeSocket *sock_scale = bke::node_find_socket(*node, SOCK_IN, "Scale");
303 const float midlevel_value =
304 sock_midlevel->default_value_typed<bNodeSocketValueFloat>()->value;
305 const float scale_value = sock_scale->default_value_typed<bNodeSocketValueFloat>()->value;
306
307 const float adjusted_bias = -midlevel_value * scale_value;
308 const pxr::GfVec4f scale(scale_value, scale_value, scale_value, 1.0f);
309 const pxr::GfVec4f bias(adjusted_bias, adjusted_bias, adjusted_bias, 0.0f);
310 set_scale_bias(usd_shader, scale, bias);
311 }
312
313 /* Look for a connected uvmap node. */
314 if (bNodeSocket *socket = bke::node_find_socket(*input_node, SOCK_IN, "Vector")) {
315 if (pxr::UsdShadeInput st_input = usd_shader.CreateInput(usdtokens::st,
316 pxr::SdfValueTypeNames->Float2))
317 {
319 usd_export_context, socket, usd_material, st_input, active_uvmap_name, reports);
320 }
321 }
322
323 /* Set opacityThreshold if an alpha cutout is used. */
324 if (input_spec.input_name == usdtokens::opacity) {
325 float threshold = 0.0f;
326
327 /* The immediate upstream node should either be a Math Round or a Math 1-minus. */
328 bNodeLink *math_link = traverse_channel(sock, SH_NODE_MATH);
329 if (math_link && math_link->fromnode) {
330 bNode *math_node = math_link->fromnode;
331
332 if (math_node->custom1 == NODE_MATH_ROUND) {
333 threshold = 0.5f;
334 }
335 else if (math_node->custom1 == NODE_MATH_SUBTRACT) {
336 /* If this is the 1-minus node, we need to search upstream to find the less-than. */
337 bNodeSocket *math_sock = blender::bke::node_find_socket(*math_node, SOCK_IN, "Value");
338 if (((bNodeSocketValueFloat *)math_sock->default_value)->value == 1.0f) {
339 math_sock = blender::bke::node_find_socket(*math_node, SOCK_IN, "Value_001");
340 math_link = traverse_channel(math_sock, SH_NODE_MATH);
341 if (math_link && math_link->fromnode) {
342 math_node = math_link->fromnode;
343
344 if (math_node->custom1 == NODE_MATH_LESS_THAN) {
345 /* We found the upstream less-than with the threshold value. */
347 *math_node, SOCK_IN, "Value_001");
348 threshold = ((bNodeSocketValueFloat *)threshold_sock->default_value)->value;
349 }
350 }
351 }
352 }
353 }
354
355 if (threshold > 0.0f) {
356 pxr::UsdShadeInput opacity_threshold_input = shader.CreateInput(
357 usdtokens::opacityThreshold, pxr::SdfValueTypeNames->Float);
358 opacity_threshold_input.GetAttr().Set(pxr::VtValue(threshold));
359 }
360 }
361
362 processed = true;
363 }
364
365 if (processed) {
366 continue;
367 }
368
369 /* No upstream Image was found. Check for an Attribute node instead */
370 input_link = traverse_channel(sock, SH_NODE_ATTRIBUTE);
371 if (input_link) {
372 const bNode *attr_node = input_link->fromnode;
373 const NodeShaderAttribute *storage = (NodeShaderAttribute *)attr_node->storage;
374
375 if (storage->type == SHD_ATTRIBUTE_GEOMETRY) {
376 pxr::SdfValueTypeName output_type;
377 pxr::UsdShadeShader usd_shader;
378 if (STREQ(input_link->fromsock->identifier, "Color")) {
379 output_type = pxr::SdfValueTypeNames->Float3;
380 usd_shader = create_primvar_reader_shader(
381 usd_export_context, usd_material, usdtokens::primvar_float3, attr_node);
382 }
383 else if (STREQ(input_link->fromsock->identifier, "Vector")) {
384 output_type = pxr::SdfValueTypeNames->Float3;
385 usd_shader = create_primvar_reader_shader(
386 usd_export_context, usd_material, usdtokens::primvar_vector, attr_node);
387 }
388 else if (STREQ(input_link->fromsock->identifier, "Fac")) {
389 output_type = pxr::SdfValueTypeNames->Float;
390 usd_shader = create_primvar_reader_shader(
391 usd_export_context, usd_material, usdtokens::primvar_float, attr_node);
392 }
393
394 std::string attr_name = make_safe_name(storage->name,
395 usd_export_context.export_params.allow_unicode);
396 usd_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->String).Set(attr_name);
397
398 pxr::UsdShadeConnectionSourceInfo source_info(usd_shader.ConnectableAPI(),
400 pxr::UsdShadeAttributeType::Output,
401 output_type);
402 shader.CreateInput(input_spec.input_name, input_spec.input_type)
403 .ConnectToSource(source_info);
404
405 processed = true;
406 }
407 }
408
409 if (processed) {
410 continue;
411 }
412
413 /* No upstream nodes, just set a default constant. */
414 if (input_spec.set_default_value) {
415 switch (sock->type) {
416 case SOCK_FLOAT: {
418 shader, input_spec, sock->default_value, input_scale);
419 } break;
420 case SOCK_VECTOR: {
422 shader, input_spec, sock->default_value, input_scale);
423 } break;
424 case SOCK_RGBA: {
426 shader, input_spec, sock->default_value, input_scale);
427 } break;
428 default:
429 break;
430 }
431 }
432 }
433}
434
435static void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
436 Material *material,
437 pxr::UsdShadeMaterial &usd_material,
438 const std::string &active_uvmap_name,
439 ReportList *reports)
440{
441 if (!material) {
442 return;
443 }
444
445 /* We only handle the first instance of either principled or
446 * diffuse bsdf nodes in the material's node tree, because
447 * USD Preview Surface has no concept of layering materials. */
448 bNode *surface_node = find_bsdf_node(material);
449 if (!surface_node) {
450 return;
451 }
452
453 pxr::UsdShadeShader preview_surface = create_usd_preview_shader(
454 usd_export_context, usd_material, surface_node);
455
456 /* Handle the primary "surface" output. */
458 usd_export_context, usd_material, preview_surface, surface_node, active_uvmap_name, reports);
459
460 /* Handle the "displacement" output if it meets our requirements. */
461 if (bNode *displacement_node = find_displacement_node(material)) {
462 if (displacement_node->custom1 != SHD_SPACE_OBJECT) {
463 CLOG_WARN(&LOG,
464 "Skipping displacement. Only Object Space displacement is supported by the "
465 "UsdPreviewSurface.");
466 return;
467 }
468
469 bNodeSocket *sock_mid = bke::node_find_socket(*displacement_node, SOCK_IN, "Midlevel");
470 bNodeSocket *sock_scale = bke::node_find_socket(*displacement_node, SOCK_IN, "Scale");
471 if (sock_mid->link || sock_scale->link) {
472 CLOG_WARN(&LOG, "Skipping displacement. Midlevel and Scale must be constants.");
473 return;
474 }
475
476 usd_material.CreateDisplacementOutput().ConnectToSource(preview_surface.ConnectableAPI(),
478
479 bNodeSocket *sock_height = bke::node_find_socket(*displacement_node, SOCK_IN, "Height");
480 if (sock_height->link) {
481 process_inputs(usd_export_context,
482 usd_material,
483 preview_surface,
484 displacement_node,
485 active_uvmap_name,
486 reports);
487 }
488 else {
489 /* The Height itself was also a constant. Odd but still valid. As there's only 1 value that
490 * can be written to USD, this will be a lossy conversion upon reading back in. The reader
491 * will calculate the node's parameters assuming default values for Midlevel and Scale. */
492 const float mid_value = sock_mid->default_value_typed<bNodeSocketValueFloat>()->value;
493 const float scale_value = sock_scale->default_value_typed<bNodeSocketValueFloat>()->value;
494 const float height_value = sock_height->default_value_typed<bNodeSocketValueFloat>()->value;
495 const float displacement_value = (height_value - mid_value) * scale_value;
496 const InputSpec &spec = preview_surface_input_map().lookup("Height");
497 preview_surface.CreateInput(spec.input_name, spec.input_type).Set(displacement_value);
498 }
499 }
500}
501
502void set_normal_texture_range(pxr::UsdShadeShader &usd_shader, const InputSpec &input_spec)
503{
504 /* Set the scale and bias for normal map textures
505 * The USD spec requires them to be within the -1 to 1 space. */
506
507 /* Only run if this input_spec is for a normal. */
508 if (input_spec.input_name != usdtokens::normal) {
509 return;
510 }
511
512 /* Make sure this is a texture shader prim. */
513 pxr::TfToken shader_id;
514 if (!usd_shader.GetIdAttr().Get(&shader_id) || shader_id != usdtokens::uv_texture) {
515 return;
516 }
517
518 /* We should only be setting this if the colorspace is raw. sRGB will not map the same. */
519 pxr::TfToken colorspace;
520 auto colorspace_attr = usd_shader.GetInput(usdtokens::sourceColorSpace);
521 if (!colorspace_attr || !colorspace_attr.Get(&colorspace) || colorspace != usdtokens::raw) {
522 return;
523 }
524
525 /* Get or Create the scale attribute and set it. */
526 auto scale_attr = usd_shader.GetInput(usdtokens::scale);
527 if (!scale_attr) {
528 scale_attr = usd_shader.CreateInput(usdtokens::scale, pxr::SdfValueTypeNames->Float4);
529 }
530 scale_attr.Set(pxr::GfVec4f(2.0f, 2.0f, 2.0f, 2.0f));
531
532 /* Get or Create the bias attribute and set it. */
533 auto bias_attr = usd_shader.GetInput(usdtokens::bias);
534 if (!bias_attr) {
535 bias_attr = usd_shader.CreateInput(usdtokens::bias, pxr::SdfValueTypeNames->Float4);
536 }
537 bias_attr.Set(pxr::GfVec4f(-1.0f, -1.0f, -1.0f, -1.0f));
538}
539
540/* Create USD Shade Material network from Blender viewport display settings. */
541static void create_usd_viewport_material(const USDExporterContext &usd_export_context,
542 const Material *material,
543 const pxr::UsdShadeMaterial &usd_material)
544{
545 /* Construct the shader. */
546 pxr::SdfPath shader_path = usd_material.GetPath().AppendChild(usdtokens::preview_shader);
547 pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
548
549 shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
550 shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
551 .Set(pxr::GfVec3f(material->r, material->g, material->b));
552 shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
553 shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
554
555 /* Connect the shader and the material together. */
556 usd_material.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), usdtokens::surface);
557}
558
559/* Return USD Preview Surface input map singleton. */
561{
562 static const InputSpecMap input_map = []() {
563 InputSpecMap map;
564 map.add_new("Base Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f, true});
565 map.add_new("Emission Color",
566 {usdtokens::emissive_color, pxr::SdfValueTypeNames->Color3f, true});
567 map.add_new("Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f, true});
568 map.add_new("Roughness", {usdtokens::roughness, pxr::SdfValueTypeNames->Float, true});
569 map.add_new("Metallic", {usdtokens::metallic, pxr::SdfValueTypeNames->Float, true});
570 map.add_new("Specular IOR Level", {usdtokens::specular, pxr::SdfValueTypeNames->Float, true});
571 map.add_new("Alpha", {usdtokens::opacity, pxr::SdfValueTypeNames->Float, true});
572 map.add_new("IOR", {usdtokens::ior, pxr::SdfValueTypeNames->Float, true});
573
574 /* Note that for the Normal input set_default_value is false. */
575 map.add_new("Normal", {usdtokens::normal, pxr::SdfValueTypeNames->Normal3f, false});
576 map.add_new("Coat Weight", {usdtokens::clearcoat, pxr::SdfValueTypeNames->Float, true});
577 map.add_new("Coat Roughness",
578 {usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float, true});
579 map.add_new("Height", {usdtokens::displacement, pxr::SdfValueTypeNames->Float, false});
580 return map;
581 }();
582
583 return input_map;
584}
585
586/* Find the UVMAP node input to the given texture image node and convert it
587 * to a USD primvar reader shader. If no UVMAP node is found, create a primvar
588 * reader for the given default uv set. The primvar reader will be attached to
589 * the 'st' input of the given USD texture shader. */
590static void create_uvmap_shader(const USDExporterContext &usd_export_context,
591 const bNodeLink *uvmap_link,
592 const pxr::UsdShadeMaterial &usd_material,
593 const pxr::UsdShadeInput &usd_input,
594 const std::string &active_uvmap_name,
595 ReportList *reports)
596
597{
598 const bNode *uv_node = (uvmap_link && uvmap_link->fromnode ? uvmap_link->fromnode : nullptr);
599
600 BLI_assert(!uv_node || uv_node->type_legacy == SH_NODE_UVMAP);
601
602 const StringRef shader_name = uv_node ? uv_node->name : "uvmap";
603
604 pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
605 usd_export_context, usd_material, shader_name, SH_NODE_UVMAP);
606
607 if (!uv_shader) {
608 BKE_reportf(reports, RPT_WARNING, "%s: Couldn't create USD shader for UV map", __func__);
609 return;
610 }
611
612 std::string uv_name = active_uvmap_name;
613 if (uv_node && uv_node->storage) {
614 const NodeShaderUVMap *shader_uv_map = static_cast<const NodeShaderUVMap *>(uv_node->storage);
615 uv_name = shader_uv_map->uv_map;
616 }
617 if (usd_export_context.export_params.rename_uvmaps && uv_name == active_uvmap_name) {
618 uv_name = usdtokens::st;
619 }
620 /* We need to make valid, same as was done when exporting UV primvar. */
621 uv_name = make_safe_name(uv_name, usd_export_context.export_params.allow_unicode);
622
623 uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->String).Set(uv_name);
624 usd_input.ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
625}
626
627static void create_transform2d_shader(const USDExporterContext &usd_export_context,
628 bNodeLink *mapping_link,
629 pxr::UsdShadeMaterial &usd_material,
630 pxr::UsdShadeInput &usd_input,
631 const std::string &uvmap_name,
632 ReportList *reports)
633
634{
635 bNode *mapping_node = (mapping_link && mapping_link->fromnode ? mapping_link->fromnode :
636 nullptr);
637
638 BLI_assert(mapping_node && mapping_node->type_legacy == SH_NODE_MAPPING);
639
640 if (!mapping_node) {
641 return;
642 }
643
644 if (mapping_node->custom1 != TEXMAP_TYPE_POINT) {
645 if (bNodeSocket *socket = bke::node_find_socket(*mapping_node, SOCK_IN, "Vector")) {
646 create_uv_input(usd_export_context, socket, usd_material, usd_input, uvmap_name, reports);
647 }
648 return;
649 }
650
651 pxr::UsdShadeShader transform2d_shader = create_usd_preview_shader(
652 usd_export_context, usd_material, mapping_node);
653
654 if (!transform2d_shader) {
655 BKE_reportf(reports, RPT_WARNING, "%s: Couldn't create USD shader for mapping node", __func__);
656 return;
657 }
658
659 usd_input.ConnectToSource(transform2d_shader.ConnectableAPI(), usdtokens::result);
660
661 float scale[3] = {1.0f, 1.0f, 1.0f};
662 float loc[3] = {0.0f, 0.0f, 0.0f};
663 float rot[3] = {0.0f, 0.0f, 0.0f};
664
665 if (bNodeSocket *scale_socket = bke::node_find_socket(*mapping_node, SOCK_IN, "Scale")) {
666 copy_v3_v3(scale, ((bNodeSocketValueVector *)scale_socket->default_value)->value);
667 /* Ignore the Z scale. */
668 scale[2] = 1.0f;
669 }
670
671 if (bNodeSocket *loc_socket = bke::node_find_socket(*mapping_node, SOCK_IN, "Location")) {
672 copy_v3_v3(loc, ((bNodeSocketValueVector *)loc_socket->default_value)->value);
673 /* Ignore the Z translation. */
674 loc[2] = 0.0f;
675 }
676
677 if (bNodeSocket *rot_socket = bke::node_find_socket(*mapping_node, SOCK_IN, "Rotation")) {
678 copy_v3_v3(rot, ((bNodeSocketValueVector *)rot_socket->default_value)->value);
679 /* Ignore the X and Y rotations. */
680 rot[0] = 0.0f;
681 rot[1] = 0.0f;
682 }
683
684 if (pxr::UsdShadeInput scale_input = transform2d_shader.CreateInput(
685 usdtokens::scale, pxr::SdfValueTypeNames->Float2))
686 {
687 pxr::GfVec2f scale_val(scale[0], scale[1]);
688 scale_input.Set(scale_val);
689 }
690
691 if (pxr::UsdShadeInput trans_input = transform2d_shader.CreateInput(
692 usdtokens::translation, pxr::SdfValueTypeNames->Float2))
693 {
694 pxr::GfVec2f trans_val(loc[0], loc[1]);
695 trans_input.Set(trans_val);
696 }
697
698 if (pxr::UsdShadeInput rot_input = transform2d_shader.CreateInput(usdtokens::rotation,
699 pxr::SdfValueTypeNames->Float))
700 {
701 /* Convert to degrees. */
702 float rot_val = rot[2] * 180.0f / M_PI;
703 rot_input.Set(rot_val);
704 }
705
706 if (bNodeSocket *socket = bke::node_find_socket(*mapping_node, SOCK_IN, "Vector")) {
707 if (pxr::UsdShadeInput in_input = transform2d_shader.CreateInput(
708 usdtokens::in, pxr::SdfValueTypeNames->Float2))
709 {
710 create_uv_input(usd_export_context, socket, usd_material, in_input, uvmap_name, reports);
711 }
712 }
713}
714
715static void create_uv_input(const USDExporterContext &usd_export_context,
716 bNodeSocket *input_socket,
717 pxr::UsdShadeMaterial &usd_material,
718 pxr::UsdShadeInput &usd_input,
719 const std::string &active_uvmap_name,
720 ReportList *reports)
721{
722 if (!(usd_material && usd_input)) {
723 return;
724 }
725
726 if (bNodeLink *mapping_link = traverse_channel(input_socket, SH_NODE_MAPPING)) {
727 /* Use either "st" or active UV map name from mesh, depending if it was renamed. */
728 std::string uvmap_name = (usd_export_context.export_params.rename_uvmaps) ? usdtokens::st :
729 active_uvmap_name;
731 usd_export_context, mapping_link, usd_material, usd_input, uvmap_name, reports);
732 return;
733 }
734
735 const bNodeLink *uvmap_link = traverse_channel(input_socket, SH_NODE_UVMAP);
736
737 /* Note that uvmap_link might be null, but create_uv_shader() can handle this case. */
739 usd_export_context, uvmap_link, usd_material, usd_input, active_uvmap_name, reports);
740}
741
742static bool has_generated_tiles(const Image *ima)
743{
744 bool any_generated = false;
746 if ((tile->gen_flag & IMA_GEN_TILE) != 0) {
747 any_generated = true;
748 break;
749 }
750 }
751 return any_generated;
752}
753
755{
756 return has_generated_tiles(ima) || BKE_image_is_dirty(ima);
757}
758
759static bool is_packed_texture(const Image *ima)
760{
761 return BKE_image_has_packedfile(ima);
762}
763
764/* Generate a file name for an in-memory image that doesn't have a
765 * filepath already defined. */
766static std::string get_in_memory_texture_filename(Image *ima)
767{
768 bool is_dirty = BKE_image_is_dirty(ima);
769 bool is_generated = has_generated_tiles(ima);
770 bool is_packed = BKE_image_has_packedfile(ima);
771 bool is_tiled = ima->source == IMA_SRC_TILED;
772 if (!(is_generated || is_dirty || is_packed)) {
773 return "";
774 }
775
776 /* Determine the correct file extension from the image format. */
777 ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
778 if (!imbuf) {
779 return "";
780 }
781
782 ImageFormatData imageFormat;
783 BKE_image_format_from_imbuf(&imageFormat, imbuf);
784 BKE_image_release_ibuf(ima, imbuf, nullptr);
785
786 char file_name[FILE_MAX];
787
788 /* NOTE: Any changes in packed filepath handling here should be considered alongside potential
789 * changes in `export_packed_texture`. The file name returned needs to match. */
790 if (is_packed && ima->filepath[0] != '\0') {
792 return file_name;
793 }
794
795 /* Use the image name for the file name. */
796 STRNCPY(file_name, ima->id.name + 2);
797
798 BKE_image_path_ext_from_imformat_ensure(file_name, sizeof(file_name), &imageFormat);
799
800 if (is_tiled && !BKE_image_is_filename_tokenized(file_name)) {
801 /* Ensure that the UDIM tag is in. */
802 char file_body[FILE_MAX];
803 char file_ext[FILE_MAX];
804 BLI_string_split_suffix(file_name, FILE_MAX, file_body, file_ext);
805 SNPRINTF(file_name, "%s.<UDIM>%s", file_body, file_ext);
806 }
807
808 return file_name;
809}
810
812 const std::string &export_dir,
813 const char image_abs_path[FILE_MAX],
814 const char file_name[FILE_MAX],
815 const bool allow_overwrite,
816 ReportList *reports)
817{
818 ImageFormatData imageFormat;
819 BKE_image_format_from_imbuf(&imageFormat, imbuf);
820
821 char export_path[FILE_MAX];
822 BLI_path_join(export_path, FILE_MAX, export_dir.c_str(), file_name);
823
824 if (!allow_overwrite && BLI_exists(export_path)) {
825 return;
826 }
827
829 /* As a precaution, don't overwrite the original path. */
830 return;
831 }
832
833 CLOG_DEBUG(&LOG, "Exporting in-memory texture to '%s'", export_path);
834
835 if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == false) {
837 reports, RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path);
838 }
839}
840
842 const std::string &export_dir,
843 const bool allow_overwrite,
844 ReportList *reports)
845{
846 char image_abs_path[FILE_MAX] = {};
847
848 char file_name[FILE_MAX];
849 if (ima->filepath[0]) {
852 }
853 else {
854 /* Use the image name for the file name. */
855 std::string file = get_in_memory_texture_filename(ima);
856 STRNCPY(file_name, file.c_str());
857 }
858
859 /* This image in its current state only exists in Blender memory.
860 * So we have to export it. The export will keep the image state intact,
861 * so the exported file will not be associated with the image. */
862 if (ima->source != IMA_SRC_TILED) {
863 ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
864 if (!imbuf) {
865 return;
866 }
867
868 export_in_memory_imbuf(imbuf, export_dir, image_abs_path, file_name, allow_overwrite, reports);
869 BKE_image_release_ibuf(ima, imbuf, nullptr);
870 }
871 else {
872 eUDIM_TILE_FORMAT tile_format;
873 char *udim_pattern = nullptr;
874 udim_pattern = BKE_image_get_tile_strformat(file_name, &tile_format);
875 if (tile_format == UDIM_TILE_FORMAT_NONE) {
876 return;
877 }
878
879 /* Save all the tiles. */
880 ImageUser iuser{};
882 char tile_filepath[FILE_MAX];
884 tile_filepath, udim_pattern, tile_format, tile->tile_number);
885 iuser.tile = tile->tile_number;
886
887 ImBuf *imbuf = BKE_image_acquire_ibuf(ima, &iuser, nullptr);
888 if (!imbuf) {
889 continue;
890 }
891
893 imbuf, export_dir, image_abs_path, tile_filepath, allow_overwrite, reports);
894 BKE_image_release_ibuf(ima, imbuf, nullptr);
895 }
896 MEM_freeN(udim_pattern);
897 }
898}
899
901 const std::string &export_dir,
902 const bool allow_overwrite,
903 ReportList *reports)
904{
906 if (!imapf || !imapf->packedfile || !imapf->packedfile->data || !imapf->packedfile->size) {
907 continue;
908 }
909
910 const PackedFile *pf = imapf->packedfile;
911
912 char image_abs_path[FILE_MAX] = {};
913 char file_name[FILE_MAX];
914
915 if (imapf->filepath[0] != '\0') {
916 /* Get the file name from the original path. */
917 /* Make absolute source path. */
918 STRNCPY(image_abs_path, imapf->filepath);
920 image_abs_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id), false /* Not for import */);
922 }
923 else {
924 /* The following logic is taken from unpack_generate_paths() in packedFile.cc. */
925
926 /* NOTE: we generally do not have any real way to re-create extension out of data. */
927 const size_t len = STRNCPY_RLEN(file_name, ima->id.name + 2);
928
929 /* For images ensure that the temporary filename contains tile number information as well as
930 * a file extension based on the file magic. */
931
932 enum eImbFileType ftype = eImbFileType(
933 IMB_test_image_type_from_memory(static_cast<const uchar *>(pf->data), pf->size));
934 if (ima->source == IMA_SRC_TILED) {
935 char tile_number[6];
936 SNPRINTF(tile_number, ".%d", imapf->tile_number);
937 BLI_strncpy(file_name + len, tile_number, sizeof(file_name) - len);
938 }
939 if (ftype != IMB_FTYPE_NONE) {
940 const int imtype = BKE_ftype_to_imtype(ftype, nullptr);
941 BKE_image_path_ext_from_imtype_ensure(file_name, sizeof(file_name), imtype);
942 }
943 }
944
945 char export_path_buf[FILE_MAX];
946 BLI_path_join(export_path_buf, FILE_MAX, export_dir.c_str(), file_name);
947 BLI_string_replace_char(export_path_buf, '\\', '/');
948
949 const std::string export_path(export_path_buf);
950 if (!allow_overwrite && asset_exists(export_path)) {
951 return;
952 }
953
954 const std::string image_path(image_abs_path);
955 if (paths_equal(export_path, image_path) && asset_exists(image_path)) {
956 /* As a precaution, don't overwrite the original path. */
957 return;
958 }
959
960 CLOG_DEBUG(&LOG, "Exporting packed texture to '%s'", export_path.c_str());
961
962 write_to_path(pf->data, pf->size, export_path, reports);
963 }
964}
965
966/* Get the absolute filepath of the given image. Assumes
967 * r_path result array is of length FILE_MAX. */
968static void get_absolute_path(const Image *ima, char *r_path)
969{
970 /* Make absolute source path. */
971 BLI_strncpy(r_path, ima->filepath, FILE_MAX);
973 BLI_path_normalize(r_path);
974}
975
976static pxr::TfToken get_node_tex_image_color_space(const bNode *node)
977{
978 if (!node->id) {
979 return pxr::TfToken();
980 }
981
982 const Image *ima = reinterpret_cast<const Image *>(node->id);
983
985 return usdtokens::raw;
986 }
988 return usdtokens::sRGB;
989 }
990
991 return pxr::TfToken();
992}
993
994static pxr::TfToken get_node_tex_image_wrap(const bNode *node)
995{
996 if (node->type_legacy != SH_NODE_TEX_IMAGE) {
997 return pxr::TfToken();
998 }
999
1000 if (node->storage == nullptr) {
1001 return pxr::TfToken();
1002 }
1003
1004 const NodeTexImage *tex_image = static_cast<const NodeTexImage *>(node->storage);
1005
1006 pxr::TfToken wrap;
1007
1008 switch (tex_image->extension) {
1011 break;
1014 break;
1017 break;
1020 break;
1021 }
1022
1023 return wrap;
1024}
1025
1026/* Search the upstream node links connected to the given socket and return the first occurrence
1027 * of the link connected to the node of the given type. Return null if no such link was found.
1028 * The 'fromnode' and 'fromsock' members of the returned link are guaranteed to be not null. */
1029static bNodeLink *traverse_channel(bNodeSocket *input, const short target_type)
1030{
1031 if (!(input->link && input->link->fromnode && input->link->fromsock)) {
1032 return nullptr;
1033 }
1034
1035 bNode *linked_node = input->link->fromnode;
1036 if (linked_node->type_legacy == target_type) {
1037 /* Return match. */
1038 return input->link;
1039 }
1040
1041 /* Recursively traverse the linked node's sockets. */
1042 LISTBASE_FOREACH (bNodeSocket *, sock, &linked_node->inputs) {
1043 if (bNodeLink *found_link = traverse_channel(sock, target_type)) {
1044 return found_link;
1045 }
1046 }
1047
1048 return nullptr;
1049}
1050
1051/* Returns the first occurrence of a principled BSDF or a diffuse BSDF node found in the given
1052 * material's node tree. Returns null if no instance of either type was found. */
1054{
1055 for (bNode *node : material->nodetree->all_nodes()) {
1056 if (ELEM(node->type_legacy, SH_NODE_BSDF_PRINCIPLED, SH_NODE_BSDF_DIFFUSE)) {
1057 return node;
1058 }
1059 }
1060
1061 return nullptr;
1062}
1063
1070{
1071 for (bNode *node : material->nodetree->all_nodes()) {
1072 if (node->type_legacy == SH_NODE_DISPLACEMENT) {
1073 return node;
1074 }
1075 }
1076
1077 return nullptr;
1078}
1079
1080/* Creates a USD Preview Surface shader based on the given cycles node name and type. */
1081static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
1082 const pxr::UsdShadeMaterial &material,
1083 const StringRef name,
1084 const int type)
1085{
1086 pxr::SdfPath shader_path = material.GetPath().AppendChild(
1087 pxr::TfToken(make_safe_name(name, usd_export_context.export_params.allow_unicode)));
1088 pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
1089
1090 switch (type) {
1091 case SH_NODE_TEX_IMAGE: {
1092 shader.CreateIdAttr(pxr::VtValue(usdtokens::uv_texture));
1093 break;
1094 }
1095 case SH_NODE_MAPPING: {
1096 shader.CreateIdAttr(pxr::VtValue(usdtokens::UsdTransform2d));
1097 break;
1098 }
1099 case SH_NODE_TEX_COORD:
1100 case SH_NODE_UVMAP: {
1101 shader.CreateIdAttr(pxr::VtValue(usdtokens::primvar_float2));
1102 break;
1103 }
1106 shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
1107 material.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), usdtokens::surface);
1108 break;
1109 }
1110
1111 default:
1112 break;
1113 }
1114
1115 return shader;
1116}
1117
1118/* Creates a USD Preview Surface shader based on the given cycles shading node.
1119 * Due to the limited nodes in the USD Preview Surface specification, only the following nodes
1120 * are supported:
1121 * - UVMap
1122 * - Texture Coordinate
1123 * - Image Texture
1124 * - Principled BSDF
1125 * More may be added in the future.
1126 */
1127static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
1128 const pxr::UsdShadeMaterial &material,
1129 bNode *node)
1130{
1131 pxr::UsdShadeShader shader = create_usd_preview_shader(
1132 usd_export_context, material, node->name, node->type_legacy);
1133
1134 if (node->type_legacy != SH_NODE_TEX_IMAGE) {
1135 return shader;
1136 }
1137
1138 /* For texture image nodes we set the image path and color space. */
1139 std::string imagePath = get_tex_image_asset_filepath(usd_export_context, node);
1140 if (!imagePath.empty()) {
1141 shader.CreateInput(usdtokens::file, pxr::SdfValueTypeNames->Asset)
1142 .Set(pxr::SdfAssetPath(imagePath));
1143 }
1144
1145 pxr::TfToken colorSpace = get_node_tex_image_color_space(node);
1146 if (!colorSpace.IsEmpty()) {
1147 shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token).Set(colorSpace);
1148 }
1149
1150 pxr::TfToken wrap = get_node_tex_image_wrap(node);
1151 if (!wrap.IsEmpty()) {
1152 shader.CreateInput(usdtokens::wrapS, pxr::SdfValueTypeNames->Token).Set(wrap);
1153 shader.CreateInput(usdtokens::wrapT, pxr::SdfValueTypeNames->Token).Set(wrap);
1154 }
1155
1156 return shader;
1157}
1158
1159static pxr::UsdShadeShader create_primvar_reader_shader(
1160 const USDExporterContext &usd_export_context,
1161 const pxr::UsdShadeMaterial &material,
1162 const pxr::TfToken &primvar_type,
1163 const bNode *node)
1164{
1165 pxr::SdfPath shader_path = material.GetPath().AppendChild(
1166 pxr::TfToken(make_safe_name(node->name, usd_export_context.export_params.allow_unicode)));
1167 pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
1168
1169 shader.CreateIdAttr(pxr::VtValue(primvar_type));
1170 return shader;
1171}
1172
1173static std::string get_tex_image_asset_filepath(const Image *ima)
1174{
1175 char filepath[FILE_MAX];
1176 get_absolute_path(ima, filepath);
1177
1178 return std::string(filepath);
1179}
1180
1181static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context,
1182 bNode *node)
1183{
1185 node, usd_export_context.stage, usd_export_context.export_params);
1186}
1187
1189 const pxr::UsdStageRefPtr stage,
1190 const USDExportParams &export_params)
1191{
1192 std::string stage_path = stage->GetRootLayer()->GetRealPath();
1193
1194 if (!ima) {
1195 return "";
1196 }
1197
1198 std::string path;
1199
1200 if (is_in_memory_texture(ima)) {
1202 }
1203 else {
1204 if (!export_params.export_textures && export_params.use_original_paths) {
1205 path = get_usd_source_path(&ima->id);
1206 }
1207
1208 if (is_packed_texture(ima)) {
1209 if (path.empty()) {
1210 char file_name[FILE_MAX];
1212 BLI_path_join(file_name, FILE_MAX, ".", "textures", path.c_str());
1213 path = file_name;
1214 }
1215 }
1216 else if (ima->filepath[0] != '\0') {
1217 /* Get absolute path. */
1218 path = get_tex_image_asset_filepath(ima);
1219 }
1220 }
1221
1222 return get_tex_image_asset_filepath(path, stage_path, export_params);
1223}
1224
1225std::string get_tex_image_asset_filepath(const std::string &path,
1226 const std::string &stage_path,
1227 const USDExportParams &export_params)
1228{
1229 if (path.empty()) {
1230 return path;
1231 }
1232
1233 if (export_params.export_textures) {
1234 /* The texture is exported to a 'textures' directory next to the
1235 * USD root layer. */
1236
1237 char exp_path[FILE_MAX];
1238 char file_path[FILE_MAX];
1239 BLI_path_split_file_part(path.c_str(), file_path, FILE_MAX);
1240
1241 if (export_params.relative_paths) {
1242 BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path);
1243 }
1244 else {
1245 /* Create absolute path in the textures directory. */
1246 if (stage_path.empty()) {
1247 return path;
1248 }
1249
1250 char dir_path[FILE_MAX];
1251 BLI_path_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
1252 BLI_path_join(exp_path, FILE_MAX, dir_path, "textures", file_path);
1253 }
1254 BLI_string_replace_char(exp_path, '\\', '/');
1255 return exp_path;
1256 }
1257
1258 if (export_params.relative_paths) {
1259 /* Get the path relative to the USD. */
1260 if (stage_path.empty()) {
1261 return path;
1262 }
1263
1264 std::string rel_path = get_relative_path(path, stage_path);
1265 if (rel_path.empty()) {
1266 return path;
1267 }
1268 return rel_path;
1269 }
1270
1271 return path;
1272}
1273
1275 const pxr::UsdStageRefPtr stage,
1276 const USDExportParams &export_params)
1277{
1278 Image *ima = reinterpret_cast<Image *>(node->id);
1279 return get_tex_image_asset_filepath(ima, stage, export_params);
1280}
1281
1282/* If the given image is tiled, copy the image tiles to the given
1283 * destination directory. */
1285 const std::string &dest_dir,
1286 const bool allow_overwrite,
1287 ReportList *reports)
1288{
1289 char src_path[FILE_MAX];
1290 get_absolute_path(ima, src_path);
1291
1292 eUDIM_TILE_FORMAT tile_format;
1293 char *udim_pattern = BKE_image_get_tile_strformat(src_path, &tile_format);
1294
1295 /* Only <UDIM> tile formats are supported by USD right now. */
1296 if (tile_format != UDIM_TILE_FORMAT_UDIM) {
1297 CLOG_WARN(&LOG, "Unsupported tile format for '%s'", src_path);
1298 MEM_SAFE_FREE(udim_pattern);
1299 return;
1300 }
1301
1302 /* Copy all tiles. */
1303 LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
1304 char src_tile_path[FILE_MAX];
1306 src_tile_path, udim_pattern, tile_format, tile->tile_number);
1307
1308 char dest_filename[FILE_MAXFILE];
1309 BLI_path_split_file_part(src_tile_path, dest_filename, sizeof(dest_filename));
1310
1311 char dest_tile_path[FILE_MAX];
1312 BLI_path_join(dest_tile_path, FILE_MAX, dest_dir.c_str(), dest_filename);
1313
1314 if (!allow_overwrite && BLI_exists(dest_tile_path)) {
1315 continue;
1316 }
1317
1318 if (BLI_path_cmp_normalized(src_tile_path, dest_tile_path) == 0) {
1319 /* Source and destination paths are the same, don't copy. */
1320 continue;
1321 }
1322
1323 CLOG_DEBUG(&LOG, "Copying texture tile from '%s' to '%s'", src_tile_path, dest_tile_path);
1324
1325 /* Copy the file. */
1326 if (BLI_copy(src_tile_path, dest_tile_path) != 0) {
1327 BKE_reportf(reports,
1329 "USD export: could not copy texture tile from %s to %s",
1330 src_tile_path,
1331 dest_tile_path);
1332 }
1333 }
1334 MEM_SAFE_FREE(udim_pattern);
1335}
1336
1337/* Copy the given image to the destination directory. */
1338static void copy_single_file(const Image *ima,
1339 const std::string &dest_dir,
1340 const bool allow_overwrite,
1341 ReportList *reports)
1342{
1343 char source_path[FILE_MAX];
1344 get_absolute_path(ima, source_path);
1345
1346 char file_name[FILE_MAX];
1347 BLI_path_split_file_part(source_path, file_name, FILE_MAX);
1348
1349 char dest_path[FILE_MAX];
1350 BLI_path_join(dest_path, FILE_MAX, dest_dir.c_str(), file_name);
1351
1352 if (!allow_overwrite && BLI_exists(dest_path)) {
1353 return;
1354 }
1355
1356 if (BLI_path_cmp_normalized(source_path, dest_path) == 0) {
1357 /* Source and destination paths are the same, don't copy. */
1358 return;
1359 }
1360
1361 CLOG_DEBUG(&LOG, "Copying texture from '%s' to '%s'", source_path, dest_path);
1362
1363 /* Copy the file. */
1364 if (BLI_copy(source_path, dest_path) != 0) {
1365 BKE_reportf(reports,
1367 "USD export: could not copy texture from %s to %s",
1368 source_path,
1369 dest_path);
1370 }
1371}
1372
1374 const pxr::UsdStageRefPtr stage,
1375 const bool allow_overwrite,
1376 ReportList *reports)
1377{
1378 std::string dest_dir = get_export_textures_dir(stage);
1379 if (dest_dir.empty()) {
1380 CLOG_ERROR(&LOG, "Couldn't determine textures directory path");
1381 return;
1382 }
1383
1384 if (is_packed_texture(ima)) {
1385 export_packed_texture(ima, dest_dir, allow_overwrite, reports);
1386 }
1387 else if (is_in_memory_texture(ima)) {
1388 export_in_memory_texture(ima, dest_dir, allow_overwrite, reports);
1389 }
1390 else if (ima->source == IMA_SRC_TILED) {
1391 copy_tiled_textures(ima, dest_dir, allow_overwrite, reports);
1392 }
1393 else {
1394 copy_single_file(ima, dest_dir, allow_overwrite, reports);
1395 }
1396}
1397
1398/* Export the given texture node's image to a 'textures' directory in the export path.
1399 * Based on ImagesExporter::export_UV_Image() */
1400static void export_texture(const USDExporterContext &usd_export_context, bNode *node)
1401{
1402 export_texture(node,
1403 usd_export_context.stage,
1404 usd_export_context.export_params.overwrite_textures,
1405 usd_export_context.export_params.worker_status->reports);
1406}
1407
1408#ifdef WITH_MATERIALX
1409static void export_texture(const USDExporterContext &usd_export_context, Image *ima)
1410{
1411 export_texture(ima,
1412 usd_export_context.stage,
1413 usd_export_context.export_params.overwrite_textures,
1414 usd_export_context.export_params.worker_status->reports);
1415}
1416#endif
1417
1419 const pxr::UsdStageRefPtr stage,
1420 const bool allow_overwrite,
1421 ReportList *reports)
1422{
1424 return;
1425 }
1426
1427 Image *ima = reinterpret_cast<Image *>(node->id);
1428 if (!ima) {
1429 return;
1430 }
1431
1432 export_texture(ima, stage, allow_overwrite, reports);
1433}
1434
1435pxr::TfToken token_for_input(const StringRef input_name)
1436{
1437 const InputSpecMap &input_map = preview_surface_input_map();
1438 const InputSpec *spec = input_map.lookup_ptr(input_name);
1439
1440 if (spec == nullptr) {
1441 return {};
1442 }
1443
1444 return spec->input_name;
1445}
1446
1447#ifdef WITH_MATERIALX
1448/* A wrapper for the MaterialX code to re-use the standard Texture export code */
1449static std::string materialx_export_image(const USDExporterContext &usd_export_context,
1450 Main * /*main*/,
1451 Scene * /*scene*/,
1452 Image *ima,
1453 ImageUser * /*iuser*/)
1454{
1455 auto tex_path = get_tex_image_asset_filepath(
1456 ima, usd_export_context.stage, usd_export_context.export_params);
1457
1458 export_texture(usd_export_context, ima);
1459 return tex_path;
1460}
1461
1462/* Utility function to reflow connections and paths within the temporary document
1463 * to their final location in the USD document. */
1464static pxr::SdfPath reflow_materialx_paths(pxr::SdfPath input_path,
1465 pxr::SdfPath temp_path,
1466 const pxr::SdfPath &target_path,
1467 const Map<std::string, std::string> &rename_pairs)
1468{
1469
1470 const std::string &input_path_string = input_path.GetString();
1471 /* First we see if the path is in the rename_pairs,
1472 * otherwise we check if it starts with any items in the list plus a path separator (/ or .) .
1473 * Checking for the path separators, removes false positives from other prefixed elements. */
1474 const auto *value_lookup_ptr = rename_pairs.lookup_ptr(input_path_string);
1475 if (value_lookup_ptr) {
1476 input_path = pxr::SdfPath(*value_lookup_ptr);
1477 }
1478 else {
1479 for (const auto &pair : rename_pairs.items()) {
1480 if (input_path_string.length() > pair.key.length() &&
1481 pxr::TfStringStartsWith(input_path_string, pair.key) &&
1482 (input_path_string[pair.key.length()] == '/' ||
1483 input_path_string[pair.key.length()] == '.'))
1484 {
1485 input_path = input_path.ReplacePrefix(pxr::SdfPath(pair.key), pxr::SdfPath(pair.value));
1486 break;
1487 }
1488 }
1489 }
1490
1491 return input_path.ReplacePrefix(temp_path, target_path);
1492}
1493
1494/* Exports the material as a MaterialX node-graph within the USD layer. */
1495static void create_usd_materialx_material(const USDExporterContext &usd_export_context,
1496 pxr::SdfPath usd_path,
1497 Material *material,
1498 const std::string &active_uvmap_name,
1499 const pxr::UsdShadeMaterial &usd_material)
1500{
1501 blender::nodes::materialx::ExportParams export_params = {
1502 /* Output surface material node will have this name. */
1503 usd_path.GetElementString(),
1504 /* We want to re-use the same MaterialX document generation code as used by the renderer.
1505 * While the graph is traversed, we also want it to export the textures out. */
1506 (usd_export_context.export_image_fn) ?
1507 usd_export_context.export_image_fn :
1508 [usd_export_context](Main *main, Scene *scene, Image *ima, ImageUser *iuser) {
1509 return materialx_export_image(usd_export_context, main, scene, ima, iuser);
1510 },
1511 /* Active UV map name to use for default texture coordinates. */
1512 (usd_export_context.export_params.rename_uvmaps) ? "st" : active_uvmap_name,
1513 active_uvmap_name,
1514 };
1515
1516 MaterialX::DocumentPtr doc = blender::nodes::materialx::export_to_materialx(
1517 usd_export_context.depsgraph, material, export_params);
1518
1519 /* We want to merge the MaterialX graph under the same Material as the USDPreviewSurface
1520 * This allows for the same material assignment to have two levels of complexity so other
1521 * applications and renderers can easily pick which one they want.
1522 * This does mean that we need to pre-process the resulting graph so that there are no
1523 * name conflicts.
1524 * So we first gather all the existing names in this namespace to avoid that. */
1525 Set<std::string> used_names;
1526 auto material_prim = usd_material.GetPrim();
1527 for (const auto &child : material_prim.GetChildren()) {
1528 used_names.add(child.GetName().GetString());
1529 }
1530
1531 /* usdMtlx assumes a workflow where the mtlx file is referenced in,
1532 * but the resulting structure is not ideal for when the file is inlined.
1533 * Some of the issues include turning every shader input into a separate constant, which
1534 * leads to very unwieldy shader graphs in other applications. There are also extra nodes
1535 * that are only needed when referencing in the file that make editing the graph harder.
1536 * Therefore, we opt to copy just what we need over.
1537 *
1538 * To do this, we first open a temporary stage to process the structure inside */
1539
1540 auto temp_stage = pxr::UsdStage::CreateInMemory();
1541 pxr::UsdMtlxRead(doc, temp_stage, pxr::SdfPath("/root"));
1542
1543 /* Next we need to find the Material that matches this materials name */
1544 auto temp_material_path = pxr::SdfPath("/root/Materials");
1545 temp_material_path = temp_material_path.AppendChild(material_prim.GetName());
1546 auto temp_material_prim = temp_stage->GetPrimAtPath(temp_material_path);
1547 if (!temp_material_prim) {
1548 return;
1549 }
1550
1551 pxr::UsdShadeMaterial temp_material{temp_material_prim};
1552 if (!temp_material) {
1553 return;
1554 }
1555
1556 /* Copy over the MateralXConfigAPI schema and associated attribute. */
1557 pxr::UsdMtlxMaterialXConfigAPI temp_config_api{temp_material_prim};
1558 if (temp_config_api) {
1559 pxr::UsdMtlxMaterialXConfigAPI materialx_config_api = pxr::UsdMtlxMaterialXConfigAPI::Apply(
1560 material_prim);
1561 pxr::UsdAttribute temp_mtlx_version_attr = temp_config_api.GetConfigMtlxVersionAttr();
1562 pxr::VtValue mtlx_version;
1563 if (temp_mtlx_version_attr && temp_mtlx_version_attr.Get(&mtlx_version)) {
1564 materialx_config_api.CreateConfigMtlxVersionAttr(mtlx_version);
1565 }
1566 }
1567
1568 /* Once we have the material, we need to prepare for renaming any conflicts.
1569 * However, we must make sure any new names don't conflict with names in the temp stage either */
1570 Set<std::string> temp_used_names;
1571 for (const auto &child : temp_material_prim.GetChildren()) {
1572 temp_used_names.add(child.GetName().GetString());
1573 }
1574
1575 /* We loop through the top level children of the material, and make sure that the names are
1576 * unique across both the destination stage, and this temporary stage.
1577 * This is stored for later use so that we can reflow any connections */
1578 Map<std::string, std::string> rename_pairs;
1579 for (const auto &temp_material_child : temp_material_prim.GetChildren()) {
1580 uint32_t conflict_counter = 0;
1581 const std::string &name = temp_material_child.GetName().GetString();
1582 std::string target_name = name;
1583 while (used_names.contains(target_name)) {
1584 ++conflict_counter;
1585 target_name = name + "_mtlx" + std::to_string(conflict_counter);
1586
1587 while (temp_used_names.contains(target_name)) {
1588 ++conflict_counter;
1589 target_name = name + "_mtlx" + std::to_string(conflict_counter);
1590 }
1591 }
1592
1593 if (conflict_counter == 0) {
1594 continue;
1595 }
1596
1597 temp_used_names.add(target_name);
1598 const pxr::SdfPath &temp_material_child_path = temp_material_child.GetPath();
1599 const std::string &original_path = temp_material_child_path.GetString();
1600 const std::string new_path =
1601 temp_material_child.GetPath().ReplaceName(pxr::TfToken(target_name)).GetString();
1602
1603 rename_pairs.add_overwrite(original_path, new_path);
1604 }
1605
1606 /* We now need to find the connections from the material to the surface shader
1607 * and modify it to match the final target location */
1608 for (const auto &temp_material_output : temp_material.GetOutputs()) {
1609 pxr::SdfPathVector output_paths;
1610
1611 temp_material_output.GetAttr().GetConnections(&output_paths);
1612 if (output_paths.size() == 1) {
1613 output_paths[0] = reflow_materialx_paths(
1614 output_paths[0], temp_material_path, usd_path, rename_pairs);
1615
1616 auto target_material_output = usd_material.CreateOutput(temp_material_output.GetBaseName(),
1617 temp_material_output.GetTypeName());
1618 target_material_output.GetAttr().SetConnections(output_paths);
1619 }
1620 }
1621
1622 /* Next we need to iterate through every shader descendant recursively, to process them */
1623 for (const auto &temp_child : temp_material_prim.GetAllDescendants()) {
1624 /* We only care about shader children */
1625 auto temp_shader = pxr::UsdShadeShader(temp_child);
1626 if (!temp_shader) {
1627 continue;
1628 }
1629
1630 /* First, we process any inputs */
1631 for (const auto &shader_input : temp_shader.GetInputs()) {
1632 pxr::SdfPathVector connection_paths;
1633 shader_input.GetAttr().GetConnections(&connection_paths);
1634
1635 if (connection_paths.size() != 1) {
1636 continue;
1637 }
1638
1639 const pxr::SdfPath &connection_path = connection_paths[0];
1640
1641 auto connection_source = pxr::UsdShadeConnectionSourceInfo(temp_stage, connection_path);
1642 auto connection_source_prim = connection_source.source.GetPrim();
1643 if (connection_source_prim == temp_material_prim) {
1644 /* If it's connected to the material prim, we should just bake down the value.
1645 * usdMtlx connects them to constants because it wants to maximize separation between the
1646 * input mtlx file and the resulting graph, but this isn't the ideal structure when the
1647 * graph is inlined.
1648 * Baking the values down makes this much more usable. */
1649 auto connection_source_attr = temp_stage->GetAttributeAtPath(connection_path);
1650 if (connection_source_attr && shader_input.DisconnectSource()) {
1651 pxr::VtValue val;
1652 if (connection_source_attr.Get(&val) && !val.IsEmpty()) {
1653 shader_input.GetAttr().Set(val);
1654 }
1655 }
1656 }
1657 else {
1658 /* If it's connected to another prim, then we should fix the path to that prim
1659 * SdfCopySpec below will handle some cases, but only if the target path exists first
1660 * which is impossible to guarantee in a graph. */
1661
1662 connection_paths[0] = reflow_materialx_paths(
1663 connection_paths[0], temp_material_path, usd_path, rename_pairs);
1664 shader_input.GetAttr().SetConnections(connection_paths);
1665 }
1666 }
1667
1668 /* Next we iterate through the outputs */
1669 for (const auto &shader_output : temp_shader.GetOutputs()) {
1670 pxr::SdfPathVector connection_paths;
1671 shader_output.GetAttr().GetConnections(&connection_paths);
1672
1673 if (connection_paths.size() != 1) {
1674 continue;
1675 }
1676
1677 connection_paths[0] = reflow_materialx_paths(
1678 connection_paths[0], temp_material_path, usd_path, rename_pairs);
1679 shader_output.GetAttr().SetConnections(connection_paths);
1680 } /* Iterate through outputs */
1681
1682 } /* Iterate through material prim children */
1683
1684 auto temp_layer = temp_stage->Flatten();
1685
1686 /* Copy the primspecs from the temporary stage over to the target stage */
1687 auto target_root_layer = usd_export_context.stage->GetRootLayer();
1688 for (const auto &temp_material_child : temp_material_prim.GetChildren()) {
1689 auto target_path = reflow_materialx_paths(
1690 temp_material_child.GetPath(), temp_material_path, usd_path, rename_pairs);
1691 pxr::SdfCopySpec(temp_layer, temp_material_child.GetPath(), target_root_layer, target_path);
1692 }
1693}
1694#endif
1695
1696pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context,
1697 pxr::SdfPath usd_path,
1698 Material *material,
1699 const std::string &active_uvmap_name,
1700 ReportList *reports)
1701{
1702 pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Define(usd_export_context.stage,
1703 usd_path);
1704
1705 if (usd_export_context.export_params.generate_preview_surface) {
1707 usd_export_context, material, usd_material, active_uvmap_name, reports);
1708 }
1709 else {
1710 create_usd_viewport_material(usd_export_context, material, usd_material);
1711 }
1712
1713#ifdef WITH_MATERIALX
1714 if (usd_export_context.export_params.generate_materialx_network) {
1715 create_usd_materialx_material(
1716 usd_export_context, usd_path, material, active_uvmap_name, usd_material);
1717 }
1718#endif
1719
1721 usd_export_context.stage, material, usd_material, usd_export_context.export_params, reports);
1722
1723 return usd_material;
1724}
1725
1726} // namespace blender::io::usd
eUDIM_TILE_FORMAT
Definition BKE_image.hh:453
@ UDIM_TILE_FORMAT_NONE
Definition BKE_image.hh:454
@ UDIM_TILE_FORMAT_UDIM
Definition BKE_image.hh:455
ImBuf * BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
char * BKE_image_get_tile_strformat(const char *filepath, eUDIM_TILE_FORMAT *r_tile_format)
void BKE_image_set_filepath_from_tile_number(char *filepath, const char *pattern, eUDIM_TILE_FORMAT tile_format, int tile_number)
void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
bool BKE_image_is_dirty(Image *image)
bool BKE_image_has_packedfile(const Image *image)
bool BKE_image_is_filename_tokenized(char *filepath)
bool BKE_imbuf_write_as(ImBuf *ibuf, const char *filepath, const ImageFormatData *imf, bool save_copy)
void BKE_image_format_from_imbuf(ImageFormatData *im_format, const ImBuf *imbuf)
int BKE_image_path_ext_from_imformat_ensure(char *filepath, size_t filepath_maxncpy, const ImageFormatData *im_format)
char BKE_ftype_to_imtype(int ftype, const ImbFormatOptions *options)
int BKE_image_path_ext_from_imtype_ensure(char *filepath, size_t filepath_maxncpy, char imtype)
#define SH_NODE_UVMAP
#define SH_NODE_TEX_IMAGE
#define SH_NODE_BSDF_PRINCIPLED
#define SH_NODE_SEPARATE_COLOR
#define SH_NODE_TEX_COORD
#define SH_NODE_VECTOR_MATH
#define SH_NODE_TEX_ENVIRONMENT
#define SH_NODE_MATH
#define SH_NODE_BSDF_DIFFUSE
#define SH_NODE_MAPPING
#define SH_NODE_DISPLACEMENT
#define SH_NODE_ATTRIBUTE
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_WARNING
Definition BKE_report.hh:38
#define BLI_assert(a)
Definition BLI_assert.h:46
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:360
int BLI_copy(const char *path_src, const char *path_dst) ATTR_NONNULL()
#define LISTBASE_FOREACH(type, var, list)
#define M_PI
MINLINE void copy_v3_v3(float r[3], const float a[3])
bool BLI_path_abs(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1
#define FILE_MAXFILE
#define FILE_MAX
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
#define BLI_path_join(...)
void void void BLI_path_split_file_part(const char *filepath, char *file, size_t file_maxncpy) ATTR_NONNULL(1
int BLI_path_cmp_normalized(const char *p1, const char *p2) ATTR_NONNULL(1
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
#define STRNCPY_RLEN(dst, src)
Definition BLI_string.h:603
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:604
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
void BLI_string_split_suffix(const char *string, size_t string_maxlen, char *r_body, char *r_suf) ATTR_NONNULL(1
void BLI_string_replace_char(char *str, char src, char dst) ATTR_NONNULL(1)
unsigned char uchar
#define ELEM(...)
#define STREQ(a, b)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
#define CLOG_DEBUG(clg_ref,...)
Definition CLG_log.h:191
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
#define ID_BLEND_PATH_FROM_GLOBAL(_id)
Definition DNA_ID.h:688
@ IMA_GEN_TILE
@ IMA_SRC_TILED
struct ImageUser ImageUser
struct Image Image
struct Material Material
@ NODE_VECTOR_MATH_MULTIPLY_ADD
@ SHD_SPACE_OBJECT
@ NODE_MATH_LESS_THAN
@ NODE_MATH_ROUND
@ NODE_MATH_SUBTRACT
@ SHD_IMAGE_EXTENSION_MIRROR
@ SHD_IMAGE_EXTENSION_CLIP
@ SHD_IMAGE_EXTENSION_REPEAT
@ SHD_IMAGE_EXTENSION_EXTEND
@ SOCK_IN
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
@ SHD_ATTRIBUTE_GEOMETRY
struct Scene Scene
@ TEXMAP_TYPE_POINT
bool IMB_colormanagement_space_name_is_srgb(const char *name)
bool IMB_colormanagement_space_name_is_data(const char *name)
int IMB_test_image_type_from_memory(const unsigned char *buf, size_t buf_size)
eImbFileType
@ IMB_FTYPE_NONE
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
bool add_overwrite(const Key &key, const Value &value)
Definition BLI_map.hh:325
bool contains(const Key &key) const
Definition BLI_set.hh:310
bool add(const Key &key)
Definition BLI_set.hh:248
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:508
const Value & lookup(const Key &key) const
Definition BLI_map.hh:545
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
ItemIterator items() const &
Definition BLI_map.hh:902
#define rot(x, k)
#define pf(_x, _i)
Prefetch 64.
Definition gim_memory.h:48
#define input
#define in
#define out
#define main()
const ccl_global KernelWorkTile * tile
#define LOG(level)
Definition log.h:97
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
#define T2
Definition md5.cpp:21
#define T1
Definition md5.cpp:20
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2532
static void export_packed_texture(Image *ima, const std::string &export_dir, const bool allow_overwrite, ReportList *reports)
static void get_absolute_path(const Image *ima, char *r_path)
static void copy_tiled_textures(Image *ima, const std::string &dest_dir, const bool allow_overwrite, ReportList *reports)
static void export_texture(const USDExporterContext &usd_export_context, bNode *node)
static void create_usd_preview_surface_material(const USDExporterContext &usd_export_context, Material *material, pxr::UsdShadeMaterial &usd_material, const std::string &active_uvmap_name, ReportList *reports)
static const InputSpecMap & preview_surface_input_map()
static bNodeLink * traverse_channel(bNodeSocket *input, short target_type)
bool asset_exists(const std::string &path)
blender::Map< StringRef, InputSpec > InputSpecMap
static bool is_packed_texture(const Image *ima)
std::string get_relative_path(const std::string &path, const std::string &anchor)
static void create_usd_viewport_material(const USDExporterContext &usd_export_context, const Material *material, const pxr::UsdShadeMaterial &usd_material)
static pxr::TfToken get_node_tex_image_color_space(const bNode *node)
bool write_to_path(const void *data, size_t size, const std::string &path, ReportList *reports)
static bool is_in_memory_texture(Image *ima)
bool paths_equal(const std::string &path1, const std::string &path2)
pxr::TfToken token_for_input(const StringRef input_name)
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context, const pxr::UsdShadeMaterial &material, const StringRef name, int type)
static bool has_generated_tiles(const Image *ima)
static std::string get_in_memory_texture_filename(Image *ima)
static void export_in_memory_imbuf(ImBuf *imbuf, const std::string &export_dir, const char image_abs_path[FILE_MAX], const char file_name[FILE_MAX], const bool allow_overwrite, ReportList *reports)
static bNode * find_displacement_node(Material *material)
static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context, bNode *node)
void USD_path_abs(char *path, const char *basepath, bool for_import)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
std::string get_export_textures_dir(const pxr::UsdStageRefPtr stage)
static bNode * find_bsdf_node(Material *material)
static void export_in_memory_texture(Image *ima, const std::string &export_dir, const bool allow_overwrite, ReportList *reports)
static void copy_single_file(const Image *ima, const std::string &dest_dir, const bool allow_overwrite, ReportList *reports)
static void create_transform2d_shader(const USDExporterContext &usd_export_context, bNodeLink *mapping_link, pxr::UsdShadeMaterial &usd_material, pxr::UsdShadeInput &usd_input, const std::string &uvmap_name, ReportList *reports)
static void process_inputs(const USDExporterContext &usd_export_context, pxr::UsdShadeMaterial &usd_material, pxr::UsdShadeShader &shader, const bNode *node, const std::string &active_uvmap_name, ReportList *reports)
static pxr::TfToken get_node_tex_image_wrap(const bNode *node)
void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value, float scale)
static void create_uv_input(const USDExporterContext &usd_export_context, bNodeSocket *input_socket, pxr::UsdShadeMaterial &usd_material, pxr::UsdShadeInput &usd_input, const std::string &active_uvmap_name, ReportList *reports)
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context, pxr::SdfPath usd_path, Material *material, const std::string &active_uvmap_name, ReportList *reports)
void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDExportParams &export_params, ReportList *reports)
Definition usd_hook.cc:601
static pxr::UsdShadeShader create_primvar_reader_shader(const USDExporterContext &usd_export_context, const pxr::UsdShadeMaterial &material, const pxr::TfToken &primvar_type, const bNode *node)
void set_normal_texture_range(pxr::UsdShadeShader &usd_shader, const InputSpec &input_spec)
std::string get_usd_source_path(ID *id)
static void set_scale_bias(pxr::UsdShadeShader &usd_shader, const pxr::GfVec4f scale, const pxr::GfVec4f bias)
static void create_uvmap_shader(const USDExporterContext &usd_export_context, const bNodeLink *uvmap_link, const pxr::UsdShadeMaterial &usd_material, const pxr::UsdShadeInput &usd_input, const std::string &active_uvmap_name, ReportList *reports)
MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph, Material *material, const ExportParams &export_params)
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal)
static const pxr::TfToken emissive_color("emissiveColor", pxr::TfToken::Immortal)
static const pxr::TfToken st("st", pxr::TfToken::Immortal)
static const pxr::TfToken primvar_vector("UsdPrimvarReader_vector", 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 preview_shader("previewShader", 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 surface("surface", pxr::TfToken::Immortal)
static const pxr::TfToken r("r", pxr::TfToken::Immortal)
static const pxr::TfToken primvar_float("UsdPrimvarReader_float", 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 primvar_float3("UsdPrimvarReader_float3", pxr::TfToken::Immortal)
static const pxr::TfToken translation("translation", pxr::TfToken::Immortal)
static const pxr::TfToken result("result", 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 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 specular("specular", pxr::TfToken::Immortal)
static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal)
static const pxr::TfToken primvar_float2("UsdPrimvarReader_float2", pxr::TfToken::Immortal)
static const pxr::TfToken uv_texture("UsdUVTexture", 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 wrapT("wrapT", pxr::TfToken::Immortal)
static const pxr::TfToken a("a", pxr::TfToken::Immortal)
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal)
float wrap(float value, float max, float min)
Definition node_math.h:103
const char * name
static void image_abs_path(Main *bmain, Library *owner_library, const char *filepath, char *r_filepath_abs)
char name[258]
Definition DNA_ID.h:432
struct ListBase packedfiles
ColorManagedColorspaceSettings colorspace_settings
char filepath[1024]
ListBase tiles
short source
struct bNodeTree * nodetree
struct bNodeLink * link
void * default_value
char identifier[64]
int16_t custom1
ListBase inputs
struct ID * id
char name[64]
int16_t type_legacy
void * storage
wmJobWorkerStatus * worker_status
Definition usd.hh:187
ReportList * reports
Definition WM_types.hh:1028
uint len