/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2019 Blender Foundation. All rights reserved. */ #include "usd.h" #include "usd_common.h" #include "usd_hierarchy_iterator.h" #include #include #include #include #include "MEM_guardedalloc.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" #include "DEG_depsgraph_query.h" #include "DNA_scene_types.h" #include "BKE_appdir.h" #include "BKE_blender_version.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_scene.h" #include "BLI_fileops.h" #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_timeit.hh" #include "WM_api.h" #include "WM_types.h" namespace blender::io::usd { struct ExportJobData { Main *bmain; Depsgraph *depsgraph; wmWindowManager *wm; char filepath[FILE_MAX]; USDExportParams params; bool export_ok; timeit::TimePoint start_time; }; static void report_job_duration(const ExportJobData *data) { timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time; std::cout << "USD export of '" << data->filepath << "' took "; timeit::print_duration(duration); std::cout << '\n'; } static void export_startjob(void *customdata, /* Cannot be const, this function implements wm_jobs_start_callback. * NOLINTNEXTLINE: readability-non-const-parameter. */ bool *stop, bool *do_update, float *progress) { ExportJobData *data = static_cast(customdata); data->export_ok = false; data->start_time = timeit::Clock::now(); G.is_rendering = true; WM_set_locked_interface(data->wm, true); G.is_break = false; /* Construct the depsgraph for exporting. */ Scene *scene = DEG_get_input_scene(data->depsgraph); if (data->params.visible_objects_only) { DEG_graph_build_from_view_layer(data->depsgraph); } else { DEG_graph_build_for_all_objects(data->depsgraph); } BKE_scene_graph_update_tagged(data->depsgraph, data->bmain); *progress = 0.0f; *do_update = true; /* For restoring the current frame after exporting animation is done. */ const int orig_frame = scene->r.cfra; pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filepath); if (!usd_stage) { /* This happens when the USD JSON files cannot be found. When that happens, * the USD library doesn't know it has the functionality to write USDA and * USDC files, and creating a new UsdStage fails. */ WM_reportf( RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filepath); return; } usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z)); usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, double(scene->unit.scale_length)); usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") + BKE_blender_version_string()); /* Set up the stage for animated data. */ if (data->params.export_animation) { usd_stage->SetTimeCodesPerSecond(FPS); usd_stage->SetStartTimeCode(scene->r.sfra); usd_stage->SetEndTimeCode(scene->r.efra); } USDHierarchyIterator iter(data->bmain, data->depsgraph, usd_stage, data->params); if (data->params.export_animation) { /* Writing the animated frames is not 100% of the work, but it's our best guess. */ float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1)); for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) { if (G.is_break || (stop != nullptr && *stop)) { break; } /* Update the scene for the next frame to render. */ scene->r.cfra = int(frame); scene->r.subframe = frame - scene->r.cfra; BKE_scene_graph_update_for_newframe(data->depsgraph); iter.set_export_frame(frame); iter.iterate_and_write(); *progress += progress_per_frame; *do_update = true; } } else { /* If we're not animating, a single iteration over all objects is enough. */ iter.iterate_and_write(); } iter.release_writers(); usd_stage->GetRootLayer()->Save(); /* Finish up by going back to the keyframe that was current before we started. */ if (scene->r.cfra != orig_frame) { scene->r.cfra = orig_frame; BKE_scene_graph_update_for_newframe(data->depsgraph); } data->export_ok = true; *progress = 1.0f; *do_update = true; } static void export_endjob(void *customdata) { ExportJobData *data = static_cast(customdata); DEG_graph_free(data->depsgraph); if (!data->export_ok && BLI_exists(data->filepath)) { BLI_delete(data->filepath, false, false); } G.is_rendering = false; WM_set_locked_interface(data->wm, false); report_job_duration(data); } } // namespace blender::io::usd bool USD_export(bContext *C, const char *filepath, const USDExportParams *params, bool as_background_job) { ViewLayer *view_layer = CTX_data_view_layer(C); Scene *scene = CTX_data_scene(C); blender::io::usd::ensure_usd_plugin_path_registered(); blender::io::usd::ExportJobData *job = static_cast( MEM_mallocN(sizeof(blender::io::usd::ExportJobData), "ExportJobData")); job->bmain = CTX_data_main(C); job->wm = CTX_wm_manager(C); job->export_ok = false; BLI_strncpy(job->filepath, filepath, sizeof(job->filepath)); job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode); job->params = *params; bool export_ok = false; if (as_background_job) { wmJob *wm_job = WM_jobs_get( job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC); /* setup job */ WM_jobs_customdata_set(wm_job, job, MEM_freeN); WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); WM_jobs_callbacks(wm_job, blender::io::usd::export_startjob, nullptr, nullptr, blender::io::usd::export_endjob); WM_jobs_start(CTX_wm_manager(C), wm_job); } else { /* Fake a job context, so that we don't need NULL pointer checks while exporting. */ bool stop = false, do_update = false; float progress = 0.0f; blender::io::usd::export_startjob(job, &stop, &do_update, &progress); blender::io::usd::export_endjob(job); export_ok = job->export_ok; MEM_freeN(job); } return export_ok; } int USD_get_version() { /* USD 19.11 defines: * * #define PXR_MAJOR_VERSION 0 * #define PXR_MINOR_VERSION 19 * #define PXR_PATCH_VERSION 11 * #define PXR_VERSION 1911 * * So the major version is implicit/invisible in the public version number. */ return PXR_VERSION; }