Merge branch 'blender-v4.4-release'
[blender.git] / source / blender / io / usd / intern / usd_capi_export.cc
blob17d55974e0fddc8506ff742a59e7df54c27405b3
1 /* SPDX-FileCopyrightText: 2019 Blender Authors
3 * SPDX-License-Identifier: GPL-2.0-or-later */
5 #include <fmt/core.h>
7 #include "IO_subdiv_disabler.hh"
8 #include "usd.hh"
9 #include "usd_hierarchy_iterator.hh"
10 #include "usd_hook.hh"
11 #include "usd_instancing_utils.hh"
12 #include "usd_light_convert.hh"
13 #include "usd_private.hh"
15 #include <pxr/base/tf/token.h>
16 #include <pxr/pxr.h>
17 #include <pxr/usd/sdf/assetPath.h>
18 #include <pxr/usd/usd/primRange.h>
19 #include <pxr/usd/usd/stage.h>
20 #include <pxr/usd/usdGeom/metrics.h>
21 #include <pxr/usd/usdGeom/tokens.h>
22 #include <pxr/usd/usdGeom/xform.h>
23 #include <pxr/usd/usdGeom/xformCommonAPI.h>
24 #include <pxr/usd/usdUtils/usdzPackage.h>
26 #include "MEM_guardedalloc.h"
28 #include "DEG_depsgraph.hh"
29 #include "DEG_depsgraph_build.hh"
30 #include "DEG_depsgraph_query.hh"
32 #include "DNA_collection_types.h"
33 #include "DNA_scene_types.h"
35 #include "BKE_appdir.hh"
36 #include "BKE_blender_version.h"
37 #include "BKE_context.hh"
38 #include "BKE_global.hh"
39 #include "BKE_image.hh"
40 #include "BKE_image_save.hh"
41 #include "BKE_lib_id.hh"
42 #include "BKE_report.hh"
43 #include "BKE_scene.hh"
45 #include "BLI_fileops.h"
46 #include "BLI_math_matrix.h"
47 #include "BLI_math_rotation.h"
48 #include "BLI_math_vector.h"
49 #include "BLI_path_utils.hh"
50 #include "BLI_string.h"
51 #include "BLI_timeit.hh"
53 #include <IMB_imbuf.hh>
54 #include <IMB_imbuf_types.hh>
56 #include "WM_api.hh"
57 #include "WM_types.hh"
59 #include "CLG_log.h"
60 static CLG_LogRef LOG = {"io.usd"};
62 namespace blender::io::usd {
64 struct ExportJobData {
65 Main *bmain;
66 Depsgraph *depsgraph;
67 wmWindowManager *wm;
68 Scene *scene;
70 /** Unarchived_filepath is used for USDA/USDC/USD export. */
71 char unarchived_filepath[FILE_MAX];
72 char usdz_filepath[FILE_MAX];
73 USDExportParams params;
75 bool export_ok;
76 timeit::TimePoint start_time;
78 bool targets_usdz() const
80 return usdz_filepath[0] != '\0';
83 const char *export_filepath() const
85 if (targets_usdz()) {
86 return usdz_filepath;
88 return unarchived_filepath;
92 /* Returns true if the given prim path is valid, per
93 * the requirements of the prim path manipulation logic
94 * of the exporter. Also returns true if the path is
95 * the empty string. Returns false otherwise. */
96 static bool prim_path_valid(const char *path)
98 BLI_assert(path);
100 if (path[0] == '\0') {
101 /* Empty paths are ignored in the code,
102 * so they can be passed through. */
103 return true;
106 /* Check path syntax. */
107 std::string errMsg;
108 if (!pxr::SdfPath::IsValidPathString(path, &errMsg)) {
109 WM_reportf(RPT_ERROR, "USD Export: invalid path string '%s': %s", path, errMsg.c_str());
110 return false;
113 /* Verify that an absolute prim path can be constructed
114 * from this path string. */
116 pxr::SdfPath sdf_path(path);
117 if (!sdf_path.IsAbsolutePath()) {
118 WM_reportf(RPT_ERROR, "USD Export: path '%s' is not an absolute path", path);
119 return false;
122 if (!sdf_path.IsPrimPath()) {
123 WM_reportf(RPT_ERROR, "USD Export: path string '%s' is not a prim path", path);
124 return false;
127 return true;
131 * Perform validation of export parameter settings.
132 * \return true if the parameters are valid; returns false otherwise.
134 * \warning Do not call from worker thread, only from main thread (i.e. before starting the wmJob).
136 static bool export_params_valid(const USDExportParams &params)
138 bool valid = true;
140 if (!prim_path_valid(params.root_prim_path)) {
141 valid = false;
144 return valid;
148 * Create the root Xform primitive, if the Root Prim path has been set
149 * in the export options. In the future, this function can be extended
150 * to author transforms and additional schema data (e.g., model Kind)
151 * on the root prim.
153 static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &params)
155 if (params.root_prim_path[0] == '\0') {
156 return;
159 pxr::UsdGeomXform root_xf = pxr::UsdGeomXform::Define(stage,
160 pxr::SdfPath(params.root_prim_path));
162 if (!root_xf) {
163 return;
166 pxr::UsdGeomXformCommonAPI xf_api(root_xf.GetPrim());
168 if (!xf_api) {
169 return;
172 if (params.convert_scene_units) {
173 xf_api.SetScale(pxr::GfVec3f(float(1.0 / get_meters_per_unit(params))));
176 if (params.convert_orientation) {
177 float mrot[3][3];
178 mat3_from_axis_conversion(IO_AXIS_Y, IO_AXIS_Z, params.forward_axis, params.up_axis, mrot);
179 transpose_m3(mrot);
181 float eul[3];
182 mat3_to_eul(eul, mrot);
184 /* Convert radians to degrees. */
185 mul_v3_fl(eul, 180.0f / M_PI);
187 xf_api.SetRotate(pxr::GfVec3f(eul[0], eul[1], eul[2]));
190 for (const auto &path : pxr::SdfPath(params.root_prim_path).GetPrefixes()) {
191 auto xform = pxr::UsdGeomXform::Define(stage, path);
192 /* Tag generated primitives to allow filtering on import. */
193 xform.GetPrim().SetCustomDataByKey(pxr::TfToken("Blender:generated"), pxr::VtValue(true));
197 static void report_job_duration(const ExportJobData *data)
199 timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
200 const char *export_filepath = data->export_filepath();
201 fmt::print("USD export of '{}' took ", export_filepath);
202 timeit::print_duration(duration);
203 fmt::print("\n");
206 static void process_usdz_textures(const ExportJobData *data, const char *path)
208 const eUSDZTextureDownscaleSize enum_value = data->params.usdz_downscale_size;
209 if (enum_value == USD_TEXTURE_SIZE_KEEP) {
210 return;
213 const int image_size = (enum_value == USD_TEXTURE_SIZE_CUSTOM) ?
214 data->params.usdz_downscale_custom_size :
215 enum_value;
217 char texture_path[FILE_MAX];
218 STRNCPY(texture_path, path);
219 BLI_path_append(texture_path, FILE_MAX, "textures");
220 BLI_path_slash_ensure(texture_path, sizeof(texture_path));
222 direntry *entries;
223 uint num_files = BLI_filelist_dir_contents(texture_path, &entries);
225 for (int index = 0; index < num_files; index++) {
226 /* We can skip checking extensions as this folder is only created
227 * when we're doing a USDZ export. */
228 if (!BLI_is_dir(entries[index].path)) {
229 Image *im = BKE_image_load(data->bmain, entries[index].path);
230 if (!im) {
231 CLOG_WARN(&LOG, "Unable to open file for downscaling: %s", entries[index].path);
232 continue;
235 int width, height;
236 BKE_image_get_size(im, nullptr, &width, &height);
237 const int longest = width >= height ? width : height;
238 const float scale = 1.0 / (float(longest) / float(image_size));
240 if (longest > image_size) {
241 const int width_adjusted = float(width) * scale;
242 const int height_adjusted = float(height) * scale;
243 BKE_image_scale(im, width_adjusted, height_adjusted, nullptr);
245 ImageSaveOptions opts;
247 if (BKE_image_save_options_init(
248 &opts, data->bmain, data->scene, im, nullptr, false, false))
250 bool result = BKE_image_save(nullptr, data->bmain, im, nullptr, &opts);
251 if (!result) {
252 CLOG_ERROR(&LOG,
253 "Unable to resave '%s' (new size: %dx%d)",
254 data->usdz_filepath,
255 width_adjusted,
256 height_adjusted);
258 else {
259 CLOG_INFO(&LOG,
261 "Downscaled '%s' to %dx%d",
262 entries[index].path,
263 width_adjusted,
264 height_adjusted);
268 BKE_image_save_options_free(&opts);
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);
277 BLI_filelist_free(entries, num_files);
281 * For usdz export, we must first create a usd/a/c file and then covert it to usdz. In Blender's
282 * case, we first create a usdc file in Blender's temporary working directory, and store the path
283 * to the usdc file in `unarchived_filepath`. This function then does the conversion of that usdc
284 * file into usdz.
286 * \return true when the conversion from usdc to usdz is successful.
288 static bool perform_usdz_conversion(const ExportJobData *data)
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));
297 char usdz_file[FILE_MAX];
298 BLI_path_split_file_part(data->usdz_filepath, usdz_file, FILE_MAX);
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);
307 BLI_change_working_dir(usdc_temp_dir);
309 process_usdz_textures(data, usdc_temp_dir);
311 pxr::UsdUtilsCreateNewUsdzPackage(pxr::SdfAssetPath(usdc_file), usdz_file);
312 BLI_change_working_dir(original_working_dir);
314 char usdz_temp_full_path[FILE_MAX];
315 BLI_path_join(usdz_temp_full_path, FILE_MAX, usdc_temp_dir, usdz_file);
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;
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;
338 return true;
341 std::string image_cache_file_path()
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;
348 std::string get_image_cache_file(const std::string &file_name, bool mkdir)
350 std::string dir_path = image_cache_file_path();
351 if (mkdir) {
352 BLI_dir_create_recursive(dir_path.c_str());
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;
360 std::string cache_image_color(const float color[4])
362 char name[128];
363 SNPRINTF(name,
364 "color_%02d%02d%02d.hdr",
365 int(color[0] * 255),
366 int(color[1] * 255),
367 int(color[2] * 255));
368 std::string file_path = get_image_cache_file(name);
369 if (BLI_exists(file_path.c_str())) {
370 return file_path;
373 ImBuf *ibuf = IMB_allocImBuf(4, 4, 32, IB_rectfloat);
374 IMB_rectfill(ibuf, color);
375 ibuf->ftype = IMB_FTYPE_RADHDR;
377 if (IMB_saveiff(ibuf, file_path.c_str(), IB_rectfloat)) {
378 CLOG_INFO(&LOG, 1, "%s", file_path.c_str());
380 else {
381 CLOG_ERROR(&LOG, "Can't save %s", file_path.c_str());
382 file_path = "";
384 IMB_freeImBuf(ibuf);
386 return file_path;
389 pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
390 Depsgraph *depsgraph,
391 const char *filepath)
393 pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath);
394 if (!usd_stage) {
395 return usd_stage;
398 wmJobWorkerStatus *worker_status = params.worker_status;
399 Scene *scene = DEG_get_input_scene(depsgraph);
400 Main *bmain = DEG_get_bmain(depsgraph);
402 SubdivModifierDisabler mod_disabler(depsgraph);
404 /* If we want to set the subdiv scheme, then we need to the export the mesh
405 * without the subdiv modifier applied. */
406 if (ELEM(params.export_subdiv, USD_SUBDIV_BEST_MATCH, USD_SUBDIV_IGNORE)) {
407 mod_disabler.disable_modifiers();
408 BKE_scene_graph_update_tagged(depsgraph, bmain);
411 /* This whole `export_to_stage` function is assumed to cover about 80% of the whole export
412 * process, from 0.1f to 0.9f. */
413 worker_status->progress = 0.10f;
414 worker_status->do_update = true;
416 usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, double(scene->unit.scale_length));
417 usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") +
418 BKE_blender_version_string());
420 /* Set up the stage for animated data. */
421 if (params.export_animation) {
422 usd_stage->SetTimeCodesPerSecond(FPS);
423 usd_stage->SetStartTimeCode(scene->r.sfra);
424 usd_stage->SetEndTimeCode(scene->r.efra);
427 /* For restoring the current frame after exporting animation is done. */
428 const int orig_frame = scene->r.cfra;
430 /* Ensure Python types for invoking hooks are registered. */
431 register_hook_converters();
433 pxr::VtValue upAxis = pxr::VtValue(pxr::UsdGeomTokens->z);
434 if (params.convert_orientation) {
435 if (params.up_axis == IO_AXIS_X) {
436 upAxis = pxr::VtValue(pxr::UsdGeomTokens->x);
438 else if (params.up_axis == IO_AXIS_Y) {
439 upAxis = pxr::VtValue(pxr::UsdGeomTokens->y);
443 usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, upAxis);
445 const double meters_per_unit = get_meters_per_unit(params);
446 pxr::UsdGeomSetStageMetersPerUnit(usd_stage, meters_per_unit);
448 ensure_root_prim(usd_stage, params);
450 USDHierarchyIterator iter(bmain, depsgraph, usd_stage, params);
452 worker_status->progress = 0.11f;
453 worker_status->do_update = true;
455 if (params.export_animation) {
456 /* Writing the animated frames is not 100% of the work, here it's assumed to be 75% of it. */
457 float progress_per_frame = 0.75f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
459 for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) {
460 if (G.is_break || worker_status->stop) {
461 break;
464 /* Update the scene for the next frame to render. */
465 scene->r.cfra = int(frame);
466 scene->r.subframe = frame - scene->r.cfra;
467 BKE_scene_graph_update_for_newframe(depsgraph);
469 iter.set_export_frame(frame);
470 iter.iterate_and_write();
472 worker_status->progress += progress_per_frame;
473 worker_status->do_update = true;
476 else {
477 /* If we're not animating, a single iteration over all objects is enough. */
478 iter.iterate_and_write();
481 worker_status->progress = 0.86f;
482 worker_status->do_update = true;
484 iter.release_writers();
486 if (params.export_shapekeys || params.export_armatures) {
487 iter.process_usd_skel();
490 /* Creating dome lights should be called after writers have
491 * completed, to avoid a name collision when creating the light
492 * prim. */
493 if (params.convert_world_material) {
494 world_material_to_dome_light(params, scene, usd_stage);
497 /* Set the default prim if it doesn't exist */
498 if (!usd_stage->GetDefaultPrim()) {
499 /* Use TraverseAll since it's guaranteed to be depth first and will get the first top level
500 * prim, and is less verbose than getting the PseudoRoot + iterating its children. */
501 for (auto prim : usd_stage->TraverseAll()) {
502 usd_stage->SetDefaultPrim(prim);
503 break;
507 if (params.use_instancing) {
508 process_scene_graph_instances(params, usd_stage);
511 call_export_hooks(usd_stage, depsgraph, params.worker_status->reports);
513 worker_status->progress = 0.88f;
514 worker_status->do_update = true;
516 /* Finish up by going back to the keyframe that was current before we started. */
517 if (scene->r.cfra != orig_frame) {
518 scene->r.cfra = orig_frame;
519 BKE_scene_graph_update_for_newframe(depsgraph);
522 worker_status->progress = 0.9f;
523 worker_status->do_update = true;
525 return usd_stage;
528 static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
530 ExportJobData *data = static_cast<ExportJobData *>(customdata);
531 data->export_ok = false;
532 data->start_time = timeit::Clock::now();
534 G.is_rendering = true;
535 if (data->wm) {
536 WM_set_locked_interface(data->wm, true);
538 G.is_break = false;
540 worker_status->progress = 0.01f;
541 worker_status->do_update = true;
543 /* Evaluate the depsgraph for exporting.
545 * Note that, unlike with its building, this is expected to be safe to perform from worker
546 * thread, since UI is locked during export, so there should not be any more changes in the Main
547 * original data concurrently done from the main thread at this point. All necessary (deferred)
548 * changes are expected to have been triggered and processed during depsgraph building in
549 * #USD_export. */
550 BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
552 worker_status->progress = 0.1f;
553 worker_status->do_update = true;
554 data->params.worker_status = worker_status;
556 pxr::UsdStageRefPtr usd_stage = export_to_stage(
557 data->params, data->depsgraph, data->unarchived_filepath);
558 if (!usd_stage) {
559 /* This happens when the USD JSON files cannot be found. When that happens,
560 * the USD library doesn't know it has the functionality to write USDA and
561 * USDC files, and creating a new UsdStage fails. */
562 BKE_reportf(worker_status->reports,
563 RPT_ERROR,
564 "USD Export: unable to find suitable USD plugin to write %s",
565 data->unarchived_filepath);
566 return;
569 usd_stage->GetRootLayer()->Save();
571 data->export_ok = true;
572 worker_status->progress = 1.0f;
573 worker_status->do_update = true;
576 static void export_endjob_usdz_cleanup(const ExportJobData *data)
578 if (!BLI_exists(data->unarchived_filepath)) {
579 return;
582 char dir[FILE_MAX];
583 BLI_path_split_dir_part(data->unarchived_filepath, dir, FILE_MAX);
585 char usdc_temp_dir[FILE_MAX];
586 BLI_path_join(usdc_temp_dir, FILE_MAX, BKE_tempdir_session(), "USDZ", SEP_STR);
588 BLI_assert_msg(BLI_strcasecmp(dir, usdc_temp_dir) == 0,
589 "USD Export: Attempting to delete directory that doesn't match the expected "
590 "temporary directory for usdz export.");
591 BLI_delete(usdc_temp_dir, true, true);
594 static void export_endjob(void *customdata)
596 ExportJobData *data = static_cast<ExportJobData *>(customdata);
598 DEG_graph_free(data->depsgraph);
600 if (data->targets_usdz()) {
601 /* NOTE: call to #perform_usdz_conversion has to be done here instead of the main threaded
602 * worker callback (#export_startjob) because USDZ conversion requires changing the current
603 * working directory. This is not safe to do from a non-main thread. Once the USD library fix
604 * this weird requirement, this call can be moved back at the end of #export_startjob, and not
605 * block the main user interface anymore. */
606 bool usd_conversion_success = perform_usdz_conversion(data);
607 if (!usd_conversion_success) {
608 data->export_ok = false;
611 export_endjob_usdz_cleanup(data);
614 if (!data->export_ok && BLI_exists(data->unarchived_filepath)) {
615 BLI_delete(data->unarchived_filepath, false, false);
618 G.is_rendering = false;
619 if (data->wm) {
620 WM_set_locked_interface(data->wm, false);
622 report_job_duration(data);
626 * To create a USDZ file, we must first create a `.usd/a/c` file and then covert it to `.usdz`.
627 * The temporary files will be created in Blender's temporary session storage.
628 * The `.usdz` file will then be moved to `job->usdz_filepath`.
630 static void create_temp_path_for_usdz_export(const char *filepath,
631 blender::io::usd::ExportJobData *job)
633 char usdc_file[FILE_MAX];
634 STRNCPY(usdc_file, BLI_path_basename(filepath));
636 if (BLI_path_extension_check(usdc_file, ".usdz")) {
637 BLI_path_extension_replace(usdc_file, sizeof(usdc_file), ".usdc");
640 char usdc_temp_filepath[FILE_MAX];
641 BLI_path_join(usdc_temp_filepath, FILE_MAX, BKE_tempdir_session(), "USDZ", usdc_file);
643 STRNCPY(job->unarchived_filepath, usdc_temp_filepath);
644 STRNCPY(job->usdz_filepath, filepath);
647 static void set_job_filepath(blender::io::usd::ExportJobData *job, const char *filepath)
649 if (BLI_path_extension_check_n(filepath, ".usdz", nullptr)) {
650 create_temp_path_for_usdz_export(filepath, job);
651 return;
654 STRNCPY(job->unarchived_filepath, filepath);
655 job->usdz_filepath[0] = '\0';
658 bool USD_export(const bContext *C,
659 const char *filepath,
660 const USDExportParams *params,
661 bool as_background_job,
662 ReportList *reports)
664 if (!blender::io::usd::export_params_valid(*params)) {
665 return false;
668 ViewLayer *view_layer = CTX_data_view_layer(C);
669 Scene *scene = CTX_data_scene(C);
671 blender::io::usd::ExportJobData *job = static_cast<blender::io::usd::ExportJobData *>(
672 MEM_mallocN(sizeof(blender::io::usd::ExportJobData), "ExportJobData"));
674 job->bmain = CTX_data_main(C);
675 job->wm = CTX_wm_manager(C);
676 job->scene = scene;
677 job->export_ok = false;
678 set_job_filepath(job, filepath);
680 job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode);
681 job->params = *params;
683 /* Construct the depsgraph for exporting.
685 * Has to be done from main thread currently, as it may affect Main original data (e.g. when
686 * doing deferred update of the view-layers, see #112534 for details). */
687 if (job->params.collection[0]) {
688 Collection *collection = reinterpret_cast<Collection *>(
689 BKE_libblock_find_name(job->bmain, ID_GR, job->params.collection));
690 if (!collection) {
691 BKE_reportf(job->params.worker_status->reports,
692 RPT_ERROR,
693 "USD Export: Unable to find collection '%s'",
694 job->params.collection);
695 return false;
698 DEG_graph_build_from_collection(job->depsgraph, collection);
700 else if (job->params.visible_objects_only) {
701 DEG_graph_build_from_view_layer(job->depsgraph);
703 else {
704 DEG_graph_build_for_all_objects(job->depsgraph);
707 bool export_ok = false;
708 if (as_background_job) {
709 wmJob *wm_job = WM_jobs_get(
710 job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_USD_EXPORT);
712 /* setup job */
713 WM_jobs_customdata_set(wm_job, job, MEM_freeN);
714 WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME);
715 WM_jobs_callbacks(wm_job,
716 blender::io::usd::export_startjob,
717 nullptr,
718 nullptr,
719 blender::io::usd::export_endjob);
721 WM_jobs_start(CTX_wm_manager(C), wm_job);
723 else {
724 wmJobWorkerStatus worker_status = {};
725 /* Use the operator's reports in non-background case. */
726 worker_status.reports = reports;
728 blender::io::usd::export_startjob(job, &worker_status);
729 blender::io::usd::export_endjob(job);
730 export_ok = job->export_ok;
732 MEM_freeN(job);
735 return export_ok;
738 int USD_get_version()
740 /* USD 19.11 defines:
742 * #define PXR_MAJOR_VERSION 0
743 * #define PXR_MINOR_VERSION 19
744 * #define PXR_PATCH_VERSION 11
745 * #define PXR_VERSION 1911
747 * So the major version is implicit/invisible in the public version number.
749 return PXR_VERSION;
752 double get_meters_per_unit(const USDExportParams &params)
754 double result;
755 switch (params.convert_scene_units) {
756 case USD_SCENE_UNITS_CENTIMETERS:
757 result = 0.01;
758 break;
759 case USD_SCENE_UNITS_MILLIMETERS:
760 result = 0.001;
761 break;
762 case USD_SCENE_UNITS_KILOMETERS:
763 result = 1000.0;
764 break;
765 case USD_SCENE_UNITS_INCHES:
766 result = 0.0254;
767 break;
768 case USD_SCENE_UNITS_FEET:
769 result = 0.3048;
770 break;
771 case USD_SCENE_UNITS_YARDS:
772 result = 0.9144;
773 break;
774 case USD_SCENE_UNITS_CUSTOM:
775 result = double(params.custom_meters_per_unit);
776 break;
777 default:
778 result = 1.0;
779 break;
782 return result;
785 } // namespace blender::io::usd