Blender V4.5
usd_capi_export.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <fmt/core.h>
6
8#include "usd.hh"
10#include "usd_hook.hh"
12#include "usd_light_convert.hh"
13#include "usd_private.hh"
14
15#include <pxr/base/tf/token.h>
16#include <pxr/pxr.h>
17#include <pxr/usd/sdf/assetPath.h>
18#include <pxr/usd/sdf/path.h>
19#include <pxr/usd/usd/primRange.h>
20#include <pxr/usd/usd/stage.h>
21#include <pxr/usd/usdGeom/metrics.h>
22#include <pxr/usd/usdGeom/pointInstancer.h>
23#include <pxr/usd/usdGeom/tokens.h>
24#include <pxr/usd/usdGeom/xform.h>
25#include <pxr/usd/usdGeom/xformCommonAPI.h>
26#include <pxr/usd/usdUtils/usdzPackage.h>
27
28#include "MEM_guardedalloc.h"
29
30#include "DEG_depsgraph.hh"
33
35#include "DNA_scene_types.h"
36
37#include "BKE_appdir.hh"
38#include "BKE_blender_version.h"
39#include "BKE_context.hh"
40#include "BKE_global.hh"
41#include "BKE_image.hh"
42#include "BKE_image_save.hh"
43#include "BKE_lib_id.hh"
44#include "BKE_report.hh"
45#include "BKE_scene.hh"
46
47#include "BLI_fileops.h"
48#include "BLI_math_matrix.h"
49#include "BLI_math_rotation.h"
50#include "BLI_math_vector.h"
51#include "BLI_path_utils.hh"
52#include "BLI_string.h"
53#include "BLI_timeit.hh"
54
55#include <IMB_imbuf.hh>
56#include <IMB_imbuf_types.hh>
57
58#include "WM_api.hh"
59#include "WM_types.hh"
60
61#include "CLG_log.h"
62static CLG_LogRef LOG = {"io.usd"};
63
64namespace blender::io::usd {
65
67 Main *bmain = nullptr;
68 Depsgraph *depsgraph = nullptr;
69 wmWindowManager *wm = nullptr;
70 Scene *scene = nullptr;
71
76
77 bool export_ok = false;
79
80 bool targets_usdz() const
81 {
82 return usdz_filepath[0] != '\0';
83 }
84
85 const char *export_filepath() const
86 {
87 if (targets_usdz()) {
88 return usdz_filepath;
89 }
91 }
92};
93
94/* Returns true if the given prim path is valid, per
95 * the requirements of the prim path manipulation logic
96 * of the exporter. Also returns true if the path is
97 * the empty string. Returns false otherwise. */
98static bool prim_path_valid(const char *path)
99{
100 BLI_assert(path);
101
102 if (path[0] == '\0') {
103 /* Empty paths are ignored in the code,
104 * so they can be passed through. */
105 return true;
106 }
107
108 /* Check path syntax. */
109 std::string errMsg;
110 if (!pxr::SdfPath::IsValidPathString(path, &errMsg)) {
111 WM_global_reportf(RPT_ERROR, "USD Export: invalid path string '%s': %s", path, errMsg.c_str());
112 return false;
113 }
114
115 /* Verify that an absolute prim path can be constructed
116 * from this path string. */
117
118 pxr::SdfPath sdf_path(path);
119 if (!sdf_path.IsAbsolutePath()) {
120 WM_global_reportf(RPT_ERROR, "USD Export: path '%s' is not an absolute path", path);
121 return false;
122 }
123
124 if (!sdf_path.IsPrimPath()) {
125 WM_global_reportf(RPT_ERROR, "USD Export: path string '%s' is not a prim path", path);
126 return false;
127 }
128
129 return true;
130}
131
139{
140 bool valid = true;
141
142 if (!prim_path_valid(params.root_prim_path)) {
143 valid = false;
144 }
145
146 return valid;
147}
148
155static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &params)
156{
157 if (params.root_prim_path[0] == '\0') {
158 return;
159 }
160
161 pxr::UsdGeomXform root_xf = pxr::UsdGeomXform::Define(stage,
162 pxr::SdfPath(params.root_prim_path));
163
164 if (!root_xf) {
165 return;
166 }
167
168 pxr::UsdGeomXformCommonAPI xf_api(root_xf.GetPrim());
169
170 if (!xf_api) {
171 return;
172 }
173
174 if (params.convert_scene_units) {
175 xf_api.SetScale(pxr::GfVec3f(float(1.0 / get_meters_per_unit(params))));
176 }
177
178 if (params.convert_orientation) {
179 float mrot[3][3];
180 mat3_from_axis_conversion(IO_AXIS_Y, IO_AXIS_Z, params.forward_axis, params.up_axis, mrot);
181 transpose_m3(mrot);
182
183 float eul[3];
184 mat3_to_eul(eul, mrot);
185
186 /* Convert radians to degrees. */
187 mul_v3_fl(eul, 180.0f / M_PI);
188
189 xf_api.SetRotate(pxr::GfVec3f(eul[0], eul[1], eul[2]));
190 }
191
192 for (const auto &path : pxr::SdfPath(params.root_prim_path).GetPrefixes()) {
193 auto xform = pxr::UsdGeomXform::Define(stage, path);
194 /* Tag generated primitives to allow filtering on import. */
195 xform.GetPrim().SetCustomDataByKey(pxr::TfToken("Blender:generated"), pxr::VtValue(true));
196 }
197}
198
200{
201 timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
202 const char *export_filepath = data->export_filepath();
203 fmt::print("USD export of '{}' took ", export_filepath);
204 timeit::print_duration(duration);
205 fmt::print("\n");
206}
207
208static void process_usdz_textures(const ExportJobData *data, const char *path)
209{
210 const eUSDZTextureDownscaleSize enum_value = data->params.usdz_downscale_size;
211 if (enum_value == USD_TEXTURE_SIZE_KEEP) {
212 return;
213 }
214
215 const int image_size = (enum_value == USD_TEXTURE_SIZE_CUSTOM) ?
216 data->params.usdz_downscale_custom_size :
217 enum_value;
218
219 char texture_path[FILE_MAX];
220 STRNCPY(texture_path, path);
221 BLI_path_append(texture_path, FILE_MAX, "textures");
222 BLI_path_slash_ensure(texture_path, sizeof(texture_path));
223
224 direntry *entries;
225 uint num_files = BLI_filelist_dir_contents(texture_path, &entries);
226
227 for (int index = 0; index < num_files; index++) {
228 /* We can skip checking extensions as this folder is only created
229 * when we're doing a USDZ export. */
230 if (!BLI_is_dir(entries[index].path)) {
231 Image *im = BKE_image_load(data->bmain, entries[index].path);
232 if (!im) {
233 CLOG_WARN(&LOG, "Unable to open file for downscaling: %s", entries[index].path);
234 continue;
235 }
236
237 int width, height;
238 BKE_image_get_size(im, nullptr, &width, &height);
239 const int longest = width >= height ? width : height;
240 const float scale = 1.0 / (float(longest) / float(image_size));
241
242 if (longest > image_size) {
243 const int width_adjusted = float(width) * scale;
244 const int height_adjusted = float(height) * scale;
245 BKE_image_scale(im, width_adjusted, height_adjusted, nullptr);
246
247 ImageSaveOptions opts;
248
250 &opts, data->bmain, data->scene, im, nullptr, false, false))
251 {
252 bool result = BKE_image_save(nullptr, data->bmain, im, nullptr, &opts);
253 if (!result) {
255 "Unable to resave '%s' (new size: %dx%d)",
256 data->usdz_filepath,
257 width_adjusted,
258 height_adjusted);
259 }
260 else {
261 CLOG_INFO(&LOG,
262 2,
263 "Downscaled '%s' to %dx%d",
264 entries[index].path,
265 width_adjusted,
266 height_adjusted);
267 }
268 }
269
271 }
272
273 /* Make sure to free the image so it doesn't stick
274 * around in the library of the open file. */
275 BKE_id_free(data->bmain, (void *)im);
276 }
277 }
278
279 BLI_filelist_free(entries, num_files);
280}
281
291{
292 char usdc_temp_dir[FILE_MAX], usdc_file[FILE_MAX];
293 BLI_path_split_dir_file(data->unarchived_filepath,
294 usdc_temp_dir,
295 sizeof(usdc_temp_dir),
296 usdc_file,
297 sizeof(usdc_file));
298
299 char usdz_file[FILE_MAX];
300 BLI_path_split_file_part(data->usdz_filepath, usdz_file, FILE_MAX);
301
302 char original_working_dir_buff[FILE_MAX];
303 const char *original_working_dir = BLI_current_working_dir(original_working_dir_buff,
304 sizeof(original_working_dir_buff));
305 /* Buffer is expected to be returned by #BLI_current_working_dir, although in theory other
306 * returns are possible on some platforms, this is not handled by this code. */
307 BLI_assert(original_working_dir == original_working_dir_buff);
308
309 BLI_change_working_dir(usdc_temp_dir);
310
311 process_usdz_textures(data, usdc_temp_dir);
312
313 pxr::UsdUtilsCreateNewUsdzPackage(pxr::SdfAssetPath(usdc_file), usdz_file);
314 BLI_change_working_dir(original_working_dir);
315
316 char usdz_temp_full_path[FILE_MAX];
317 BLI_path_join(usdz_temp_full_path, FILE_MAX, usdc_temp_dir, usdz_file);
318
319 int result = 0;
320 if (BLI_exists(data->usdz_filepath)) {
321 result = BLI_delete(data->usdz_filepath, false, false);
322 if (result != 0) {
323 BKE_reportf(data->params.worker_status->reports,
324 RPT_ERROR,
325 "USD Export: Unable to delete existing usdz file %s",
326 data->usdz_filepath);
327 return false;
328 }
329 }
330 result = BLI_path_move(usdz_temp_full_path, data->usdz_filepath);
331 if (result != 0) {
332 BKE_reportf(data->params.worker_status->reports,
333 RPT_ERROR,
334 "USD Export: Couldn't move new usdz file from temporary location %s to %s",
335 usdz_temp_full_path,
336 data->usdz_filepath);
337 return false;
338 }
339
340 return true;
341}
342
344{
345 char dir_path[FILE_MAX];
346 BLI_path_join(dir_path, sizeof(dir_path), BKE_tempdir_session(), "usd", "image_cache");
347 return dir_path;
348}
349
350std::string get_image_cache_file(const std::string &file_name, bool mkdir)
351{
352 std::string dir_path = image_cache_file_path();
353 if (mkdir) {
354 BLI_dir_create_recursive(dir_path.c_str());
355 }
356
357 char file_path[FILE_MAX];
358 BLI_path_join(file_path, sizeof(file_path), dir_path.c_str(), file_name.c_str());
359 return file_path;
360}
361
362std::string cache_image_color(const float color[4])
363{
364 char name[128];
365 SNPRINTF(name,
366 "color_%02d%02d%02d.hdr",
367 int(color[0] * 255),
368 int(color[1] * 255),
369 int(color[2] * 255));
370 std::string file_path = get_image_cache_file(name);
371 if (BLI_exists(file_path.c_str())) {
372 return file_path;
373 }
374
375 ImBuf *ibuf = IMB_allocImBuf(4, 4, 32, IB_float_data);
376 IMB_rectfill(ibuf, color);
377 ibuf->ftype = IMB_FTYPE_RADHDR;
378
379 if (IMB_save_image(ibuf, file_path.c_str(), IB_float_data)) {
380 CLOG_INFO(&LOG, 1, "%s", file_path.c_str());
381 }
382 else {
383 CLOG_ERROR(&LOG, "Can't save %s", file_path.c_str());
384 file_path = "";
385 }
386 IMB_freeImBuf(ibuf);
387
388 return file_path;
389}
390
392 pxr::UsdGeomPointInstancer instancer,
393 const pxr::UsdStageRefPtr &stage,
394 const pxr::SdfPath &wrapper_path,
395 std::vector<pxr::UsdPrim> &proto_list)
396{
397 /* Compute extent of the current point instancer.*/
398 pxr::VtArray<pxr::GfVec3f> extent;
399 instancer.ComputeExtentAtTime(
400 &extent, pxr::UsdTimeCode().Default(), pxr::UsdTimeCode().Default());
401 instancer.CreateExtentAttr().Set(extent);
402
403 pxr::UsdPrim wrapper_prim = stage->GetPrimAtPath(wrapper_path);
404 if (!wrapper_prim || !wrapper_prim.IsValid()) {
405 return;
406 }
407
408 std::string real_path_str;
409
410 for (const pxr::SdfPrimSpecHandle &primSpec : wrapper_prim.GetPrimStack()) {
411 if (!primSpec || !primSpec->HasReferences()) {
412 continue;
413 }
414
415 for (const pxr::SdfReference &ref : primSpec->GetReferenceList().GetPrependedItems()) {
416 if (ref.GetAssetPath().empty() && !ref.GetPrimPath().IsEmpty()) {
417 real_path_str = ref.GetPrimPath().GetString();
418 break;
419 }
420 }
421 if (!real_path_str.empty()) {
422 break;
423 }
424 }
425
426 if (real_path_str.empty()) {
427 CLOG_WARN(&LOG, "No prototype reference found for: %s", wrapper_path.GetText());
428 return;
429 }
430
431 const pxr::SdfPath real_path(real_path_str);
432 pxr::UsdPrim proto_prim = stage->GetPrimAtPath(real_path);
433
434 if (!proto_prim || !proto_prim.IsValid()) {
435 CLOG_WARN(&LOG, "Referenced prototype not found at: %s", real_path.GetText());
436 return;
437 }
438
439 proto_list.push_back(proto_prim);
440 proto_list.push_back(wrapper_prim.GetParent());
441
442 std::string doc_message = fmt::format(
443 "This prim is used as a prototype by the PointInstancer \"{}\" so we override the def "
444 "with an \"over\" so that it isn't imaged in the scene, but is available as a prototype "
445 "that can be referenced.",
446 wrapper_prim.GetName().GetString());
447 proto_prim.SetDocumentation(doc_message);
448
449 /* Check if the proto prim itself is a PointInstancer. */
450 if (proto_prim.IsA<pxr::UsdGeomPointInstancer>()) {
451 pxr::UsdGeomPointInstancer nested_instancer(proto_prim);
452 pxr::SdfPathVector nested_targets;
453 if (nested_instancer.GetPrototypesRel().GetTargets(&nested_targets)) {
454 for (const pxr::SdfPath &nested_wrapper_path : nested_targets) {
456 nested_instancer, stage, nested_wrapper_path, proto_list);
457 }
458 }
459 }
460
461 /* Also check all children of the proto prim for nested PointInstancers. */
462 for (const pxr::UsdPrim &child : proto_prim.GetAllChildren()) {
463 if (child.IsA<pxr::UsdGeomPointInstancer>()) {
464 pxr::UsdGeomPointInstancer nested_instancer(child);
465 pxr::SdfPathVector nested_targets;
466 if (nested_instancer.GetPrototypesRel().GetTargets(&nested_targets)) {
467 for (const pxr::SdfPath &nested_wrapper_path : nested_targets) {
469 nested_instancer, stage, nested_wrapper_path, proto_list);
470 }
471 }
472 }
473 }
474}
475
476pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
477 Depsgraph *depsgraph,
478 const char *filepath)
479{
480 pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath);
481 if (!usd_stage) {
482 return usd_stage;
483 }
484
485 wmJobWorkerStatus *worker_status = params.worker_status;
487 Main *bmain = DEG_get_bmain(depsgraph);
488
489 SubdivModifierDisabler mod_disabler(depsgraph);
490
491 /* If we want to set the subdiv scheme, then we need to the export the mesh
492 * without the subdiv modifier applied. */
493 if (ELEM(params.export_subdiv, USD_SUBDIV_BEST_MATCH, USD_SUBDIV_IGNORE)) {
494 mod_disabler.disable_modifiers();
496 }
497
498 /* This whole `export_to_stage` function is assumed to cover about 80% of the whole export
499 * process, from 0.1f to 0.9f. */
500 worker_status->progress = 0.10f;
501 worker_status->do_update = true;
502
503 usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, double(scene->unit.scale_length));
504 usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") +
506
507 /* Set up the stage for animated data. */
508 if (params.export_animation) {
509 usd_stage->SetTimeCodesPerSecond(FPS);
510 usd_stage->SetStartTimeCode(scene->r.sfra);
511 usd_stage->SetEndTimeCode(scene->r.efra);
512 }
513
514 /* For restoring the current frame after exporting animation is done. */
515 const int orig_frame = scene->r.cfra;
516
517 /* Ensure Python types for invoking hooks are registered. */
519
520 pxr::VtValue upAxis = pxr::VtValue(pxr::UsdGeomTokens->z);
521 if (params.convert_orientation) {
522 if (params.up_axis == IO_AXIS_X) {
523 upAxis = pxr::VtValue(pxr::UsdGeomTokens->x);
524 }
525 else if (params.up_axis == IO_AXIS_Y) {
526 upAxis = pxr::VtValue(pxr::UsdGeomTokens->y);
527 }
528 }
529
530 usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, upAxis);
531
532 const double meters_per_unit = get_meters_per_unit(params);
533 pxr::UsdGeomSetStageMetersPerUnit(usd_stage, meters_per_unit);
534
535 ensure_root_prim(usd_stage, params);
536
537 USDHierarchyIterator iter(bmain, depsgraph, usd_stage, params);
538
539 worker_status->progress = 0.11f;
540 worker_status->do_update = true;
541
542 if (params.export_animation) {
543 /* Writing the animated frames is not 100% of the work, here it's assumed to be 75% of it. */
544 float progress_per_frame = 0.75f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
545
546 for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) {
547 if (G.is_break || worker_status->stop) {
548 break;
549 }
550
551 /* Update the scene for the next frame to render. */
552 scene->r.cfra = int(frame);
553 scene->r.subframe = frame - scene->r.cfra;
555
556 iter.set_export_frame(frame);
557 iter.iterate_and_write();
558
559 worker_status->progress += progress_per_frame;
560 worker_status->do_update = true;
561 }
562 }
563 else {
564 /* If we're not animating, a single iteration over all objects is enough. */
565 iter.iterate_and_write();
566 }
567
568 worker_status->progress = 0.86f;
569 worker_status->do_update = true;
570
571 iter.release_writers();
572
573 if (params.export_shapekeys || params.export_armatures) {
574 iter.process_usd_skel();
575 }
576
577 /* Creating dome lights should be called after writers have
578 * completed, to avoid a name collision when creating the light
579 * prim. */
580 if (params.convert_world_material) {
581 world_material_to_dome_light(params, scene, usd_stage);
582 }
583
584 /* Set the default prim if it doesn't exist */
585 if (!usd_stage->GetDefaultPrim()) {
586 /* Use TraverseAll since it's guaranteed to be depth first and will get the first top level
587 * prim, and is less verbose than getting the PseudoRoot + iterating its children. */
588 for (auto prim : usd_stage->TraverseAll()) {
589 usd_stage->SetDefaultPrim(prim);
590 break;
591 }
592 }
593
594 if (params.use_instancing) {
596 }
597
598 call_export_hooks(usd_stage, depsgraph, params.worker_status->reports);
599
600 worker_status->progress = 0.88f;
601 worker_status->do_update = true;
602
603 /* Finish up by going back to the keyframe that was current before we started. */
604 if (scene->r.cfra != orig_frame) {
605 scene->r.cfra = orig_frame;
607 }
608
609 worker_status->progress = 0.9f;
610 worker_status->do_update = true;
611
612 return usd_stage;
613}
614
615static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
616{
617 ExportJobData *data = static_cast<ExportJobData *>(customdata);
618 data->export_ok = false;
619 data->start_time = timeit::Clock::now();
620
621 G.is_rendering = true;
622 if (data->wm) {
623 WM_set_locked_interface(data->wm, true);
624 }
625 G.is_break = false;
626
627 worker_status->progress = 0.01f;
628 worker_status->do_update = true;
629
630 /* Evaluate the depsgraph for exporting.
631 *
632 * Note that, unlike with its building, this is expected to be safe to perform from worker
633 * thread, since UI is locked during export, so there should not be any more changes in the Main
634 * original data concurrently done from the main thread at this point. All necessary (deferred)
635 * changes are expected to have been triggered and processed during depsgraph building in
636 * #USD_export. */
637 BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
638
639 worker_status->progress = 0.1f;
640 worker_status->do_update = true;
641 data->params.worker_status = worker_status;
642
643 pxr::UsdStageRefPtr usd_stage = export_to_stage(
644 data->params, data->depsgraph, data->unarchived_filepath);
645 if (!usd_stage) {
646 /* This happens when the USD JSON files cannot be found. When that happens,
647 * the USD library doesn't know it has the functionality to write USDA and
648 * USDC files, and creating a new UsdStage fails. */
649 BKE_reportf(worker_status->reports,
650 RPT_ERROR,
651 "USD Export: unable to find suitable USD plugin to write %s",
652 data->unarchived_filepath);
653 return;
654 }
655
656 /* Traverse the point instancer to make sure the prototype referenced by nested point instancers
657 * are also marked as over. */
658 std::vector<pxr::UsdPrim> proto_list;
659 for (const pxr::UsdPrim &prim : usd_stage->Traverse()) {
660 if (!prim.IsA<pxr::UsdGeomPointInstancer>()) {
661 continue;
662 }
663 pxr::UsdGeomPointInstancer instancer(prim);
664 pxr::SdfPathVector targets;
665 if (instancer.GetPrototypesRel().GetTargets(&targets)) {
666 for (const pxr::SdfPath &wrapper_path : targets) {
668 instancer, usd_stage, wrapper_path, proto_list);
669 }
670 }
671 }
672
673 /* The standard way is to mark the point instancer's prototypes as over. Reference in OpenUSD:
674 * https://openusd.org/docs/api/class_usd_geom_point_instancer.html#:~:text=place%20them%20under%20a%20prim%20that%20is%20just%20an%20%22over%22
675 */
676 for (pxr::UsdPrim &proto : proto_list) {
677 proto.SetSpecifier(pxr::SdfSpecifierOver);
678 }
679
680 usd_stage->GetRootLayer()->Save();
681
682 data->export_ok = true;
683 worker_status->progress = 1.0f;
684 worker_status->do_update = true;
685}
686
688{
689 if (!BLI_exists(data->unarchived_filepath)) {
690 return;
691 }
692
693 char dir[FILE_MAX];
694 BLI_path_split_dir_part(data->unarchived_filepath, dir, FILE_MAX);
695
696 char usdc_temp_dir[FILE_MAX];
697 BLI_path_join(usdc_temp_dir, FILE_MAX, BKE_tempdir_session(), "USDZ", SEP_STR);
698
699 BLI_assert_msg(BLI_strcasecmp(dir, usdc_temp_dir) == 0,
700 "USD Export: Attempting to delete directory that doesn't match the expected "
701 "temporary directory for usdz export.");
702 BLI_delete(usdc_temp_dir, true, true);
703}
704
705static void export_endjob(void *customdata)
706{
707 ExportJobData *data = static_cast<ExportJobData *>(customdata);
708
709 DEG_graph_free(data->depsgraph);
710
711 if (data->targets_usdz()) {
712 /* NOTE: call to #perform_usdz_conversion has to be done here instead of the main threaded
713 * worker callback (#export_startjob) because USDZ conversion requires changing the current
714 * working directory. This is not safe to do from a non-main thread. Once the USD library fix
715 * this weird requirement, this call can be moved back at the end of #export_startjob, and not
716 * block the main user interface anymore. */
717 bool usd_conversion_success = perform_usdz_conversion(data);
718 if (!usd_conversion_success) {
719 data->export_ok = false;
720 }
721
723 }
724
725 if (!data->export_ok && BLI_exists(data->unarchived_filepath)) {
726 BLI_delete(data->unarchived_filepath, false, false);
727 }
728
729 G.is_rendering = false;
730 if (data->wm) {
731 WM_set_locked_interface(data->wm, false);
732 }
734}
735
741static void create_temp_path_for_usdz_export(const char *filepath,
743{
744 char usdc_file[FILE_MAX];
745 STRNCPY(usdc_file, BLI_path_basename(filepath));
746
747 if (BLI_path_extension_check(usdc_file, ".usdz")) {
748 BLI_path_extension_replace(usdc_file, sizeof(usdc_file), ".usdc");
749 }
750
751 char usdc_temp_filepath[FILE_MAX];
752 BLI_path_join(usdc_temp_filepath, FILE_MAX, BKE_tempdir_session(), "USDZ", usdc_file);
753
754 STRNCPY(job->unarchived_filepath, usdc_temp_filepath);
755 STRNCPY(job->usdz_filepath, filepath);
756}
757
758static void set_job_filepath(blender::io::usd::ExportJobData *job, const char *filepath)
759{
760 if (BLI_path_extension_check_n(filepath, ".usdz", nullptr)) {
762 return;
763 }
764
765 STRNCPY(job->unarchived_filepath, filepath);
766 job->usdz_filepath[0] = '\0';
767}
768
770 const char *filepath,
771 const USDExportParams *params,
772 bool as_background_job,
774{
776 return false;
777 }
778
779 ViewLayer *view_layer = CTX_data_view_layer(C);
780 Scene *scene = CTX_data_scene(C);
781
782 blender::io::usd::ExportJobData *job = MEM_new<blender::io::usd::ExportJobData>("ExportJobData");
783
784 job->bmain = CTX_data_main(C);
785 job->wm = CTX_wm_manager(C);
786 job->scene = scene;
787 job->export_ok = false;
788 set_job_filepath(job, filepath);
789
790 job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode);
791 job->params = *params;
792
793 /* Construct the depsgraph for exporting.
794 *
795 * Has to be done from main thread currently, as it may affect Main original data (e.g. when
796 * doing deferred update of the view-layers, see #112534 for details). */
797 if (job->params.collection[0]) {
798 Collection *collection = reinterpret_cast<Collection *>(
800 if (!collection) {
802 RPT_ERROR,
803 "USD Export: Unable to find collection '%s'",
804 job->params.collection);
805 return false;
806 }
807
809 }
810 else if (job->params.visible_objects_only) {
812 }
813 else {
815 }
816
817 bool export_ok = false;
818 if (as_background_job) {
819 wmJob *wm_job = WM_jobs_get(
820 job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_USD_EXPORT);
821
822 /* setup job */
823 WM_jobs_customdata_set(wm_job, job, [](void *j) {
824 MEM_delete(static_cast<blender::io::usd::ExportJobData *>(j));
825 });
827 WM_jobs_callbacks(wm_job,
829 nullptr,
830 nullptr,
832
834 }
835 else {
836 wmJobWorkerStatus worker_status = {};
837 /* Use the operator's reports in non-background case. */
838 worker_status.reports = reports;
839
840 blender::io::usd::export_startjob(job, &worker_status);
842 export_ok = job->export_ok;
843
844 MEM_delete(job);
845 }
846
847 return export_ok;
848}
849
851{
852 /* USD 19.11 defines:
853 *
854 * #define PXR_MAJOR_VERSION 0
855 * #define PXR_MINOR_VERSION 19
856 * #define PXR_PATCH_VERSION 11
857 * #define PXR_VERSION 1911
858 *
859 * So the major version is implicit/invisible in the public version number.
860 */
861 return PXR_VERSION;
862}
863
865{
866 double result;
867 switch (params.convert_scene_units) {
869 result = 0.01;
870 break;
872 result = 0.001;
873 break;
875 result = 1000.0;
876 break;
878 result = 0.0254;
879 break;
881 result = 0.3048;
882 break;
884 result = 0.9144;
885 break;
887 result = double(params.custom_meters_per_unit);
888 break;
889 default:
890 result = 1.0;
891 break;
892 }
893
894 return result;
895}
896
897} // namespace blender::io::usd
const char * BKE_blender_version_string(void)
Definition blender.cc:143
wmWindow * CTX_wm_window(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
Image * BKE_image_load(Main *bmain, const char *filepath)
bool BKE_image_scale(Image *image, int width, int height, ImageUser *iuser)
void BKE_image_get_size(Image *image, ImageUser *iuser, int *r_width, int *r_height)
bool BKE_image_save(ReportList *reports, Main *bmain, Image *ima, ImageUser *iuser, const ImageSaveOptions *opts)
bool BKE_image_save_options_init(ImageSaveOptions *opts, Main *bmain, Scene *scene, Image *ima, ImageUser *iuser, const bool guess_path, const bool save_as_render)
Definition image_save.cc:45
void BKE_image_save_options_free(ImageSaveOptions *opts)
ID * BKE_libblock_find_name(Main *bmain, short type, const char *name, const std::optional< Library * > lib=std::nullopt) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition lib_id.cc:1679
void BKE_id_free(Main *bmain, void *idv)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain)
Definition scene.cc:2618
void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph)
Definition scene.cc:2697
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
File and directory operations.
bool BLI_change_working_dir(const char *dir)
Definition storage.cc:63
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:373
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:391
unsigned int BLI_filelist_dir_contents(const char *dirname, struct direntry **r_filelist)
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:456
void BLI_filelist_free(struct direntry *filelist, unsigned int nrentries)
char * BLI_current_working_dir(char *dir, size_t maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:85
int BLI_path_move(const char *path_src, const char *path_dst) ATTR_NONNULL()
#define M_PI
void transpose_m3(float R[3][3])
void mat3_to_eul(float eul[3], const float mat[3][3])
bool mat3_from_axis_conversion(int src_forward, int src_up, int dst_forward, int dst_up, float r_mat[3][3])
MINLINE void mul_v3_fl(float r[3], float f)
size_t BLI_path_append(char *__restrict dst, size_t dst_maxncpy, const char *__restrict file) ATTR_NONNULL(1
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
#define FILE_MAX
bool BLI_path_extension_replace(char *path, size_t path_maxncpy, const char *ext) 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
void BLI_path_split_dir_file(const char *filepath, char *dir, size_t dir_maxncpy, char *file, size_t file_maxncpy) ATTR_NONNULL(1
bool BLI_path_extension_check(const char *path, const char *ext) ATTR_NONNULL(1
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
bool BLI_path_extension_check_n(const char *path,...) ATTR_NONNULL(1) ATTR_SENTINEL(0)
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
int char char int BLI_strcasecmp(const char *s1, const char *s2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
unsigned int uint
#define ELEM(...)
#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
Depsgraph * DEG_graph_new(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode)
Definition depsgraph.cc:278
void DEG_graph_free(Depsgraph *graph)
Definition depsgraph.cc:306
void DEG_graph_build_from_collection(Depsgraph *graph, Collection *collection)
void DEG_graph_build_for_all_objects(Depsgraph *graph)
void DEG_graph_build_from_view_layer(Depsgraph *graph)
Main * DEG_get_bmain(const Depsgraph *graph)
Scene * DEG_get_input_scene(const Depsgraph *graph)
@ ID_GR
Object groups, one object can be in many groups at once.
#define FPS
void IMB_freeImBuf(ImBuf *ibuf)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
void IMB_rectfill(ImBuf *drect, const float col[4])
Definition rectop.cc:979
bool IMB_save_image(ImBuf *ibuf, const char *filepath, const int flags)
Definition writeimage.cc:20
@ IMB_FTYPE_RADHDR
@ IB_float_data
@ IO_AXIS_Y
@ IO_AXIS_Z
@ IO_AXIS_X
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
@ WM_JOB_TYPE_USD_EXPORT
Definition WM_api.hh:1749
@ WM_JOB_PROGRESS
Definition WM_api.hh:1716
#define NC_SCENE
Definition WM_types.hh:375
ReportList * reports
Definition WM_types.hh:1025
#define ND_FRAME
Definition WM_types.hh:431
BMesh const char void * data
BPy_StructRNA * depsgraph
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define LOG(severity)
Definition log.h:32
#define G(x, y, z)
std::string image_cache_file_path()
void world_material_to_dome_light(const USDExportParams &params, const Scene *scene, pxr::UsdStageRefPtr stage)
static void set_job_filepath(blender::io::usd::ExportJobData *job, const char *filepath)
static void collect_point_instancer_prototypes_and_set_extent(pxr::UsdGeomPointInstancer instancer, const pxr::UsdStageRefPtr &stage, const pxr::SdfPath &wrapper_path, std::vector< pxr::UsdPrim > &proto_list)
static bool perform_usdz_conversion(const ExportJobData *data)
@ USD_SCENE_UNITS_CUSTOM
Definition usd.hh:117
@ USD_SCENE_UNITS_MILLIMETERS
Definition usd.hh:121
@ USD_SCENE_UNITS_CENTIMETERS
Definition usd.hh:120
@ USD_SCENE_UNITS_KILOMETERS
Definition usd.hh:119
@ USD_SCENE_UNITS_FEET
Definition usd.hh:123
@ USD_SCENE_UNITS_YARDS
Definition usd.hh:124
@ USD_SCENE_UNITS_INCHES
Definition usd.hh:122
std::string cache_image_color(const float color[4])
static void report_job_duration(const ExportJobData *data)
static bool prim_path_valid(const char *path)
std::string get_image_cache_file(const std::string &file_name, bool mkdir)
static bool export_params_valid(const USDExportParams &params)
static void export_endjob_usdz_cleanup(const ExportJobData *data)
@ USD_SUBDIV_BEST_MATCH
Definition usd.hh:88
@ USD_SUBDIV_IGNORE
Definition usd.hh:81
void process_scene_graph_instances(const USDExportParams &export_params, pxr::UsdStageRefPtr stage)
bool USD_export(const bContext *C, const char *filepath, const USDExportParams *params, bool as_background_job, ReportList *reports)
static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &params)
pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params, Depsgraph *depsgraph, const char *filepath)
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
Definition usd_hook.cc:583
static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
double get_meters_per_unit(const USDExportParams &params)
static void create_temp_path_for_usdz_export(const char *filepath, blender::io::usd::ExportJobData *job)
eUSDZTextureDownscaleSize
Definition usd.hh:97
@ USD_TEXTURE_SIZE_CUSTOM
Definition usd.hh:98
@ USD_TEXTURE_SIZE_KEEP
Definition usd.hh:99
static void process_usdz_textures(const ExportJobData *data, const char *path)
static void export_endjob(void *customdata)
void register_hook_converters()
Definition usd_hook.cc:291
std::chrono::nanoseconds Nanoseconds
Definition BLI_timeit.hh:21
Clock::time_point TimePoint
Definition BLI_timeit.hh:20
void print_duration(Nanoseconds duration)
Definition timeit.cc:45
enum eImbFileType ftype
struct RenderData r
struct UnitSettings unit
const char * export_filepath() const
char collection[MAX_IDPROP_NAME]
Definition usd.hh:180
const char * path
void * BKE_tempdir_session
Definition stubs.c:38
#define SEP_STR
Definition unit.cc:39
void WM_global_reportf(eReportType type, const char *format,...)
void WM_set_locked_interface(wmWindowManager *wm, bool lock)
void WM_jobs_timer(wmJob *wm_job, double time_step, uint note, uint endnote)
Definition wm_jobs.cc:353
void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
Definition wm_jobs.cc:456
wmJob * WM_jobs_get(wmWindowManager *wm, wmWindow *win, const void *owner, const char *name, const eWM_JobFlag flag, const eWM_JobType job_type)
Definition wm_jobs.cc:190
void WM_jobs_callbacks(wmJob *wm_job, wm_jobs_start_callback startjob, void(*initjob)(void *), void(*update)(void *), void(*endjob)(void *))
Definition wm_jobs.cc:365
void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void(*free)(void *customdata))
Definition wm_jobs.cc:337