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