/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2019 Blender Foundation. All rights reserved. */ #include "IO_types.h" #include "usd.h" #include "usd_common.h" #include "usd_hierarchy_iterator.h" #include "usd_reader_geom.h" #include "usd_reader_prim.h" #include "usd_reader_stage.h" #include "BKE_appdir.h" #include "BKE_blender_version.h" #include "BKE_cachefile.h" #include "BKE_cdderivedmesh.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_library.h" #include "BKE_main.h" #include "BKE_node.h" #include "BKE_object.h" #include "BKE_scene.h" #include "BKE_world.h" #include "BLI_fileops.h" #include "BLI_listbase.h" #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_timeit.hh" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" #include "DEG_depsgraph_query.h" #include "DNA_cachefile_types.h" #include "DNA_collection_types.h" #include "DNA_node_types.h" #include "DNA_scene_types.h" #include "DNA_world_types.h" #include "MEM_guardedalloc.h" #include "WM_api.h" #include "WM_types.h" #include #include #include #include #include #include namespace blender::io::usd { static CacheArchiveHandle *handle_from_stage_reader(USDStageReader *reader) { return reinterpret_cast(reader); } static USDStageReader *stage_reader_from_handle(CacheArchiveHandle *handle) { return reinterpret_cast(handle); } static bool gather_objects_paths(const pxr::UsdPrim &object, ListBase *object_paths) { if (!object.IsValid()) { return false; } for (const pxr::UsdPrim &childPrim : object.GetChildren()) { gather_objects_paths(childPrim, object_paths); } void *usd_path_void = MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath"); CacheObjectPath *usd_path = static_cast(usd_path_void); BLI_strncpy(usd_path->path, object.GetPrimPath().GetString().c_str(), sizeof(usd_path->path)); BLI_addtail(object_paths, usd_path); return true; } /* Update the given import settings with the global rotation matrix to orient * imported objects with Z-up, if necessary */ static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings) { if (!stage || pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z) { return; } if (!r_settings) { return; } r_settings->do_convert_mat = true; /* Rotate 90 degrees about the X-axis. */ float rmat[3][3]; float axis[3] = {1.0f, 0.0f, 0.0f}; axis_angle_normalized_to_mat3(rmat, axis, M_PI_2); unit_m4(r_settings->conversion_mat); copy_m4_m3(r_settings->conversion_mat, rmat); } enum { USD_NO_ERROR = 0, USD_ARCHIVE_FAIL, }; struct ImportJobData { Main *bmain; Scene *scene; ViewLayer *view_layer; wmWindowManager *wm; char filepath[1024]; USDImportParams params; ImportSettings settings; USDStageReader *archive; short *stop; short *do_update; float *progress; char error_code; bool was_canceled; bool import_ok; timeit::TimePoint start_time; }; static void report_job_duration(const ImportJobData *data) { timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time; std::cout << "USD import of '" << data->filepath << "' took "; timeit::print_duration(duration); std::cout << '\n'; } static void import_startjob(void *customdata, short *stop, short *do_update, float *progress) { ImportJobData *data = static_cast(customdata); data->stop = stop; data->do_update = do_update; data->progress = progress; data->was_canceled = false; data->archive = nullptr; data->start_time = timeit::Clock::now(); WM_set_locked_interface(data->wm, true); G.is_break = false; if (data->params.create_collection) { char display_name[1024]; BLI_path_to_display_name( display_name, strlen(data->filepath), BLI_path_basename(data->filepath)); Collection *import_collection = BKE_collection_add( data->bmain, data->scene->master_collection, display_name); id_fake_user_set(&import_collection->id); DEG_id_tag_update(&import_collection->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(data->bmain); WM_main_add_notifier(NC_SCENE | ND_LAYER, nullptr); data->view_layer->active_collection = BKE_layer_collection_first_from_scene_collection( data->view_layer, import_collection); } BLI_path_abs(data->filepath, BKE_main_blendfile_path_from_global()); CacheFile *cache_file = static_cast( BKE_cachefile_add(data->bmain, BLI_path_basename(data->filepath))); /* Decrement the ID ref-count because it is going to be incremented for each * modifier and constraint that it will be attached to, so since currently * it is not used by anyone, its use count will off by one. */ id_us_min(&cache_file->id); cache_file->is_sequence = data->params.is_sequence; cache_file->scale = data->params.scale; STRNCPY(cache_file->filepath, data->filepath); data->settings.cache_file = cache_file; *data->do_update = true; *data->progress = 0.05f; if (G.is_break) { data->was_canceled = true; return; } *data->do_update = true; *data->progress = 0.1f; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(data->filepath); if (!stage) { WM_reportf(RPT_ERROR, "USD Import: unable to open stage to read %s", data->filepath); data->import_ok = false; return; } convert_to_z_up(stage, &data->settings); /* Set up the stage for animated data. */ if (data->params.set_frame_range) { data->scene->r.sfra = stage->GetStartTimeCode(); data->scene->r.efra = stage->GetEndTimeCode(); } *data->do_update = true; *data->progress = 0.15f; USDStageReader *archive = new USDStageReader(stage, data->params, data->settings); data->archive = archive; archive->collect_readers(data->bmain); *data->do_update = true; *data->progress = 0.2f; const float size = float(archive->readers().size()); size_t i = 0; /* Sort readers by name: when creating a lot of objects in Blender, * it is much faster if the order is sorted by name. */ archive->sort_readers(); *data->do_update = true; *data->progress = 0.25f; /* Create blender objects. */ for (USDPrimReader *reader : archive->readers()) { if (!reader) { continue; } reader->create_object(data->bmain, 0.0); if ((++i & 1023) == 0) { *data->do_update = true; *data->progress = 0.25f + 0.25f * (i / size); } } /* Setup parenthood and read actual object data. */ i = 0; for (USDPrimReader *reader : archive->readers()) { if (!reader) { continue; } Object *ob = reader->object(); reader->read_object_data(data->bmain, 0.0); USDPrimReader *parent = reader->parent(); if (parent == nullptr) { ob->parent = nullptr; } else { ob->parent = parent->object(); } *data->progress = 0.5f + 0.5f * (++i / size); *data->do_update = true; if (G.is_break) { data->was_canceled = true; return; } } data->import_ok = !data->was_canceled; *progress = 1.0f; *do_update = true; } static void import_endjob(void *customdata) { ImportJobData *data = static_cast(customdata); /* Delete objects on cancellation. */ if (data->was_canceled && data->archive) { for (USDPrimReader *reader : data->archive->readers()) { if (!reader) { continue; } /* It's possible that cancellation occurred between the creation of * the reader and the creation of the Blender object. */ if (Object *ob = reader->object()) { BKE_id_free_us(data->bmain, ob); } } } else if (data->archive) { Base *base; LayerCollection *lc; const Scene *scene = data->scene; ViewLayer *view_layer = data->view_layer; BKE_view_layer_base_deselect_all(scene, view_layer); lc = BKE_layer_collection_get_active(view_layer); /* Add all objects to the collection. */ for (USDPrimReader *reader : data->archive->readers()) { if (!reader) { continue; } Object *ob = reader->object(); if (!ob) { continue; } BKE_collection_object_add(data->bmain, lc->collection, ob); } /* Sync and do the view layer operations. */ BKE_view_layer_synced_ensure(scene, view_layer); for (USDPrimReader *reader : data->archive->readers()) { if (!reader) { continue; } Object *ob = reader->object(); if (!ob) { continue; } base = BKE_view_layer_base_find(view_layer, ob); /* TODO: is setting active needed? */ BKE_view_layer_base_select_and_set_active(view_layer, base); DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE); DEG_id_tag_update_ex(data->bmain, &ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION | ID_RECALC_BASE_FLAGS); } DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); DEG_relations_tag_update(data->bmain); } WM_set_locked_interface(data->wm, false); switch (data->error_code) { default: case USD_NO_ERROR: data->import_ok = !data->was_canceled; break; case USD_ARCHIVE_FAIL: WM_report(RPT_ERROR, "Could not open USD archive for reading! See console for detail."); break; } WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); report_job_duration(data); } static void import_freejob(void *user_data) { ImportJobData *data = static_cast(user_data); delete data->archive; delete data; } } // namespace blender::io::usd using namespace blender::io::usd; bool USD_import(struct bContext *C, const char *filepath, const USDImportParams *params, bool as_background_job) { blender::io::usd::ensure_usd_plugin_path_registered(); /* Using new here since `MEM_*` functions do not call constructor to properly initialize data. */ ImportJobData *job = new ImportJobData(); job->bmain = CTX_data_main(C); job->scene = CTX_data_scene(C); job->view_layer = CTX_data_view_layer(C); job->wm = CTX_wm_manager(C); job->import_ok = false; BLI_strncpy(job->filepath, filepath, 1024); job->settings.scale = params->scale; job->settings.sequence_offset = params->offset; job->settings.is_sequence = params->is_sequence; job->settings.sequence_len = params->sequence_len; job->settings.validate_meshes = params->validate_meshes; job->settings.sequence_len = params->sequence_len; job->error_code = USD_NO_ERROR; job->was_canceled = false; job->archive = nullptr; job->params = *params; G.is_break = false; bool import_ok = false; if (as_background_job) { wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), job->scene, "USD Import", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC); /* setup job */ WM_jobs_customdata_set(wm_job, job, import_freejob); WM_jobs_timer(wm_job, 0.1, NC_SCENE, NC_SCENE); WM_jobs_callbacks(wm_job, import_startjob, nullptr, nullptr, import_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 importing. */ short stop = 0, do_update = 0; float progress = 0.0f; import_startjob(job, &stop, &do_update, &progress); import_endjob(job); import_ok = job->import_ok; import_freejob(job); } return import_ok; } /* TODO(makowalski): Extend this function with basic validation that the * USD reader is compatible with the type of the given (currently unused) 'ob' * Object parameter, similar to the logic in get_abc_reader() in the * Alembic importer code. */ static USDPrimReader *get_usd_reader(CacheReader *reader, Object * /* ob */, const char **err_str) { USDPrimReader *usd_reader = reinterpret_cast(reader); pxr::UsdPrim iobject = usd_reader->prim(); if (!iobject.IsValid()) { *err_str = "Invalid object: verify object path"; return nullptr; } return usd_reader; } struct Mesh *USD_read_mesh(struct CacheReader *reader, struct Object *ob, struct Mesh *existing_mesh, const double time, const char **err_str, const int read_flag) { USDGeomReader *usd_reader = dynamic_cast(get_usd_reader(reader, ob, err_str)); if (usd_reader == nullptr) { return nullptr; } return usd_reader->read_mesh(existing_mesh, time, read_flag, err_str); } bool USD_mesh_topology_changed( CacheReader *reader, Object *ob, Mesh *existing_mesh, const double time, const char **err_str) { USDGeomReader *usd_reader = dynamic_cast(get_usd_reader(reader, ob, err_str)); if (usd_reader == nullptr) { return false; } return usd_reader->topology_changed(existing_mesh, time); } void USD_CacheReader_incref(CacheReader *reader) { USDPrimReader *usd_reader = reinterpret_cast(reader); usd_reader->incref(); } CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle, CacheReader *reader, Object *object, const char *object_path) { if (object_path[0] == '\0') { return reader; } USDStageReader *archive = stage_reader_from_handle(handle); if (!archive || !archive->valid()) { return reader; } pxr::UsdPrim prim = archive->stage()->GetPrimAtPath(pxr::SdfPath(object_path)); if (reader) { USD_CacheReader_free(reader); } /* TODO(makowalski): The handle does not have the proper import params or settings. */ USDPrimReader *usd_reader = archive->create_reader(prim); if (usd_reader == nullptr) { /* This object is not supported. */ return nullptr; } usd_reader->object(object); usd_reader->incref(); return reinterpret_cast(usd_reader); } void USD_CacheReader_free(CacheReader *reader) { USDPrimReader *usd_reader = reinterpret_cast(reader); usd_reader->decref(); if (usd_reader->refcount() == 0) { delete usd_reader; } } CacheArchiveHandle *USD_create_handle(struct Main * /*bmain*/, const char *filepath, ListBase *object_paths) { /* Must call this so that USD file format plugins are loaded. */ ensure_usd_plugin_path_registered(); pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(filepath); if (!stage) { return nullptr; } USDImportParams params{}; blender::io::usd::ImportSettings settings{}; convert_to_z_up(stage, &settings); USDStageReader *stage_reader = new USDStageReader(stage, params, settings); if (object_paths) { gather_objects_paths(stage->GetPseudoRoot(), object_paths); } return handle_from_stage_reader(stage_reader); } void USD_free_handle(CacheArchiveHandle *handle) { USDStageReader *stage_reader = stage_reader_from_handle(handle); delete stage_reader; } void USD_get_transform(struct CacheReader *reader, float r_mat_world[4][4], float time, float scale) { if (!reader) { return; } USDXformReader *usd_reader = reinterpret_cast(reader); bool is_constant = false; /* Convert from the local matrix we obtain from USD to world coordinates * for Blender. This conversion is done here rather than by Blender due to * work around the non-standard interpretation of CONSTRAINT_SPACE_LOCAL in * BKE_constraint_mat_convertspace(). */ Object *object = usd_reader->object(); if (object->parent == nullptr) { /* No parent, so local space is the same as world space. */ usd_reader->read_matrix(r_mat_world, time, scale, &is_constant); return; } float mat_parent[4][4]; BKE_object_get_parent_matrix(object, object->parent, mat_parent); float mat_local[4][4]; usd_reader->read_matrix(mat_local, time, scale, &is_constant); mul_m4_m4m4(r_mat_world, mat_parent, object->parentinv); mul_m4_m4m4(r_mat_world, r_mat_world, mat_local); }