IO: Import multiple Alembic files at once
Allows to import multiple alembic files in single operator call. When importing multiple alembic files as background job, import progress will be divided by all files, so if `5` files are imported, each file will make progress of `20%`. This can be improved if the job text can be customized to display for example `Import Alembic 1/5` and using 100% progress status display for each file, but that is out of the scope of this pr. The Scene min and max frame are set based on the minimum/maximum frame ranges detected from all files. Pull Request: https://projects.blender.org/blender/blender/pulls/121492
This commit is contained in:
committed by
Jesse Yurkovich
parent
f913fb6159
commit
5e9d19d58b
@@ -600,14 +600,12 @@ static int wm_alembic_import_invoke(bContext *C, wmOperator *op, const wmEvent *
|
||||
|
||||
static int wm_alembic_import_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
|
||||
blender::Vector<std::string> paths = blender::ed::io::paths_from_operator_properties(op->ptr);
|
||||
if (paths.is_empty()) {
|
||||
BKE_report(op->reports, RPT_ERROR, "No filepath given");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
char filepath[FILE_MAX];
|
||||
RNA_string_get(op->ptr, "filepath", filepath);
|
||||
|
||||
const float scale = RNA_float_get(op->ptr, "scale");
|
||||
const bool is_sequence = RNA_boolean_get(op->ptr, "is_sequence");
|
||||
const bool set_frame_range = RNA_boolean_get(op->ptr, "set_frame_range");
|
||||
@@ -615,14 +613,19 @@ static int wm_alembic_import_exec(bContext *C, wmOperator *op)
|
||||
const bool always_add_cache_reader = RNA_boolean_get(op->ptr, "always_add_cache_reader");
|
||||
const bool as_background_job = RNA_boolean_get(op->ptr, "as_background_job");
|
||||
|
||||
int offset = 0;
|
||||
int sequence_len = 1;
|
||||
int sequence_min_frame = std::numeric_limits<int>::max();
|
||||
int sequence_max_frame = std::numeric_limits<int>::min();
|
||||
|
||||
if (is_sequence) {
|
||||
sequence_len = get_sequence_len(filepath, &offset);
|
||||
if (sequence_len < 0) {
|
||||
BKE_report(op->reports, RPT_ERROR, "Unable to determine ABC sequence length");
|
||||
return OPERATOR_CANCELLED;
|
||||
for (const std::string &path : paths) {
|
||||
int offset = 0;
|
||||
int sequence_len = get_sequence_len(path.c_str(), &offset);
|
||||
if (sequence_len < 0) {
|
||||
BKE_report(op->reports, RPT_ERROR, "Unable to determine ABC sequence length");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
sequence_min_frame = std::min(sequence_min_frame, offset);
|
||||
sequence_max_frame = std::max(sequence_max_frame, offset + (sequence_len - 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,16 +635,17 @@ static int wm_alembic_import_exec(bContext *C, wmOperator *op)
|
||||
blender::ed::object::mode_set(C, OB_MODE_OBJECT);
|
||||
}
|
||||
|
||||
AlembicImportParams params = {0};
|
||||
AlembicImportParams params{};
|
||||
params.paths = std::move(paths);
|
||||
params.global_scale = scale;
|
||||
params.sequence_len = sequence_len;
|
||||
params.sequence_offset = offset;
|
||||
params.sequence_min_frame = sequence_min_frame;
|
||||
params.sequence_max_frame = sequence_max_frame;
|
||||
params.is_sequence = is_sequence;
|
||||
params.set_frame_range = set_frame_range;
|
||||
params.validate_meshes = validate_meshes;
|
||||
params.always_add_cache_reader = always_add_cache_reader;
|
||||
|
||||
bool ok = ABC_import(C, filepath, ¶ms, as_background_job);
|
||||
bool ok = ABC_import(C, ¶ms, as_background_job);
|
||||
|
||||
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
|
||||
}
|
||||
@@ -662,7 +666,8 @@ void WM_OT_alembic_import(wmOperatorType *ot)
|
||||
FILE_TYPE_FOLDER | FILE_TYPE_ALEMBIC,
|
||||
FILE_BLENDER,
|
||||
FILE_OPENFILE,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS |
|
||||
WM_FILESEL_DIRECTORY | WM_FILESEL_FILES,
|
||||
FILE_DEFAULTDISPLAY,
|
||||
FILE_SORT_DEFAULT);
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
#include <string>
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct CacheArchiveHandle;
|
||||
struct CacheFileLayer;
|
||||
struct CacheReader;
|
||||
@@ -69,10 +68,13 @@ struct AlembicImportParams {
|
||||
* as what Blender expects (e.g. centimeters instead of meters). */
|
||||
float global_scale;
|
||||
|
||||
/* Number of consecutive files to expect if the cached animation is split in a sequence. */
|
||||
int sequence_len;
|
||||
blender::Vector<std::string> paths;
|
||||
|
||||
/* Last frame number of consecutive files to expect if the cached animation is split in a
|
||||
* sequence. */
|
||||
int sequence_max_frame;
|
||||
/* Start frame of the sequence, offset from 0. */
|
||||
int sequence_offset;
|
||||
int sequence_min_frame;
|
||||
/* True if the cache is split in multiple files. */
|
||||
bool is_sequence;
|
||||
|
||||
@@ -103,7 +105,6 @@ bool ABC_export(struct Scene *scene,
|
||||
bool as_background_job);
|
||||
|
||||
bool ABC_import(struct bContext *C,
|
||||
const char *filepath,
|
||||
const struct AlembicImportParams *params,
|
||||
bool as_background_job);
|
||||
|
||||
@@ -153,7 +154,3 @@ struct CacheReader *CacheReader_open_alembic_object(struct CacheArchiveHandle *h
|
||||
struct Object *object,
|
||||
const char *object_path,
|
||||
bool is_sequence);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -39,9 +39,9 @@ struct ImportSettings {
|
||||
bool is_sequence;
|
||||
bool set_frame_range;
|
||||
|
||||
/* Length and frame offset of file sequences. */
|
||||
int sequence_len;
|
||||
int sequence_offset;
|
||||
/* Min and max frame detected from file sequences. */
|
||||
int sequence_min_frame;
|
||||
int sequence_max_frame;
|
||||
|
||||
/* From MeshSeqCacheModifierData.read_flag */
|
||||
int read_flag;
|
||||
@@ -62,8 +62,8 @@ struct ImportSettings {
|
||||
scale(1.0f),
|
||||
is_sequence(false),
|
||||
set_frame_range(false),
|
||||
sequence_len(1),
|
||||
sequence_offset(0),
|
||||
sequence_min_frame(0),
|
||||
sequence_max_frame(1),
|
||||
read_flag(0),
|
||||
velocity_name(""),
|
||||
velocity_scale(1.0f),
|
||||
|
||||
@@ -424,11 +424,17 @@ struct ImportJobData {
|
||||
ViewLayer *view_layer;
|
||||
wmWindowManager *wm;
|
||||
|
||||
char filepath[1024];
|
||||
ImportSettings settings;
|
||||
|
||||
ArchiveReader *archive;
|
||||
std::vector<AbcObjectReader *> readers;
|
||||
blender::Vector<ArchiveReader *> archives;
|
||||
blender::Vector<AbcObjectReader *> readers;
|
||||
|
||||
blender::Vector<std::string> paths;
|
||||
|
||||
/** Min time read from file import. */
|
||||
chrono_t min_time = std::numeric_limits<chrono_t>::max();
|
||||
/** Max time read from file import. */
|
||||
chrono_t max_time = std::numeric_limits<chrono_t>::min();
|
||||
|
||||
bool *stop;
|
||||
bool *do_update;
|
||||
@@ -444,7 +450,7 @@ struct ImportJobData {
|
||||
static void report_job_duration(const ImportJobData *data)
|
||||
{
|
||||
blender::timeit::Nanoseconds duration = blender::timeit::Clock::now() - data->start_time;
|
||||
std::cout << "Alembic import of '" << data->filepath << "' took ";
|
||||
std::cout << "Alembic import took ";
|
||||
blender::timeit::print_duration(duration);
|
||||
std::cout << '\n';
|
||||
}
|
||||
@@ -459,20 +465,12 @@ static void sort_readers(blender::MutableSpan<AbcObjectReader *> readers)
|
||||
});
|
||||
}
|
||||
|
||||
static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
static void import_file(ImportJobData *data, const char *filepath, float progress_factor)
|
||||
{
|
||||
blender::timeit::TimePoint start_time = blender::timeit::Clock::now();
|
||||
SCOPE_TIMER("Alembic import, objects reading and creation");
|
||||
|
||||
ImportJobData *data = static_cast<ImportJobData *>(user_data);
|
||||
|
||||
data->stop = &worker_status->stop;
|
||||
data->do_update = &worker_status->do_update;
|
||||
data->progress = &worker_status->progress;
|
||||
data->start_time = blender::timeit::Clock::now();
|
||||
|
||||
WM_set_locked_interface(data->wm, true);
|
||||
|
||||
ArchiveReader *archive = ArchiveReader::get(data->bmain, {data->filepath});
|
||||
ArchiveReader *archive = ArchiveReader::get(data->bmain, {filepath});
|
||||
|
||||
if (!archive || !archive->valid()) {
|
||||
data->error_code = ABC_ARCHIVE_FAIL;
|
||||
@@ -481,7 +479,7 @@ static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
}
|
||||
|
||||
CacheFile *cache_file = static_cast<CacheFile *>(
|
||||
BKE_cachefile_add(data->bmain, BLI_path_basename(data->filepath)));
|
||||
BKE_cachefile_add(data->bmain, BLI_path_basename(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
|
||||
@@ -490,58 +488,57 @@ static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
|
||||
cache_file->is_sequence = data->settings.is_sequence;
|
||||
cache_file->scale = data->settings.scale;
|
||||
STRNCPY(cache_file->filepath, data->filepath);
|
||||
STRNCPY(cache_file->filepath, filepath);
|
||||
|
||||
data->archive = archive;
|
||||
data->archives.append(archive);
|
||||
data->settings.cache_file = cache_file;
|
||||
|
||||
*data->do_update = true;
|
||||
*data->progress = 0.05f;
|
||||
*data->progress += 0.05f * progress_factor;
|
||||
|
||||
/* Parse Alembic Archive. */
|
||||
AbcObjectReader::ptr_vector assign_as_parent;
|
||||
visit_object(archive->getTop(), data->readers, data->settings, assign_as_parent);
|
||||
std::vector<AbcObjectReader *> readers{};
|
||||
visit_object(archive->getTop(), readers, data->settings, assign_as_parent);
|
||||
|
||||
/* There shouldn't be any orphans. */
|
||||
BLI_assert(assign_as_parent.empty());
|
||||
|
||||
if (G.is_break) {
|
||||
data->was_cancelled = true;
|
||||
data->readers.extend(readers);
|
||||
return;
|
||||
}
|
||||
|
||||
*data->do_update = true;
|
||||
*data->progress = 0.1f;
|
||||
*data->progress += 0.05f * progress_factor;
|
||||
|
||||
/* Create objects and set scene frame range. */
|
||||
|
||||
/* Sort readers by name: when creating a lot of objects in Blender,
|
||||
* it is much faster if the order is sorted by name. */
|
||||
sort_readers(data->readers);
|
||||
sort_readers(readers);
|
||||
data->readers.extend(readers);
|
||||
|
||||
const float size = float(data->readers.size());
|
||||
size_t i = 0;
|
||||
|
||||
chrono_t min_time = std::numeric_limits<chrono_t>::max();
|
||||
chrono_t max_time = std::numeric_limits<chrono_t>::min();
|
||||
const float size = float(readers.size());
|
||||
|
||||
ISampleSelector sample_sel(0.0);
|
||||
std::vector<AbcObjectReader *>::iterator iter;
|
||||
for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
|
||||
const float read_object_progress_step = (0.6f / size) * progress_factor;
|
||||
for (iter = readers.begin(); iter != readers.end(); ++iter) {
|
||||
AbcObjectReader *reader = *iter;
|
||||
|
||||
if (reader->valid()) {
|
||||
reader->readObjectData(data->bmain, sample_sel);
|
||||
|
||||
min_time = std::min(min_time, reader->minTime());
|
||||
max_time = std::max(max_time, reader->maxTime());
|
||||
data->min_time = std::min(data->min_time, reader->minTime());
|
||||
data->max_time = std::max(data->max_time, reader->maxTime());
|
||||
}
|
||||
else {
|
||||
std::cerr << "Object " << reader->name() << " in Alembic file " << data->filepath
|
||||
std::cerr << "Object " << reader->name() << " in Alembic file " << filepath
|
||||
<< " is invalid.\n";
|
||||
}
|
||||
|
||||
*data->progress = 0.1f + 0.6f * (++i / size);
|
||||
*data->progress += read_object_progress_step;
|
||||
*data->do_update = true;
|
||||
|
||||
if (G.is_break) {
|
||||
@@ -550,23 +547,8 @@ static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
}
|
||||
}
|
||||
|
||||
if (data->settings.set_frame_range) {
|
||||
Scene *scene = data->scene;
|
||||
|
||||
if (data->settings.is_sequence) {
|
||||
scene->r.sfra = data->settings.sequence_offset;
|
||||
scene->r.efra = scene->r.sfra + (data->settings.sequence_len - 1);
|
||||
scene->r.cfra = scene->r.sfra;
|
||||
}
|
||||
else if (min_time < max_time) {
|
||||
scene->r.sfra = int(round(min_time * FPS));
|
||||
scene->r.efra = int(round(max_time * FPS));
|
||||
scene->r.cfra = scene->r.sfra;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup parenthood. */
|
||||
for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
|
||||
for (iter = readers.begin(); iter != readers.end(); ++iter) {
|
||||
const AbcObjectReader *reader = *iter;
|
||||
const AbcObjectReader *parent_reader = reader->parent_reader;
|
||||
Object *ob = reader->object();
|
||||
@@ -580,12 +562,12 @@ static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
}
|
||||
|
||||
/* Setup transformations and constraints. */
|
||||
i = 0;
|
||||
for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
|
||||
const float setup_object_transform_progress_step = (0.3f / size) * progress_factor;
|
||||
for (iter = readers.begin(); iter != readers.end(); ++iter) {
|
||||
AbcObjectReader *reader = *iter;
|
||||
reader->setupObjectTransform(0.0);
|
||||
|
||||
*data->progress = 0.7f + 0.3f * (++i / size);
|
||||
*data->progress += setup_object_transform_progress_step;
|
||||
*data->do_update = true;
|
||||
|
||||
if (G.is_break) {
|
||||
@@ -593,6 +575,51 @@ static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
return;
|
||||
}
|
||||
}
|
||||
blender::timeit::Nanoseconds duration = blender::timeit::Clock::now() - start_time;
|
||||
std::cout << "Alembic import " << filepath << " took ";
|
||||
blender::timeit::print_duration(duration);
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
static void set_frame_range(ImportJobData *data)
|
||||
{
|
||||
if (!data->settings.set_frame_range) {
|
||||
return;
|
||||
}
|
||||
Scene *scene = data->scene;
|
||||
if (data->settings.is_sequence) {
|
||||
scene->r.sfra = data->settings.sequence_min_frame;
|
||||
scene->r.efra = data->settings.sequence_max_frame;
|
||||
scene->r.cfra = scene->r.sfra;
|
||||
}
|
||||
else if (data->min_time < data->max_time) {
|
||||
scene->r.sfra = int(round(data->min_time * FPS));
|
||||
scene->r.efra = int(round(data->max_time * FPS));
|
||||
scene->r.cfra = scene->r.sfra;
|
||||
}
|
||||
}
|
||||
|
||||
static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(user_data);
|
||||
data->stop = &worker_status->stop;
|
||||
data->do_update = &worker_status->do_update;
|
||||
data->progress = &worker_status->progress;
|
||||
data->start_time = blender::timeit::Clock::now();
|
||||
|
||||
WM_set_locked_interface(data->wm, true);
|
||||
float file_progress_factor = 1.0f / float(data->paths.size());
|
||||
for (int idx : data->paths.index_range()) {
|
||||
import_file(data, data->paths[idx].c_str(), file_progress_factor);
|
||||
|
||||
if (G.is_break || data->was_cancelled) {
|
||||
data->was_cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
worker_status->progress = float(idx + 1) * file_progress_factor;
|
||||
}
|
||||
set_frame_range(data);
|
||||
}
|
||||
|
||||
static void import_endjob(void *user_data)
|
||||
@@ -681,14 +708,13 @@ static void import_endjob(void *user_data)
|
||||
static void import_freejob(void *user_data)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(user_data);
|
||||
delete data->archive;
|
||||
for (ArchiveReader *archive : data->archives) {
|
||||
delete archive;
|
||||
}
|
||||
delete data;
|
||||
}
|
||||
|
||||
bool ABC_import(bContext *C,
|
||||
const char *filepath,
|
||||
const AlembicImportParams *params,
|
||||
bool as_background_job)
|
||||
bool ABC_import(bContext *C, const AlembicImportParams *params, bool as_background_job)
|
||||
{
|
||||
/* Using new here since MEM_* functions do not call constructor to properly initialize data. */
|
||||
ImportJobData *job = new ImportJobData();
|
||||
@@ -698,18 +724,17 @@ bool ABC_import(bContext *C,
|
||||
job->view_layer = CTX_data_view_layer(C);
|
||||
job->wm = CTX_wm_manager(C);
|
||||
job->import_ok = false;
|
||||
STRNCPY(job->filepath, filepath);
|
||||
job->paths = params->paths;
|
||||
|
||||
job->settings.scale = params->global_scale;
|
||||
job->settings.is_sequence = params->is_sequence;
|
||||
job->settings.set_frame_range = params->set_frame_range;
|
||||
job->settings.sequence_len = params->sequence_len;
|
||||
job->settings.sequence_offset = params->sequence_offset;
|
||||
job->settings.sequence_min_frame = params->sequence_min_frame;
|
||||
job->settings.sequence_max_frame = params->sequence_max_frame;
|
||||
job->settings.validate_meshes = params->validate_meshes;
|
||||
job->settings.always_add_cache_reader = params->always_add_cache_reader;
|
||||
job->error_code = ABC_NO_ERROR;
|
||||
job->was_cancelled = false;
|
||||
job->archive = nullptr;
|
||||
job->is_background_job = as_background_job;
|
||||
|
||||
G.is_break = false;
|
||||
|
||||
Reference in New Issue
Block a user