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