Blender  V3.3
usd_writer_material.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include "usd_writer_material.h"
4 
5 #include "usd.h"
6 #include "usd_exporter_context.h"
7 
8 #include "BKE_image.h"
9 #include "BKE_image_format.h"
10 #include "BKE_main.h"
11 #include "BKE_node.h"
12 
13 #include "IMB_colormanagement.h"
14 
15 #include "BLI_fileops.h"
16 #include "BLI_linklist.h"
17 #include "BLI_listbase.h"
18 #include "BLI_math.h"
19 #include "BLI_path_util.h"
20 #include "BLI_string.h"
21 
22 #include "DNA_material_types.h"
23 
24 #include "MEM_guardedalloc.h"
25 
26 #include "WM_api.h"
27 
28 #include <pxr/base/tf/stringUtils.h>
29 #include <pxr/pxr.h>
30 #include <pxr/usd/usdGeom/scope.h>
31 
32 #include <iostream>
33 
34 /* TfToken objects are not cheap to construct, so we do it once. */
35 namespace usdtokens {
36 // Materials
37 static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal);
38 static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal);
39 static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal);
40 static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
41 static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
42 static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
43 static const pxr::TfToken uv_texture("UsdUVTexture", pxr::TfToken::Immortal);
44 static const pxr::TfToken primvar_float2("UsdPrimvarReader_float2", pxr::TfToken::Immortal);
45 static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
46 static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
47 static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal);
48 static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
49 static const pxr::TfToken perspective("perspective", pxr::TfToken::Immortal);
50 static const pxr::TfToken orthographic("orthographic", pxr::TfToken::Immortal);
51 static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal);
52 static const pxr::TfToken r("r", pxr::TfToken::Immortal);
53 static const pxr::TfToken g("g", pxr::TfToken::Immortal);
54 static const pxr::TfToken b("b", pxr::TfToken::Immortal);
55 static const pxr::TfToken st("st", pxr::TfToken::Immortal);
56 static const pxr::TfToken result("result", pxr::TfToken::Immortal);
57 static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
58 static const pxr::TfToken out("out", pxr::TfToken::Immortal);
59 static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
60 static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
61 static const pxr::TfToken file("file", pxr::TfToken::Immortal);
62 static const pxr::TfToken preview("preview", pxr::TfToken::Immortal);
63 static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
64 static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
65 static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
66 static const pxr::TfToken Shader("Shader", pxr::TfToken::Immortal);
67 } // namespace usdtokens
68 
69 /* Cycles specific tokens. */
70 namespace cyclestokens {
71 static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal);
72 } // namespace cyclestokens
73 
74 namespace blender::io::usd {
75 
76 /* Preview surface input specification. */
77 struct InputSpec {
78  pxr::TfToken input_name;
79  pxr::SdfValueTypeName input_type;
80  pxr::TfToken source_name;
81  /* Whether a default value should be set
82  * if the node socket has not input. Usually
83  * false for the Normal input. */
85 };
86 
87 /* Map Blender socket names to USD Preview Surface InputSpec structs. */
88 using InputSpecMap = std::map<std::string, InputSpec>;
89 
90 /* Static function forward declarations. */
91 static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
92  pxr::UsdShadeMaterial &material,
93  const char *name,
94  int type);
95 static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
96  pxr::UsdShadeMaterial &material,
97  bNode *node);
98 static void create_uvmap_shader(const USDExporterContext &usd_export_context,
99  bNode *tex_node,
100  pxr::UsdShadeMaterial &usd_material,
101  pxr::UsdShadeShader &usd_tex_shader,
102  const pxr::TfToken &default_uv);
103 static void export_texture(bNode *node,
104  const pxr::UsdStageRefPtr stage,
105  const bool allow_overwrite = false);
107 static void get_absolute_path(Image *ima, char *r_path);
108 static std::string get_tex_image_asset_path(bNode *node,
109  const pxr::UsdStageRefPtr stage,
110  const USDExportParams &export_params);
112 static bNode *traverse_channel(bNodeSocket *input, short target_type);
113 
114 template<typename T1, typename T2>
115 void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value);
116 
119  pxr::UsdShadeMaterial &usd_material,
120  const std::string &default_uv)
121 {
122  if (!material) {
123  return;
124  }
125 
126  /* Define a 'preview' scope beneath the material which will contain the preview shaders. */
127  pxr::UsdGeomScope::Define(usd_export_context.stage,
128  usd_material.GetPath().AppendChild(usdtokens::preview));
129 
130  /* Default map when creating UV primvar reader shaders. */
131  pxr::TfToken default_uv_sampler = default_uv.empty() ? cyclestokens::UVMap :
132  pxr::TfToken(default_uv);
133 
134  /* We only handle the first instance of either principled or
135  * diffuse bsdf nodes in the material's node tree, because
136  * USD Preview Surface has no concept of layering materials. */
138  if (!node) {
139  return;
140  }
141 
142  pxr::UsdShadeShader preview_surface = create_usd_preview_shader(
143  usd_export_context, usd_material, node);
144 
145  const InputSpecMap &input_map = preview_surface_input_map();
146 
147  /* Set the preview surface inputs. */
148  LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
149 
150  /* Check if this socket is mapped to a USD preview shader input. */
151  const InputSpecMap::const_iterator it = input_map.find(sock->name);
152 
153  if (it == input_map.end()) {
154  continue;
155  }
156 
157  pxr::UsdShadeShader created_shader;
158 
159  bNode *input_node = traverse_channel(sock, SH_NODE_TEX_IMAGE);
160 
161  const InputSpec &input_spec = it->second;
162 
163  if (input_node) {
164  /* Create connection. */
165  created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
166 
167  preview_surface.CreateInput(input_spec.input_name, input_spec.input_type)
168  .ConnectToSource(created_shader.ConnectableAPI(), input_spec.source_name);
169  }
170  else if (input_spec.set_default_value) {
171  /* Set hardcoded value. */
172  switch (sock->type) {
173  case SOCK_FLOAT: {
174  create_input<bNodeSocketValueFloat, float>(
175  preview_surface, input_spec, sock->default_value);
176  } break;
177  case SOCK_VECTOR: {
178  create_input<bNodeSocketValueVector, pxr::GfVec3f>(
179  preview_surface, input_spec, sock->default_value);
180  } break;
181  case SOCK_RGBA: {
182  create_input<bNodeSocketValueRGBA, pxr::GfVec3f>(
183  preview_surface, input_spec, sock->default_value);
184  } break;
185  default:
186  break;
187  }
188  }
189 
190  /* If any input texture node has been found, export the texture, if necessary,
191  * and look for a connected uv node. */
192  if (!(created_shader && input_node && input_node->type == SH_NODE_TEX_IMAGE)) {
193  continue;
194  }
195 
196  if (usd_export_context.export_params.export_textures) {
197  export_texture(input_node,
198  usd_export_context.stage,
199  usd_export_context.export_params.overwrite_textures);
200  }
201 
203  usd_export_context, input_node, usd_material, created_shader, default_uv_sampler);
204  }
205 }
206 
207 void create_usd_viewport_material(const USDExporterContext &usd_export_context,
209  pxr::UsdShadeMaterial &usd_material)
210 {
211  /* Construct the shader. */
212  pxr::SdfPath shader_path = usd_material.GetPath().AppendChild(usdtokens::preview_shader);
213  pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
214 
215  shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
216  shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
217  .Set(pxr::GfVec3f(material->r, material->g, material->b));
218  shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
219  shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
220 
221  /* Connect the shader and the material together. */
222  usd_material.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), usdtokens::surface);
223 }
224 
225 /* Return USD Preview Surface input map singleton. */
227 {
228  static InputSpecMap input_map = {
229  {"Base Color",
230  {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}},
231  {"Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}},
232  {"Roughness", {usdtokens::roughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
233  {"Metallic", {usdtokens::metallic, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
234  {"Specular", {usdtokens::specular, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
235  {"Alpha", {usdtokens::opacity, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
236  {"IOR", {usdtokens::ior, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
237  /* Note that for the Normal input set_default_value is false. */
238  {"Normal", {usdtokens::normal, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, false}},
239  {"Clearcoat", {usdtokens::clearcoat, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
240  {"Clearcoat Roughness",
241  {usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
242  };
243 
244  return input_map;
245 }
246 
247 /* Create an input on the given shader with name and type
248  * provided by the InputSpec and assign the given value to the
249  * input. Parameters T1 and T2 indicate the Blender and USD
250  * value types, respectively. */
251 template<typename T1, typename T2>
252 void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value)
253 {
254  const T1 *cast_value = static_cast<const T1 *>(value);
255  shader.CreateInput(spec.input_name, spec.input_type).Set(T2(cast_value->value));
256 }
257 
258 /* Find the UVMAP node input to the given texture image node and convert it
259  * to a USD primvar reader shader. If no UVMAP node is found, create a primvar
260  * reader for the given default uv set. The primvar reader will be attached to
261  * the 'st' input of the given USD texture shader. */
262 static void create_uvmap_shader(const USDExporterContext &usd_export_context,
263  bNode *tex_node,
264  pxr::UsdShadeMaterial &usd_material,
265  pxr::UsdShadeShader &usd_tex_shader,
266  const pxr::TfToken &default_uv)
267 {
268  bool found_uv_node = false;
269 
270  /* Find UV input to the texture node. */
271  LISTBASE_FOREACH (bNodeSocket *, tex_node_sock, &tex_node->inputs) {
272 
273  if (!tex_node_sock->link || !STREQ(tex_node_sock->name, "Vector")) {
274  continue;
275  }
276 
277  bNode *uv_node = traverse_channel(tex_node_sock, SH_NODE_UVMAP);
278  if (uv_node == nullptr) {
279  continue;
280  }
281 
282  pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
283  usd_export_context, usd_material, uv_node);
284 
285  if (!uv_shader.GetPrim().IsValid()) {
286  continue;
287  }
288 
289  found_uv_node = true;
290 
291  if (NodeShaderUVMap *shader_uv_map = static_cast<NodeShaderUVMap *>(uv_node->storage)) {
292  /* We need to make valid here because actual uv primvar has been. */
293  std::string uv_set = pxr::TfMakeValidIdentifier(shader_uv_map->uv_map);
294 
295  uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token)
296  .Set(pxr::TfToken(uv_set));
297  usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
298  .ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
299  }
300  else {
301  uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(default_uv);
302  usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
303  .ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
304  }
305  }
306 
307  if (!found_uv_node) {
308  /* No UVMAP node was linked to the texture node. However, we generate
309  * a primvar reader node that specifies the UV set to sample, as some
310  * DCCs require this. */
311 
312  pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
313  usd_export_context, usd_material, "uvmap", SH_NODE_TEX_COORD);
314 
315  if (uv_shader.GetPrim().IsValid()) {
316  uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(default_uv);
317  usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
318  .ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
319  }
320  }
321 }
322 
323 /* Generate a file name for an in-memory image that doesn't have a
324  * filepath already defined. */
325 static std::string get_in_memory_texture_filename(Image *ima)
326 {
327  bool is_dirty = BKE_image_is_dirty(ima);
328  bool is_generated = ima->source == IMA_SRC_GENERATED;
329  bool is_packed = BKE_image_has_packedfile(ima);
330  if (!(is_generated || is_dirty || is_packed)) {
331  return "";
332  }
333 
334  /* Determine the correct file extension from the image format. */
335  ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
336  if (!imbuf) {
337  return "";
338  }
339 
340  ImageFormatData imageFormat;
341  BKE_image_format_from_imbuf(&imageFormat, imbuf);
342 
343  char file_name[FILE_MAX];
344  /* Use the image name for the file name. */
345  strcpy(file_name, ima->id.name + 2);
346 
347  BKE_image_path_ensure_ext_from_imformat(file_name, &imageFormat);
348 
349  return file_name;
350 }
351 
353  const std::string &export_dir,
354  const bool allow_overwrite)
355 {
356  char image_abs_path[FILE_MAX];
357 
358  char file_name[FILE_MAX];
359  if (strlen(ima->filepath) > 0) {
360  get_absolute_path(ima, image_abs_path);
361  BLI_split_file_part(image_abs_path, file_name, FILE_MAX);
362  }
363  else {
364  /* Use the image name for the file name. */
365  strcpy(file_name, ima->id.name + 2);
366  }
367 
368  ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
369  if (!imbuf) {
370  return;
371  }
372 
373  ImageFormatData imageFormat;
374  BKE_image_format_from_imbuf(&imageFormat, imbuf);
375 
376  /* This image in its current state only exists in Blender memory.
377  * So we have to export it. The export will keep the image state intact,
378  * so the exported file will not be associated with the image. */
379 
380  BKE_image_path_ensure_ext_from_imformat(file_name, &imageFormat);
381 
382  char export_path[FILE_MAX];
383  BLI_path_join(export_path, FILE_MAX, export_dir.c_str(), file_name, nullptr);
384 
385  if (!allow_overwrite && BLI_exists(export_path)) {
386  return;
387  }
388 
389  if ((BLI_path_cmp_normalized(export_path, image_abs_path) == 0) && BLI_exists(image_abs_path)) {
390  /* As a precaution, don't overwrite the original path. */
391  return;
392  }
393 
394  std::cout << "Exporting in-memory texture to " << export_path << std::endl;
395 
396  if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) {
397  WM_reportf(RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path);
398  }
399 }
400 
401 /* Get the absolute filepath of the given image. Assumes
402  * r_path result array is of length FILE_MAX. */
403 static void get_absolute_path(Image *ima, char *r_path)
404 {
405  /* Make absolute source path. */
406  BLI_strncpy(r_path, ima->filepath, FILE_MAX);
407  BLI_path_abs(r_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
408  BLI_path_normalize(nullptr, r_path);
409 }
410 
412 {
413  if (!node->id) {
414  return pxr::TfToken();
415  }
416 
417  Image *ima = reinterpret_cast<Image *>(node->id);
418 
420  return usdtokens::raw;
421  }
423  return usdtokens::sRGB;
424  }
425 
426  return pxr::TfToken();
427 }
428 
429 /* Search the upstream nodes connected to the given socket and return the first occurrence
430  * of the node of the given type. Return null if no node of this type was found. */
431 static bNode *traverse_channel(bNodeSocket *input, const short target_type)
432 {
433  if (!input->link) {
434  return nullptr;
435  }
436 
437  bNode *linked_node = input->link->fromnode;
438  if (linked_node->type == target_type) {
439  /* Return match. */
440  return linked_node;
441  }
442 
443  /* Recursively traverse the linked node's sockets. */
444  LISTBASE_FOREACH (bNodeSocket *, sock, &linked_node->inputs) {
445  if (bNode *found_node = traverse_channel(sock, target_type)) {
446  return found_node;
447  }
448  }
449 
450  return nullptr;
451 }
452 
453 /* Returns the first occurrence of a principled BSDF or a diffuse BSDF node found in the given
454  * material's node tree. Returns null if no instance of either type was found. */
456 {
458  if (node->type == SH_NODE_BSDF_PRINCIPLED || node->type == SH_NODE_BSDF_DIFFUSE) {
459  return node;
460  }
461  }
462 
463  return nullptr;
464 }
465 
466 /* Creates a USD Preview Surface shader based on the given cycles node name and type. */
467 static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
468  pxr::UsdShadeMaterial &material,
469  const char *name,
470  const int type)
471 {
472  pxr::SdfPath shader_path = material.GetPath()
473  .AppendChild(usdtokens::preview)
474  .AppendChild(pxr::TfToken(pxr::TfMakeValidIdentifier(name)));
475  pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
476 
477  switch (type) {
478  case SH_NODE_TEX_IMAGE: {
479  shader.CreateIdAttr(pxr::VtValue(usdtokens::uv_texture));
480  break;
481  }
482  case SH_NODE_TEX_COORD:
483  case SH_NODE_UVMAP: {
484  shader.CreateIdAttr(pxr::VtValue(usdtokens::primvar_float2));
485  break;
486  }
489  shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
490  material.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), usdtokens::surface);
491  break;
492  }
493 
494  default:
495  break;
496  }
497 
498  return shader;
499 }
500 
501 /* Creates a USD Preview Surface shader based on the given cycles shading node. */
502 static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
503  pxr::UsdShadeMaterial &material,
504  bNode *node)
505 {
506  pxr::UsdShadeShader shader = create_usd_preview_shader(
507  usd_export_context, material, node->name, node->type);
508 
509  if (node->type != SH_NODE_TEX_IMAGE) {
510  return shader;
511  }
512 
513  /* For texture image nodes we set the image path and color space. */
514  std::string imagePath = get_tex_image_asset_path(
515  node, usd_export_context.stage, usd_export_context.export_params);
516  if (!imagePath.empty()) {
517  shader.CreateInput(usdtokens::file, pxr::SdfValueTypeNames->Asset)
518  .Set(pxr::SdfAssetPath(imagePath));
519  }
520 
521  pxr::TfToken colorSpace = get_node_tex_image_color_space(node);
522  if (!colorSpace.IsEmpty()) {
523  shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token).Set(colorSpace);
524  }
525 
526  return shader;
527 }
528 
529 static std::string get_tex_image_asset_path(Image *ima)
530 {
531  char filepath[FILE_MAX];
532  get_absolute_path(ima, filepath);
533 
534  return std::string(filepath);
535 }
536 
537 /* Gets an asset path for the given texture image node. The resulting path
538  * may be absolute, relative to the USD file, or in a 'textures' directory
539  * in the same directory as the USD file, depending on the export parameters.
540  * The filename is typically the image filepath but might also be automatically
541  * generated based on the image name for in-memory textures when exporting textures.
542  * This function may return an empty string if the image does not have a filepath
543  * assigned and no asset path could be determined. */
544 static std::string get_tex_image_asset_path(bNode *node,
545  const pxr::UsdStageRefPtr stage,
546  const USDExportParams &export_params)
547 {
548  Image *ima = reinterpret_cast<Image *>(node->id);
549  if (!ima) {
550  return "";
551  }
552 
553  std::string path;
554 
555  if (strlen(ima->filepath) > 0) {
556  /* Get absolute path. */
557  path = get_tex_image_asset_path(ima);
558  }
559  else if (export_params.export_textures) {
560  /* Image has no filepath, but since we are exporting textures,
561  * check if this is an in-memory texture for which we can
562  * generate a file name. */
563  path = get_in_memory_texture_filename(ima);
564  }
565 
566  if (path.empty()) {
567  return path;
568  }
569 
570  if (export_params.export_textures) {
571  /* The texture is exported to a 'textures' directory next to the
572  * USD root layer. */
573 
574  char exp_path[FILE_MAX];
575  char file_path[FILE_MAX];
576  BLI_split_file_part(path.c_str(), file_path, FILE_MAX);
577 
578  if (export_params.relative_paths) {
579  BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path, nullptr);
580  }
581  else {
582  /* Create absolute path in the textures directory. */
583  pxr::SdfLayerHandle layer = stage->GetRootLayer();
584  std::string stage_path = layer->GetRealPath();
585  if (stage_path.empty()) {
586  return path;
587  }
588 
589  char dir_path[FILE_MAX];
590  BLI_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
591  BLI_path_join(exp_path, FILE_MAX, dir_path, "textures", file_path, nullptr);
592  }
593  BLI_str_replace_char(exp_path, '\\', '/');
594  return exp_path;
595  }
596 
597  if (export_params.relative_paths) {
598  /* Get the path relative to the USD. */
599  pxr::SdfLayerHandle layer = stage->GetRootLayer();
600  std::string stage_path = layer->GetRealPath();
601  if (stage_path.empty()) {
602  return path;
603  }
604 
605  char rel_path[FILE_MAX];
606  strcpy(rel_path, path.c_str());
607 
608  BLI_path_rel(rel_path, stage_path.c_str());
609  if (!BLI_path_is_rel(rel_path)) {
610  return path;
611  }
612  BLI_str_replace_char(rel_path, '\\', '/');
613  return rel_path + 2;
614  }
615 
616  return path;
617 }
618 
619 /* If the given image is tiled, copy the image tiles to the given
620  * destination directory. */
621 static void copy_tiled_textures(Image *ima,
622  const std::string &dest_dir,
623  const bool allow_overwrite)
624 {
625  char src_path[FILE_MAX];
626  get_absolute_path(ima, src_path);
627 
628  eUDIM_TILE_FORMAT tile_format;
629  char *udim_pattern = BKE_image_get_tile_strformat(src_path, &tile_format);
630 
631  /* Only <UDIM> tile formats are supported by USD right now. */
632  if (tile_format != UDIM_TILE_FORMAT_UDIM) {
633  std::cout << "WARNING: unsupported tile format for `" << src_path << "`" << std::endl;
634  MEM_SAFE_FREE(udim_pattern);
635  return;
636  }
637 
638  /* Copy all tiles. */
639  LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
640  char src_tile_path[FILE_MAX];
642  src_tile_path, udim_pattern, tile_format, tile->tile_number);
643 
644  char dest_filename[FILE_MAXFILE];
645  BLI_split_file_part(src_tile_path, dest_filename, sizeof(dest_filename));
646 
647  char dest_tile_path[FILE_MAX];
648  BLI_path_join(dest_tile_path, FILE_MAX, dest_dir.c_str(), dest_filename, nullptr);
649 
650  if (!allow_overwrite && BLI_exists(dest_tile_path)) {
651  continue;
652  }
653 
654  if (BLI_path_cmp_normalized(src_tile_path, dest_tile_path) == 0) {
655  /* Source and destination paths are the same, don't copy. */
656  continue;
657  }
658 
659  std::cout << "Copying texture tile from " << src_tile_path << " to " << dest_tile_path
660  << std::endl;
661 
662  /* Copy the file. */
663  if (BLI_copy(src_tile_path, dest_tile_path) != 0) {
665  "USD export: couldn't copy texture tile from %s to %s",
666  src_tile_path,
667  dest_tile_path);
668  }
669  }
670  MEM_SAFE_FREE(udim_pattern);
671 }
672 
673 /* Copy the given image to the destination directory. */
674 static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite)
675 {
676  char source_path[FILE_MAX];
677  get_absolute_path(ima, source_path);
678 
679  char file_name[FILE_MAX];
680  BLI_split_file_part(source_path, file_name, FILE_MAX);
681 
682  char dest_path[FILE_MAX];
683  BLI_path_join(dest_path, FILE_MAX, dest_dir.c_str(), file_name, nullptr);
684 
685  if (!allow_overwrite && BLI_exists(dest_path)) {
686  return;
687  }
688 
689  if (BLI_path_cmp_normalized(source_path, dest_path) == 0) {
690  /* Source and destination paths are the same, don't copy. */
691  return;
692  }
693 
694  std::cout << "Copying texture from " << source_path << " to " << dest_path << std::endl;
695 
696  /* Copy the file. */
697  if (BLI_copy(source_path, dest_path) != 0) {
698  WM_reportf(
699  RPT_WARNING, "USD export: couldn't copy texture from %s to %s", source_path, dest_path);
700  }
701 }
702 
703 /* Export the given texture node's image to a 'textures' directory
704  * next to given stage's root layer USD.
705  * Based on ImagesExporter::export_UV_Image() */
706 static void export_texture(bNode *node,
707  const pxr::UsdStageRefPtr stage,
708  const bool allow_overwrite)
709 {
710  if (node->type != SH_NODE_TEX_IMAGE && node->type != SH_NODE_TEX_ENVIRONMENT) {
711  return;
712  }
713 
714  Image *ima = reinterpret_cast<Image *>(node->id);
715  if (!ima) {
716  return;
717  }
718 
719  pxr::SdfLayerHandle layer = stage->GetRootLayer();
720  std::string stage_path = layer->GetRealPath();
721  if (stage_path.empty()) {
722  return;
723  }
724 
725  char usd_dir_path[FILE_MAX];
726  BLI_split_dir_part(stage_path.c_str(), usd_dir_path, FILE_MAX);
727 
728  char tex_dir_path[FILE_MAX];
729  BLI_path_join(tex_dir_path, FILE_MAX, usd_dir_path, "textures", SEP_STR, nullptr);
730 
731  BLI_dir_create_recursive(tex_dir_path);
732 
733  const bool is_dirty = BKE_image_is_dirty(ima);
734  const bool is_generated = ima->source == IMA_SRC_GENERATED;
735  const bool is_packed = BKE_image_has_packedfile(ima);
736 
737  std::string dest_dir(tex_dir_path);
738 
739  if (is_generated || is_dirty || is_packed) {
740  export_in_memory_texture(ima, dest_dir, allow_overwrite);
741  }
742  else if (ima->source == IMA_SRC_TILED) {
743  copy_tiled_textures(ima, dest_dir, allow_overwrite);
744  }
745  else {
746  copy_single_file(ima, dest_dir, allow_overwrite);
747  }
748 }
749 
750 } // namespace blender::io::usd
eUDIM_TILE_FORMAT
Definition: BKE_image.h:378
@ UDIM_TILE_FORMAT_UDIM
Definition: BKE_image.h:380
int BKE_imbuf_write_as(struct ImBuf *ibuf, const char *name, const struct ImageFormatData *imf, bool save_copy)
void BKE_image_set_filepath_from_tile_number(char *filepath, const char *pattern, eUDIM_TILE_FORMAT tile_format, int tile_number)
struct ImBuf * BKE_image_acquire_ibuf(struct Image *ima, struct ImageUser *iuser, void **r_lock)
bool BKE_image_has_packedfile(const struct Image *image)
bool BKE_image_is_dirty(struct Image *image)
char * BKE_image_get_tile_strformat(const char *filepath, eUDIM_TILE_FORMAT *r_tile_format)
void BKE_image_format_from_imbuf(struct ImageFormatData *im_format, const struct ImBuf *imbuf)
int BKE_image_path_ensure_ext_from_imformat(char *string, const struct ImageFormatData *im_format)
#define SH_NODE_UVMAP
Definition: BKE_node.h:1158
#define SH_NODE_BSDF_PRINCIPLED
Definition: BKE_node.h:1164
#define SH_NODE_TEX_ENVIRONMENT
Definition: BKE_node.h:1128
#define SH_NODE_BSDF_DIFFUSE
Definition: BKE_node.h:1109
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: storage.c:314
bool BLI_dir_create_recursive(const char *dir) ATTR_NONNULL()
Definition: fileops.c:1219
int BLI_copy(const char *file, const char *to) ATTR_NONNULL()
Definition: fileops.c:1198
#define LISTBASE_FOREACH(type, var, list)
Definition: BLI_listbase.h:336
void BLI_split_dir_part(const char *string, char *dir, size_t dirlen)
Definition: path_util.c:1490
bool BLI_path_is_rel(const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT
Definition: path_util.c:347
#define FILE_MAXFILE
#define FILE_MAX
void BLI_path_normalize(const char *relabase, char *path) ATTR_NONNULL(2)
Definition: path_util.c:131
size_t BLI_path_join(char *__restrict dst, size_t dst_len, const char *path_first,...) ATTR_NONNULL(1
int BLI_path_cmp_normalized(const char *p1, const char *p2) ATTR_NONNULL(1
void BLI_path_rel(char *file, const char *relfile) ATTR_NONNULL()
Definition: path_util.c:450
void BLI_split_file_part(const char *string, char *file, size_t filelen)
Definition: path_util.c:1495
bool BLI_path_abs(char *path, const char *basepath) ATTR_NONNULL()
Definition: path_util.c:897
void BLI_str_replace_char(char *str, char src, char dst) ATTR_NONNULL()
Definition: string.c:503
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t maxncpy) ATTR_NONNULL()
Definition: string.c:64
#define STREQ(a, b)
#define ID_BLEND_PATH_FROM_GLOBAL(_id)
Definition: DNA_ID.h:561
@ IMA_SRC_GENERATED
@ IMA_SRC_TILED
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum type
bool IMB_colormanagement_space_name_is_srgb(const char *name)
bool IMB_colormanagement_space_name_is_data(const char *name)
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Sky Generate a procedural sky texture Noise Generate fractal Perlin noise Wave Generate procedural bands or rings with noise Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks SH_NODE_TEX_COORD
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block SH_NODE_TEX_IMAGE
ColorManagedColorspaceSettings colorspace_settings
char filepath[1024]
ListBase tiles
short source
OperationNode * node
EvaluationStage stage
Definition: deg_eval.cc:89
Material material
ccl_global const KernelWorkTile * tile
ccl_global KernelShaderEvalInput * input
#define T2
Definition: md5.cpp:18
#define T1
Definition: md5.cpp:17
static void export_in_memory_texture(Image *ima, const std::string &export_dir, const bool allow_overwrite)
static pxr::TfToken get_node_tex_image_color_space(bNode *node)
void create_usd_preview_surface_material(const USDExporterContext &usd_export_context, Material *material, pxr::UsdShadeMaterial &usd_material, const std::string &default_uv)
static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite)
static bNode * traverse_channel(bNodeSocket *input, short target_type)
void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value)
static void copy_tiled_textures(Image *ima, const std::string &dest_dir, const bool allow_overwrite)
static std::string get_in_memory_texture_filename(Image *ima)
std::map< std::string, InputSpec > InputSpecMap
static InputSpecMap & preview_surface_input_map()
static void export_texture(bNode *node, const pxr::UsdStageRefPtr stage, const bool allow_overwrite=false)
static bNode * find_bsdf_node(Material *material)
void create_usd_viewport_material(const USDExporterContext &usd_export_context, Material *material, pxr::UsdShadeMaterial &usd_material)
static void create_uvmap_shader(const USDExporterContext &usd_export_context, bNode *tex_node, pxr::UsdShadeMaterial &usd_material, pxr::UsdShadeShader &usd_tex_shader, const pxr::TfToken &default_uv)
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context, pxr::UsdShadeMaterial &material, const char *name, int type)
static std::string get_tex_image_asset_path(bNode *node, const pxr::UsdStageRefPtr stage, const USDExportParams &export_params)
static void get_absolute_path(Image *ima, char *r_path)
static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal)
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal)
static const pxr::TfToken st("st", 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 ior("ior", 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 clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal)
static const pxr::TfToken file("file", pxr::TfToken::Immortal)
static const pxr::TfToken normal("normal", pxr::TfToken::Immortal)
static const pxr::TfToken roughness("roughness", 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 preview("preview", 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 rgb("rgb", pxr::TfToken::Immortal)
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal)
static const pxr::TfToken Shader("Shader", pxr::TfToken::Immortal)
char name[66]
Definition: DNA_ID.h:378
struct bNodeTree * nodetree
bool relative_paths
Definition: usd.h:38
bool export_textures
Definition: usd.h:36
bool overwrite_textures
Definition: usd.h:37
ListBase nodes
ListBase inputs
short type
void * storage
pxr::SdfValueTypeName input_type
#define SEP_STR
Definition: unit.c:33
void WM_reportf(eReportType type, const char *format,...)