Make the `Object *` and `Mesh *` parameter of `USD_mesh_topology_changed()` `const`. This function only inspects them, and doesn't need to modify them. No functional changes.
610 lines
16 KiB
C++
610 lines
16 KiB
C++
/* 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 <pxr/usd/usd/stage.h>
|
|
#include <pxr/usd/usdGeom/metrics.h>
|
|
#include <pxr/usd/usdGeom/scope.h>
|
|
#include <pxr/usd/usdGeom/tokens.h>
|
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
|
|
|
#include <iostream>
|
|
|
|
namespace blender::io::usd {
|
|
|
|
static CacheArchiveHandle *handle_from_stage_reader(USDStageReader *reader)
|
|
{
|
|
return reinterpret_cast<CacheArchiveHandle *>(reader);
|
|
}
|
|
|
|
static USDStageReader *stage_reader_from_handle(CacheArchiveHandle *handle)
|
|
{
|
|
return reinterpret_cast<USDStageReader *>(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<CacheObjectPath *>(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;
|
|
|
|
bool *stop;
|
|
bool *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, bool *stop, bool *do_update, float *progress)
|
|
{
|
|
ImportJobData *data = static_cast<ImportJobData *>(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<CacheFile *>(
|
|
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<ImportJobData *>(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<ImportJobData *>(user_data);
|
|
|
|
delete data->archive;
|
|
delete data;
|
|
}
|
|
|
|
} // namespace blender::io::usd
|
|
|
|
using namespace blender::io::usd;
|
|
|
|
void USD_ensure_plugin_path_registered()
|
|
{
|
|
blender::io::usd::ensure_usd_plugin_path_registered();
|
|
}
|
|
|
|
bool USD_import(struct bContext *C,
|
|
const char *filepath,
|
|
const USDImportParams *params,
|
|
bool as_background_job)
|
|
{
|
|
/* 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. */
|
|
bool stop = false, do_update = false;
|
|
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,
|
|
const Object * /* ob */,
|
|
const char **err_str)
|
|
{
|
|
USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(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<USDGeomReader *>(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,
|
|
const Object *ob,
|
|
const Mesh *existing_mesh,
|
|
const double time,
|
|
const char **err_str)
|
|
{
|
|
USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(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<USDPrimReader *>(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<CacheReader *>(usd_reader);
|
|
}
|
|
|
|
void USD_CacheReader_free(CacheReader *reader)
|
|
{
|
|
USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(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)
|
|
{
|
|
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<USDXformReader *>(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);
|
|
}
|