Alembic: new exporter based on the USD exporter structure

The Alembic exporter has been restructured by leverages the
`AbstractHierarchyIterator` introduced by the USD exporter. The produced
Alembic files have not changed much (details below), as the Alembic
writing code has simply been moved from the old exporter to the new. How
the export hierarchy is handled changed a lot, though, and also the way
in which transforms are computed. As a result, T71395 is fixed.

Differences between the old and new exporter, in terms of the produced
Alembic file:
- Duplicated objects now have a unique numerical suffix.
- Matrices are computed differently, namely by simply computing the
  evaluated transform of the object relative to the evaluated transform
  of its export-parent. This fixes {T71395}, but otherwise should
  produce the same result as before (but with simpler code).

Compared to the old Alembic exporter, Subdivision modifiers are now
disabled in a cleaner, more efficient way (they are disabled when
exporting with the "Apply Subdivisions" option is unchecked). Previously
the exporter would move to a new frame, disable the modifier, evaluate
the object, and enable the modifier again. This is now done before
exporting starts, and modifiers are only restored when exporting ends.

Some issues with the old Alembic exporter that have NOT been fixed in
this patch:
- Exporting NURBS patches and curves (see T49114 for example).
- Exporting flattened hierarchy in combination with dupli-objects. This
  seems to be broken in the old Alembic exporter as well, but nobody
  reported this yet.

Differential Revision: https://developer.blender.org/D7664

Reviewed By: Sergey
This commit is contained in:
Sybren A. Stüvel 2020-06-19 16:36:10 +02:00
parent a2b7c84ae8
commit 2917df21ad
Notes: blender-bot 2023-02-14 00:18:01 +01:00
Referenced by commit 834e87af7b, Alembic: remove non-functional "Renderable Objects" only option
Referenced by commit a6775efb4f, Alembic exporter: Fix Windows build errors
Referenced by issue #77611, Restructure Alembic exporter based on USD exporter
Referenced by issue #71395, Alembic - exported file does not keep collection offset
37 changed files with 2160 additions and 2287 deletions

View File

@ -30,6 +30,7 @@ set(INC
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/clog
../../../../intern/guardedalloc
../../../../intern/utfconv
)
@ -54,16 +55,17 @@ set(SRC
intern/abc_util.cc
intern/alembic_capi.cc
exporter/abc_archive.cc
exporter/abc_export_capi.cc
exporter/abc_exporter.cc
exporter/abc_writer_archive.cc
exporter/abc_hierarchy_iterator.cc
exporter/abc_subdiv_disabler.cc
exporter/abc_writer_abstract.cc
exporter/abc_writer_camera.cc
exporter/abc_writer_curves.cc
exporter/abc_writer_hair.cc
exporter/abc_writer_mball.cc
exporter/abc_writer_mesh.cc
exporter/abc_writer_mball.cc
exporter/abc_writer_nurbs.cc
exporter/abc_writer_object.cc
exporter/abc_writer_points.cc
exporter/abc_writer_transform.cc
@ -80,15 +82,16 @@ set(SRC
intern/abc_reader_transform.h
intern/abc_util.h
exporter/abc_exporter.h
exporter/abc_writer_archive.h
exporter/abc_archive.h
exporter/abc_hierarchy_iterator.h
exporter/abc_subdiv_disabler.h
exporter/abc_writer_abstract.h
exporter/abc_writer_camera.h
exporter/abc_writer_curves.h
exporter/abc_writer_hair.h
exporter/abc_writer_mball.h
exporter/abc_writer_mesh.h
exporter/abc_writer_mball.h
exporter/abc_writer_nurbs.h
exporter/abc_writer_object.h
exporter/abc_writer_points.h
exporter/abc_writer_transform.h
)

View File

@ -0,0 +1,255 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#include "abc_archive.h"
#include "BKE_blender_version.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.h"
#include "DNA_scene_types.h"
#include <Alembic/AbcCoreOgawa/All.h>
#include <Alembic/AbcGeom/All.h>
namespace blender {
namespace io {
namespace alembic {
using Alembic::Abc::ErrorHandler;
using Alembic::Abc::kWrapExisting;
using Alembic::Abc::MetaData;
using Alembic::Abc::OArchive;
using Alembic::Abc::TimeSampling;
using Alembic::Abc::TimeSamplingPtr;
using Alembic::Abc::TimeSamplingType;
static MetaData create_abc_metadata(const Main *bmain, double scene_fps)
{
MetaData abc_metadata;
std::string abc_user_description(bmain->name);
if (abc_user_description.empty()) {
abc_user_description = "unknown";
}
abc_metadata.set(Alembic::Abc::kApplicationNameKey, "Blender");
abc_metadata.set(Alembic::Abc::kUserDescriptionKey, abc_user_description);
abc_metadata.set("blender_version", std::string("v") + BKE_blender_version_string());
abc_metadata.set("FramesPerTimeUnit", std::to_string(scene_fps));
time_t raw_time;
time(&raw_time);
char buffer[128];
#if defined _WIN32 || defined _WIN64
ctime_s(buffer, 128, &raw_time);
#else
ctime_r(&raw_time, buffer);
#endif
const std::size_t buffer_len = strlen(buffer);
if (buffer_len > 0 && buffer[buffer_len - 1] == '\n') {
buffer[buffer_len - 1] = '\0';
}
abc_metadata.set(Alembic::Abc::kDateWrittenKey, buffer);
return abc_metadata;
}
static OArchive *create_archive(std::ofstream *abc_ostream,
const std::string &filename,
MetaData &abc_metadata)
{
/* Use stream to support unicode character paths on Windows. */
#ifdef WIN32
UTF16_ENCODE(filename);
std::wstring wstr(filename_16);
abc_ostream->open(wstr.c_str(), std::ios::out | std::ios::binary);
UTF16_UN_ENCODE(filename);
#else
abc_ostream->open(filename, std::ios::out | std::ios::binary);
#endif
ErrorHandler::Policy policy = ErrorHandler::kThrowPolicy;
Alembic::AbcCoreOgawa::WriteArchive archive_writer;
return new OArchive(archive_writer(abc_ostream, abc_metadata), kWrapExisting, policy);
}
/* Construct list of shutter samples.
*
* These are taken from the interval [shutter open, shutter close),
* uniformly sampled with 'nr_of_samples' samples.
*
* TODO(Sybren): test that the above interval is indeed half-open.
*
* If 'time_relative' is true, samples are returned as time (in seconds) from params.frame_start.
* If 'time_relative' is false, samples are returned as fractional frames from 0.
* */
static void get_shutter_samples(double scene_fps,
const AlembicExportParams &params,
int nr_of_samples,
bool time_relative,
std::vector<double> &r_samples)
{
int frame_offset = time_relative ? params.frame_start : 0;
double time_factor = time_relative ? scene_fps : 1.0;
double shutter_open = params.shutter_open;
double shutter_close = params.shutter_close;
double time_inc = (shutter_close - shutter_open) / nr_of_samples;
/* sample between shutter open & close */
for (int sample = 0; sample < nr_of_samples; sample++) {
double sample_time = shutter_open + time_inc * sample;
double time = (frame_offset + sample_time) / time_factor;
r_samples.push_back(time);
}
}
static TimeSamplingPtr create_time_sampling(double scene_fps,
const AlembicExportParams &params,
int nr_of_samples)
{
std::vector<double> samples;
if (params.frame_start == params.frame_end) {
return TimeSamplingPtr(new TimeSampling());
}
get_shutter_samples(scene_fps, params, nr_of_samples, true, samples);
TimeSamplingType ts(static_cast<uint32_t>(samples.size()), 1.0 / scene_fps);
return TimeSamplingPtr(new TimeSampling(ts, samples));
}
static void get_frames(double scene_fps,
const AlembicExportParams &params,
unsigned int nr_of_samples,
std::set<double> &r_frames)
{
/* Get one set of shutter samples, then add those around each frame to export. */
std::vector<double> shutter_samples;
get_shutter_samples(scene_fps, params, nr_of_samples, false, shutter_samples);
for (double frame = params.frame_start; frame <= params.frame_end; frame += 1.0) {
for (size_t j = 0; j < nr_of_samples; j++) {
r_frames.insert(frame + shutter_samples[j]);
}
}
}
/* ****************************************************************** */
ABCArchive::ABCArchive(const Main *bmain,
const Scene *scene,
AlembicExportParams params,
std::string filename)
: archive(nullptr)
{
double scene_fps = FPS;
MetaData abc_metadata = create_abc_metadata(bmain, scene_fps);
// Create the Archive.
archive = create_archive(&abc_ostream_, filename, abc_metadata);
// Create time samples for transforms and shapes.
TimeSamplingPtr ts_xform;
TimeSamplingPtr ts_shapes;
ts_xform = create_time_sampling(scene_fps, params, params.frame_samples_xform);
time_sampling_index_transforms_ = archive->addTimeSampling(*ts_xform);
const bool export_animation = params.frame_start != params.frame_end;
if (!export_animation || params.frame_samples_shape == params.frame_samples_xform) {
ts_shapes = ts_xform;
time_sampling_index_shapes_ = time_sampling_index_transforms_;
}
else {
ts_shapes = create_time_sampling(scene_fps, params, params.frame_samples_shape);
time_sampling_index_shapes_ = archive->addTimeSampling(*ts_shapes);
}
// Construct the frames to export.
get_frames(scene_fps, params, params.frame_samples_xform, xform_frames_);
get_frames(scene_fps, params, params.frame_samples_shape, shape_frames_);
// Merge all frames to get the final set of frames to export.
export_frames_.insert(xform_frames_.begin(), xform_frames_.end());
export_frames_.insert(shape_frames_.begin(), shape_frames_.end());
abc_archive_bbox_ = Alembic::AbcGeom::CreateOArchiveBounds(*archive,
time_sampling_index_transforms_);
}
ABCArchive::~ABCArchive()
{
delete archive;
}
uint32_t ABCArchive::time_sampling_index_transforms() const
{
return time_sampling_index_transforms_;
}
uint32_t ABCArchive::time_sampling_index_shapes() const
{
return time_sampling_index_shapes_;
}
ABCArchive::Frames::const_iterator ABCArchive::frames_begin() const
{
return export_frames_.begin();
}
ABCArchive::Frames::const_iterator ABCArchive::frames_end() const
{
return export_frames_.end();
}
size_t ABCArchive::total_frame_count() const
{
return export_frames_.size();
}
bool ABCArchive::is_xform_frame(double frame) const
{
return xform_frames_.find(frame) != xform_frames_.end();
}
bool ABCArchive::is_shape_frame(double frame) const
{
return shape_frames_.find(frame) != shape_frames_.end();
}
ExportSubset ABCArchive::export_subset_for_frame(double frame) const
{
ExportSubset subset;
subset.transforms = is_xform_frame(frame);
subset.shapes = is_shape_frame(frame);
return subset;
}
void ABCArchive::update_bounding_box(const Imath::Box3d &bounds)
{
abc_archive_bbox_.set(bounds);
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -0,0 +1,87 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup Alembic
*/
#pragma once
#include "ABC_alembic.h"
#include "IO_abstract_hierarchy_iterator.h"
#include <Alembic/Abc/OArchive.h>
#include <Alembic/Abc/OTypedScalarProperty.h>
#include <fstream>
#include <set>
#include <string>
struct Main;
struct Scene;
namespace blender {
namespace io {
namespace alembic {
/* Container for an Alembic archive and time sampling info.
*
* Constructor arguments are used to create the correct output stream and to set the archive's
* metadata. */
class ABCArchive {
public:
typedef std::set<double> Frames;
Alembic::Abc::OArchive *archive;
ABCArchive(const Main *bmain,
const Scene *scene,
AlembicExportParams params,
std::string filename);
~ABCArchive();
uint32_t time_sampling_index_transforms() const;
uint32_t time_sampling_index_shapes() const;
Frames::const_iterator frames_begin() const;
Frames::const_iterator frames_end() const;
size_t total_frame_count() const;
bool is_xform_frame(double frame) const;
bool is_shape_frame(double frame) const;
ExportSubset export_subset_for_frame(double frame) const;
void update_bounding_box(const Imath::Box3d &bounds);
private:
std::ofstream abc_ostream_;
uint32_t time_sampling_index_transforms_;
uint32_t time_sampling_index_shapes_;
Frames xform_frames_;
Frames shape_frames_;
Frames export_frames_;
Alembic::Abc::OBox3dProperty abc_archive_bbox_;
};
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -18,18 +18,15 @@
*/
#include "ABC_alembic.h"
#include "abc_writer_camera.h"
#include "abc_writer_curves.h"
#include "abc_writer_hair.h"
#include "abc_writer_mesh.h"
#include "abc_writer_nurbs.h"
#include "abc_writer_points.h"
#include "abc_writer_transform.h"
#include "abc_archive.h"
#include "abc_hierarchy_iterator.h"
#include "abc_subdiv_disabler.h"
#include "MEM_guardedalloc.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
#include "DNA_modifier_types.h"
#include "DNA_scene_types.h"
@ -47,179 +44,163 @@
#include "WM_api.h"
#include "WM_types.h"
using namespace blender::io::alembic;
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
#include <algorithm>
struct ExportJobData {
ViewLayer *view_layer;
Main *bmain;
Depsgraph *depsgraph;
wmWindowManager *wm;
char filename[1024];
ExportSettings settings;
short *stop;
short *do_update;
float *progress;
char filename[FILE_MAX];
AlembicExportParams params;
bool was_canceled;
bool export_ok;
};
namespace blender {
namespace io {
namespace alembic {
// Construct the depsgraph for exporting.
static void build_depsgraph(Depsgraph *depsgraph, Main *bmain)
{
Scene *scene = DEG_get_input_scene(depsgraph);
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph);
DEG_graph_build_from_view_layer(depsgraph, bmain, scene, view_layer);
}
static void export_startjob(void *customdata, short *stop, short *do_update, float *progress)
{
ExportJobData *data = static_cast<ExportJobData *>(customdata);
data->was_canceled = false;
data->stop = stop;
data->do_update = do_update;
data->progress = progress;
/* XXX annoying hack: needed to prevent data corruption when changing
* scene frame in separate threads
*/
G.is_rendering = true;
WM_set_locked_interface(data->wm, true);
G.is_break = false;
DEG_graph_build_from_view_layer(
data->settings.depsgraph, data->bmain, data->settings.scene, data->view_layer);
BKE_scene_graph_update_tagged(data->settings.depsgraph, data->bmain);
*progress = 0.0f;
*do_update = true;
try {
AbcExporter exporter(data->bmain, data->filename, data->settings);
build_depsgraph(data->depsgraph, data->bmain);
SubdivModifierDisabler subdiv_disabler(data->depsgraph);
if (!data->params.apply_subdiv) {
subdiv_disabler.disable_modifiers();
}
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
Scene *scene = data->settings.scene; /* for the CFRA macro */
const int orig_frame = CFRA;
// For restoring the current frame after exporting animation is done.
Scene *scene = DEG_get_input_scene(data->depsgraph);
const int orig_frame = CFRA;
const bool export_animation = (data->params.frame_start != data->params.frame_end);
data->was_canceled = false;
exporter(do_update, progress, &data->was_canceled);
// Create the Alembic archive.
ABCArchive abc_archive(data->bmain, scene, data->params, std::string(data->filename));
if (CFRA != orig_frame) {
CFRA = orig_frame;
ABCHierarchyIterator iter(data->depsgraph, &abc_archive, data->params);
BKE_scene_graph_update_for_newframe(data->settings.depsgraph, data->bmain);
if (export_animation) {
CLOG_INFO(&LOG, 2, "Exporting animation");
// Writing the animated frames is not 100% of the work, but it's our best guess.
const float progress_per_frame = 1.0f / std::max(size_t(1), abc_archive.total_frame_count());
ABCArchive::Frames::const_iterator frame_it = abc_archive.frames_begin();
const ABCArchive::Frames::const_iterator frames_end = abc_archive.frames_end();
for (; frame_it != frames_end; frame_it++) {
double frame = *frame_it;
if (G.is_break || (stop != nullptr && *stop)) {
break;
}
// Update the scene for the next frame to render.
scene->r.cfra = static_cast<int>(frame);
scene->r.subframe = frame - scene->r.cfra;
BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain);
CLOG_INFO(&LOG, 2, "Exporting frame %.2f", frame);
ExportSubset export_subset = abc_archive.export_subset_for_frame(frame);
iter.set_export_subset(export_subset);
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();
}
data->export_ok = !data->was_canceled;
}
catch (const std::exception &e) {
ABC_LOG(data->settings.logger) << "Abc Export error: " << e.what() << '\n';
}
catch (...) {
ABC_LOG(data->settings.logger) << "Abc Export: unknown error...\n";
iter.release_writers();
// Finish up by going back to the keyframe that was current before we started.
if (CFRA != orig_frame) {
CFRA = orig_frame;
BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain);
}
data->export_ok = !data->was_canceled;
*progress = 1.0f;
*do_update = true;
}
static void export_endjob(void *customdata)
{
ExportJobData *data = static_cast<ExportJobData *>(customdata);
DEG_graph_free(data->settings.depsgraph);
DEG_graph_free(data->depsgraph);
if (data->was_canceled && BLI_exists(data->filename)) {
BLI_delete(data->filename, false, false);
}
std::string log = data->settings.logger.str();
if (!log.empty()) {
std::cerr << log;
WM_report(RPT_ERROR, "Errors occurred during the export, look in the console to know more...");
}
G.is_rendering = false;
WM_set_locked_interface(data->wm, false);
}
bool ABC_export(struct Scene *scene,
struct bContext *C,
} // namespace alembic
} // namespace io
} // namespace blender
bool ABC_export(Scene *scene,
bContext *C,
const char *filepath,
const struct AlembicExportParams *params,
const AlembicExportParams *params,
bool as_background_job)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
ExportJobData *job = static_cast<ExportJobData *>(
MEM_mallocN(sizeof(ExportJobData), "ExportJobData"));
job->view_layer = CTX_data_view_layer(C);
job->bmain = CTX_data_main(C);
job->wm = CTX_wm_manager(C);
job->export_ok = false;
BLI_strncpy(job->filename, filepath, 1024);
BLI_strncpy(job->filename, filepath, sizeof(job->filename));
/* Alright, alright, alright....
*
* ExportJobData contains an ExportSettings containing a SimpleLogger.
*
* Since ExportJobData is a C-style struct dynamically allocated with
* MEM_mallocN (see above), its constructor is never called, therefore the
* ExportSettings constructor is not called which implies that the
* SimpleLogger one is not called either. SimpleLogger in turn does not call
* the constructor of its data members which ultimately means that its
* std::ostringstream member has a NULL pointer. To be able to properly use
* the stream's operator<<, the pointer needs to be set, therefore we have
* to properly construct everything. And this is done using the placement
* new operator as here below. It seems hackish, but I'm too lazy to
* do bigger refactor and maybe there is a better way which does not involve
* hardcore refactoring. */
new (&job->settings) ExportSettings();
job->settings.scene = scene;
job->settings.depsgraph = DEG_graph_new(job->bmain, scene, job->view_layer, DAG_EVAL_RENDER);
/* TODO(Sybren): for now we only export the active scene layer.
* Later in the 2.8 development process this may be replaced by using
* a specific collection for Alembic I/O, which can then be toggled
* between "real" objects and cached Alembic files. */
job->settings.view_layer = job->view_layer;
job->settings.frame_start = params->frame_start;
job->settings.frame_end = params->frame_end;
job->settings.frame_samples_xform = params->frame_samples_xform;
job->settings.frame_samples_shape = params->frame_samples_shape;
job->settings.shutter_open = params->shutter_open;
job->settings.shutter_close = params->shutter_close;
/* TODO(Sybren): For now this is ignored, until we can get selection
* detection working through Base pointers (instead of ob->flags). */
job->settings.selected_only = params->selected_only;
job->settings.export_face_sets = params->face_sets;
job->settings.export_normals = params->normals;
job->settings.export_uvs = params->uvs;
job->settings.export_vcols = params->vcolors;
job->settings.export_hair = params->export_hair;
job->settings.export_particles = params->export_particles;
job->settings.apply_subdiv = params->apply_subdiv;
job->settings.curves_as_mesh = params->curves_as_mesh;
job->settings.flatten_hierarchy = params->flatten_hierarchy;
/* TODO(Sybren): visible_layer & renderable only is ignored for now,
* to be replaced with collections later in the 2.8 dev process
* (also see note above). */
job->settings.visible_objects_only = params->visible_objects_only;
job->settings.renderable_only = params->renderable_only;
job->settings.use_subdiv_schema = params->use_subdiv_schema;
job->settings.pack_uv = params->packuv;
job->settings.global_scale = params->global_scale;
job->settings.triangulate = params->triangulate;
job->settings.quad_method = params->quad_method;
job->settings.ngon_method = params->ngon_method;
if (job->settings.frame_start > job->settings.frame_end) {
std::swap(job->settings.frame_start, job->settings.frame_end);
}
job->depsgraph = DEG_graph_new(
job->bmain, scene, view_layer, DAG_EVAL_RENDER /* TODO(Sybren): params->evaluation_mode */);
job->params = *params;
bool export_ok = false;
if (as_background_job) {
wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
CTX_wm_window(C),
job->settings.scene,
"Alembic Export",
WM_JOB_PROGRESS,
WM_JOB_TYPE_ALEMBIC);
wmJob *wm_job = WM_jobs_get(
job->wm, CTX_wm_window(C), scene, "Alembic 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, export_startjob, NULL, NULL, export_endjob);
WM_jobs_callbacks(wm_job,
blender::io::alembic::export_startjob,
NULL,
NULL,
blender::io::alembic::export_endjob);
WM_jobs_start(CTX_wm_manager(C), wm_job);
}
@ -228,8 +209,8 @@ bool ABC_export(struct Scene *scene,
short stop = 0, do_update = 0;
float progress = 0.f;
export_startjob(job, &stop, &do_update, &progress);
export_endjob(job);
blender::io::alembic::export_startjob(job, &stop, &do_update, &progress);
blender::io::alembic::export_endjob(job);
export_ok = job->export_ok;
MEM_freeN(job);

View File

@ -1,681 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup balembic
*/
#include "abc_exporter.h"
#include <cmath>
#include "abc_writer_archive.h"
#include "abc_writer_camera.h"
#include "abc_writer_curves.h"
#include "abc_writer_hair.h"
#include "abc_writer_mball.h"
#include "abc_writer_mesh.h"
#include "abc_writer_nurbs.h"
#include "abc_writer_points.h"
#include "abc_writer_transform.h"
#include "intern/abc_util.h"
#include "DNA_camera_types.h"
#include "DNA_curve_types.h"
#include "DNA_fluid_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h" /* for FILE_MAX */
#include "BLI_string.h"
#ifdef WIN32
/* needed for MSCV because of snprintf from BLI_string */
# include "BLI_winstuff.h"
#endif
#include "BKE_duplilist.h"
#include "BKE_global.h"
#include "BKE_idprop.h"
#include "BKE_layer.h"
#include "BKE_main.h"
#include "BKE_mball.h"
#include "BKE_modifier.h"
#include "BKE_particle.h"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.h"
using Alembic::Abc::OBox3dProperty;
using Alembic::Abc::TimeSamplingPtr;
/* ************************************************************************** */
namespace blender {
namespace io {
namespace alembic {
ExportSettings::ExportSettings()
: scene(NULL),
view_layer(NULL),
depsgraph(NULL),
logger(),
selected_only(false),
visible_objects_only(false),
renderable_only(false),
frame_start(1),
frame_end(1),
frame_samples_xform(1),
frame_samples_shape(1),
shutter_open(0.0),
shutter_close(1.0),
global_scale(1.0f),
flatten_hierarchy(false),
export_normals(false),
export_uvs(false),
export_vcols(false),
export_face_sets(false),
export_vweigths(false),
export_hair(true),
export_particles(true),
apply_subdiv(false),
use_subdiv_schema(false),
export_child_hairs(true),
pack_uv(false),
triangulate(false),
quad_method(0),
ngon_method(0)
{
}
static bool object_is_smoke_sim(Object *ob)
{
ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluid);
if (md) {
FluidModifierData *smd = reinterpret_cast<FluidModifierData *>(md);
return (smd->type == MOD_FLUID_TYPE_DOMAIN && smd->domain &&
smd->domain->type == FLUID_DOMAIN_TYPE_GAS);
}
return false;
}
static bool object_type_is_exportable(Scene *scene, Object *ob)
{
switch (ob->type) {
case OB_MESH:
if (object_is_smoke_sim(ob)) {
return false;
}
return true;
case OB_EMPTY:
case OB_CURVE:
case OB_SURF:
case OB_CAMERA:
return true;
case OB_MBALL:
return AbcMBallWriter::isBasisBall(scene, ob);
default:
return false;
}
}
/**
* Returns whether this object should be exported into the Alembic file.
*
* \param settings: export settings, used for options like 'selected only'.
* \param base: the object's base in question.
* \param is_duplicated: Normally false; true when the object is instanced
* into the scene by a dupli-object (e.g. part of a dupli-group).
* This ignores selection and layer visibility,
* and assumes that the dupli-object itself (e.g. the group-instantiating empty) is exported.
*/
static bool export_object(const ExportSettings *const settings,
const Base *const base,
bool is_duplicated)
{
if (!is_duplicated) {
View3D *v3d = NULL;
/* These two tests only make sense when the object isn't being instanced
* into the scene. When it is, its exportability is determined by
* its dupli-object and the DupliObject::no_draw property. */
if (settings->selected_only && !BASE_SELECTED(v3d, base)) {
return false;
}
// FIXME Sybren: handle these cleanly (maybe just remove code),
// now using active scene layer instead.
if (settings->visible_objects_only && !BASE_VISIBLE(v3d, base)) {
return false;
}
}
Object *ob_eval = DEG_get_evaluated_object(settings->depsgraph, base->object);
if ((ob_eval->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0) {
/* XXX fix after 2.80: the object was not part of the depsgraph, and thus we cannot get the
* evaluated copy to export. This will be handled more elegantly in the new
* AbstractHierarchyIterator that Sybren is working on. This condition is temporary, and avoids
* a BLI_assert() failure getting the evaluated mesh of this object. */
return false;
}
// if (settings->renderable_only && (ob->restrictflag & OB_RESTRICT_RENDER)) {
// return false;
// }
return true;
}
/* ************************************************************************** */
AbcExporter::AbcExporter(Main *bmain, const char *filename, ExportSettings &settings)
: m_bmain(bmain),
m_settings(settings),
m_filename(filename),
m_trans_sampling_index(0),
m_shape_sampling_index(0),
m_writer(NULL)
{
}
AbcExporter::~AbcExporter()
{
/* Free xforms map */
m_xforms_type::iterator it_x, e_x;
for (it_x = m_xforms.begin(), e_x = m_xforms.end(); it_x != e_x; ++it_x) {
delete it_x->second;
}
/* Free shapes vector */
for (int i = 0, e = m_shapes.size(); i != e; i++) {
delete m_shapes[i];
}
delete m_writer;
}
void AbcExporter::getShutterSamples(unsigned int nr_of_samples,
bool time_relative,
std::vector<double> &samples)
{
Scene *scene = m_settings.scene; /* for use in the FPS macro */
samples.clear();
unsigned int frame_offset = time_relative ? m_settings.frame_start : 0;
double time_factor = time_relative ? FPS : 1.0;
double shutter_open = m_settings.shutter_open;
double shutter_close = m_settings.shutter_close;
double time_inc = (shutter_close - shutter_open) / nr_of_samples;
/* sample between shutter open & close */
for (int sample = 0; sample < nr_of_samples; sample++) {
double sample_time = shutter_open + time_inc * sample;
double time = (frame_offset + sample_time) / time_factor;
samples.push_back(time);
}
}
Alembic::Abc::TimeSamplingPtr AbcExporter::createTimeSampling(double step)
{
std::vector<double> samples;
if (m_settings.frame_start == m_settings.frame_end) {
return TimeSamplingPtr(new Alembic::Abc::TimeSampling());
}
getShutterSamples(step, true, samples);
/* TODO(Sybren): shouldn't we use the FPS macro here? */
Alembic::Abc::TimeSamplingType ts(static_cast<uint32_t>(samples.size()),
1.0 / m_settings.scene->r.frs_sec);
return TimeSamplingPtr(new Alembic::Abc::TimeSampling(ts, samples));
}
void AbcExporter::getFrameSet(unsigned int nr_of_samples, std::set<double> &frames)
{
frames.clear();
std::vector<double> shutter_samples;
getShutterSamples(nr_of_samples, false, shutter_samples);
for (double frame = m_settings.frame_start; frame <= m_settings.frame_end; frame += 1.0) {
for (size_t j = 0; j < nr_of_samples; j++) {
frames.insert(frame + shutter_samples[j]);
}
}
}
void AbcExporter::operator()(short *do_update, float *progress, bool *was_canceled)
{
std::string abc_scene_name;
if (m_bmain->name[0] != '\0') {
char scene_file_name[FILE_MAX];
BLI_strncpy(scene_file_name, m_bmain->name, FILE_MAX);
abc_scene_name = scene_file_name;
}
else {
abc_scene_name = "untitled";
}
m_writer = new ArchiveWriter(m_filename, abc_scene_name, m_settings.scene);
/* Create time samplings for transforms and shapes. */
TimeSamplingPtr trans_time = createTimeSampling(m_settings.frame_samples_xform);
m_trans_sampling_index = m_writer->archive().addTimeSampling(*trans_time);
TimeSamplingPtr shape_time;
if ((m_settings.frame_samples_shape == m_settings.frame_samples_xform) ||
(m_settings.frame_start == m_settings.frame_end)) {
shape_time = trans_time;
m_shape_sampling_index = m_trans_sampling_index;
}
else {
shape_time = createTimeSampling(m_settings.frame_samples_shape);
m_shape_sampling_index = m_writer->archive().addTimeSampling(*shape_time);
}
OBox3dProperty archive_bounds_prop = Alembic::AbcGeom::CreateOArchiveBounds(
m_writer->archive(), m_trans_sampling_index);
createTransformWritersHierarchy();
createShapeWriters();
/* Make a list of frames to export. */
std::set<double> xform_frames;
getFrameSet(m_settings.frame_samples_xform, xform_frames);
std::set<double> shape_frames;
getFrameSet(m_settings.frame_samples_shape, shape_frames);
/* Merge all frames needed. */
std::set<double> frames(xform_frames);
frames.insert(shape_frames.begin(), shape_frames.end());
/* Export all frames. */
std::set<double>::const_iterator begin = frames.begin();
std::set<double>::const_iterator end = frames.end();
const float size = static_cast<float>(frames.size());
size_t i = 0;
for (; begin != end; ++begin) {
*progress = (++i / size);
*do_update = 1;
if (G.is_break) {
*was_canceled = true;
break;
}
const double frame = *begin;
/* 'frame' is offset by start frame, so need to cancel the offset. */
setCurrentFrame(m_bmain, frame);
if (shape_frames.count(frame) != 0) {
for (int i = 0, e = m_shapes.size(); i != e; i++) {
m_shapes[i]->write();
}
}
if (xform_frames.count(frame) == 0) {
continue;
}
m_xforms_type::iterator xit, xe;
for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) {
xit->second->write();
}
/* Save the archive 's bounding box. */
Imath::Box3d bounds;
for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) {
Imath::Box3d box = xit->second->bounds();
bounds.extendBy(box);
}
archive_bounds_prop.set(bounds);
}
}
void AbcExporter::createTransformWritersHierarchy()
{
for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base;
base = base->next) {
Object *ob = base->object;
if (export_object(&m_settings, base, false)) {
switch (ob->type) {
case OB_LAMP:
case OB_LATTICE:
case OB_SPEAKER:
/* We do not export transforms for objects of these classes. */
break;
default:
exploreTransform(base, ob, ob->parent, NULL);
}
}
}
}
void AbcExporter::exploreTransform(Base *base,
Object *object,
Object *parent,
Object *dupliObParent)
{
/* If an object isn't exported itself, its duplilist shouldn't be
* exported either. */
if (!export_object(&m_settings, base, dupliObParent != NULL)) {
return;
}
Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object);
if (object_type_is_exportable(m_settings.scene, ob)) {
createTransformWriter(ob, parent, dupliObParent);
}
ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob);
if (lb) {
DupliObject *link = static_cast<DupliObject *>(lb->first);
Object *dupli_ob = NULL;
Object *dupli_parent = NULL;
for (; link; link = link->next) {
/* This skips things like custom bone shapes. */
if (m_settings.renderable_only && link->no_draw) {
continue;
}
if (link->type == OB_DUPLICOLLECTION) {
dupli_ob = link->ob;
dupli_parent = (dupli_ob->parent) ? dupli_ob->parent : ob;
exploreTransform(base, dupli_ob, dupli_parent, ob);
}
}
free_object_duplilist(lb);
}
}
AbcTransformWriter *AbcExporter::createTransformWriter(Object *ob,
Object *parent,
Object *dupliObParent)
{
/* An object should not be its own parent, or we'll get infinite loops. */
BLI_assert(ob != parent);
BLI_assert(ob != dupliObParent);
std::string name;
if (m_settings.flatten_hierarchy) {
name = get_id_name(ob);
}
else {
name = get_object_dag_path_name(ob, dupliObParent);
}
/* check if we have already created a transform writer for this object */
AbcTransformWriter *my_writer = getXForm(name);
if (my_writer != NULL) {
return my_writer;
}
AbcTransformWriter *parent_writer = NULL;
Alembic::Abc::OObject alembic_parent;
if (m_settings.flatten_hierarchy || parent == NULL) {
/* Parentless objects still have the "top object" as parent
* in Alembic. */
alembic_parent = m_writer->archive().getTop();
}
else {
/* Since there are so many different ways to find parents (as evident
* in the number of conditions below), we can't really look up the
* parent by name. We'll just call createTransformWriter(), which will
* return the parent's AbcTransformWriter pointer. */
if (parent->parent) {
if (parent == dupliObParent) {
parent_writer = createTransformWriter(parent, parent->parent, NULL);
}
else {
parent_writer = createTransformWriter(parent, parent->parent, dupliObParent);
}
}
else if (parent == dupliObParent) {
if (dupliObParent->parent == NULL) {
parent_writer = createTransformWriter(parent, NULL, NULL);
}
else {
parent_writer = createTransformWriter(
parent, dupliObParent->parent, dupliObParent->parent);
}
}
else {
parent_writer = createTransformWriter(parent, dupliObParent, dupliObParent);
}
BLI_assert(parent_writer);
alembic_parent = parent_writer->alembicXform();
}
my_writer = new AbcTransformWriter(
ob, alembic_parent, parent_writer, m_trans_sampling_index, m_settings);
/* When flattening, the matrix of the dupliobject has to be added. */
if (m_settings.flatten_hierarchy && dupliObParent) {
my_writer->m_proxy_from = dupliObParent;
}
m_xforms[name] = my_writer;
return my_writer;
}
void AbcExporter::createShapeWriters()
{
for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base;
base = base->next) {
exploreObject(base, base->object, NULL);
}
}
void AbcExporter::exploreObject(Base *base, Object *object, Object *dupliObParent)
{
/* If an object isn't exported itself, its duplilist shouldn't be
* exported either. */
if (!export_object(&m_settings, base, dupliObParent != NULL)) {
return;
}
Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object);
createShapeWriter(ob, dupliObParent);
ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob);
if (lb) {
DupliObject *link = static_cast<DupliObject *>(lb->first);
for (; link; link = link->next) {
/* This skips things like custom bone shapes. */
if (m_settings.renderable_only && link->no_draw) {
continue;
}
if (link->type == OB_DUPLICOLLECTION) {
exploreObject(base, link->ob, ob);
}
}
free_object_duplilist(lb);
}
}
void AbcExporter::createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform)
{
if (!m_settings.export_hair && !m_settings.export_particles) {
return;
}
ParticleSystem *psys = static_cast<ParticleSystem *>(ob->particlesystem.first);
for (; psys; psys = psys->next) {
if (!psys_check_enabled(ob, psys, G.is_rendering) || !psys->part) {
continue;
}
if (m_settings.export_hair && psys->part->type == PART_HAIR) {
m_settings.export_child_hairs = true;
m_shapes.push_back(new AbcHairWriter(ob, xform, m_shape_sampling_index, m_settings, psys));
}
else if (m_settings.export_particles &&
(psys->part->type == PART_EMITTER || psys->part->type == PART_FLUID_FLIP ||
psys->part->type == PART_FLUID_SPRAY || psys->part->type == PART_FLUID_BUBBLE ||
psys->part->type == PART_FLUID_FOAM || psys->part->type == PART_FLUID_TRACER ||
psys->part->type == PART_FLUID_SPRAYFOAM ||
psys->part->type == PART_FLUID_SPRAYBUBBLE ||
psys->part->type == PART_FLUID_FOAMBUBBLE ||
psys->part->type == PART_FLUID_SPRAYFOAMBUBBLE)) {
m_shapes.push_back(new AbcPointsWriter(ob, xform, m_shape_sampling_index, m_settings, psys));
}
}
}
void AbcExporter::createShapeWriter(Object *ob, Object *dupliObParent)
{
if (!object_type_is_exportable(m_settings.scene, ob)) {
return;
}
std::string name;
if (m_settings.flatten_hierarchy) {
name = get_id_name(ob);
}
else {
name = get_object_dag_path_name(ob, dupliObParent);
}
AbcTransformWriter *xform = getXForm(name);
if (!xform) {
ABC_LOG(m_settings.logger) << __func__ << ": xform " << name << " is NULL\n";
return;
}
createParticleSystemsWriters(ob, xform);
switch (ob->type) {
case OB_MESH: {
Mesh *me = static_cast<Mesh *>(ob->data);
if (!me) {
return;
}
m_shapes.push_back(new AbcMeshWriter(ob, xform, m_shape_sampling_index, m_settings));
break;
}
case OB_SURF: {
Curve *cu = static_cast<Curve *>(ob->data);
if (!cu) {
return;
}
AbcObjectWriter *writer;
if (m_settings.curves_as_mesh) {
writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings);
}
else {
writer = new AbcNurbsWriter(ob, xform, m_shape_sampling_index, m_settings);
}
m_shapes.push_back(writer);
break;
}
case OB_CURVE: {
Curve *cu = static_cast<Curve *>(ob->data);
if (!cu) {
return;
}
AbcObjectWriter *writer;
if (m_settings.curves_as_mesh) {
writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings);
}
else {
writer = new AbcCurveWriter(ob, xform, m_shape_sampling_index, m_settings);
}
m_shapes.push_back(writer);
break;
}
case OB_CAMERA: {
Camera *cam = static_cast<Camera *>(ob->data);
if (cam->type == CAM_PERSP) {
m_shapes.push_back(new AbcCameraWriter(ob, xform, m_shape_sampling_index, m_settings));
}
break;
}
case OB_MBALL: {
MetaBall *mball = static_cast<MetaBall *>(ob->data);
if (!mball) {
return;
}
m_shapes.push_back(
new AbcMBallWriter(m_bmain, ob, xform, m_shape_sampling_index, m_settings));
break;
}
}
}
AbcTransformWriter *AbcExporter::getXForm(const std::string &name)
{
std::map<std::string, AbcTransformWriter *>::iterator it = m_xforms.find(name);
if (it == m_xforms.end()) {
return NULL;
}
return it->second;
}
void AbcExporter::setCurrentFrame(Main *bmain, double t)
{
m_settings.scene->r.cfra = static_cast<int>(t);
m_settings.scene->r.subframe = static_cast<float>(t) - m_settings.scene->r.cfra;
BKE_scene_graph_update_for_newframe(m_settings.depsgraph, bmain);
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -1,131 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup balembic
*/
#include <Alembic/Abc/All.h>
#include <map>
#include <set>
#include <vector>
#include "intern/abc_util.h"
struct Base;
struct Depsgraph;
struct Main;
struct Object;
struct Scene;
struct ViewLayer;
namespace blender {
namespace io {
namespace alembic {
class AbcObjectWriter;
class AbcTransformWriter;
class ArchiveWriter;
struct ExportSettings {
ExportSettings();
Scene *scene;
/** Scene layer to export; all its objects will be exported, unless selected_only=true. */
ViewLayer *view_layer;
Depsgraph *depsgraph;
SimpleLogger logger;
bool selected_only;
bool visible_objects_only;
bool renderable_only;
double frame_start, frame_end;
double frame_samples_xform;
double frame_samples_shape;
double shutter_open;
double shutter_close;
float global_scale;
bool flatten_hierarchy;
bool export_normals;
bool export_uvs;
bool export_vcols;
bool export_face_sets;
bool export_vweigths;
bool export_hair;
bool export_particles;
bool apply_subdiv;
bool curves_as_mesh;
bool use_subdiv_schema;
bool export_child_hairs;
bool pack_uv;
bool triangulate;
int quad_method;
int ngon_method;
};
class AbcExporter {
Main *m_bmain;
ExportSettings &m_settings;
const char *m_filename;
unsigned int m_trans_sampling_index, m_shape_sampling_index;
ArchiveWriter *m_writer;
/* mapping from name to transform writer */
typedef std::map<std::string, AbcTransformWriter *> m_xforms_type;
m_xforms_type m_xforms;
std::vector<AbcObjectWriter *> m_shapes;
public:
AbcExporter(Main *bmain, const char *filename, ExportSettings &settings);
~AbcExporter();
void operator()(short *do_update, float *progress, bool *was_canceled);
protected:
void getShutterSamples(unsigned int nr_of_samples,
bool time_relative,
std::vector<double> &samples);
void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames);
private:
Alembic::Abc::TimeSamplingPtr createTimeSampling(double step);
void createTransformWritersHierarchy();
AbcTransformWriter *createTransformWriter(Object *ob, Object *parent, Object *dupliObParent);
void exploreTransform(Base *base, Object *object, Object *parent, Object *dupliObParent);
void exploreObject(Base *base, Object *object, Object *dupliObParent);
void createShapeWriters();
void createShapeWriter(Object *ob, Object *dupliObParent);
void createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform);
AbcTransformWriter *getXForm(const std::string &name);
void setCurrentFrame(Main *bmain, double t);
};
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -0,0 +1,261 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#include "abc_hierarchy_iterator.h"
#include "abc_writer_abstract.h"
#include "abc_writer_camera.h"
#include "abc_writer_curves.h"
#include "abc_writer_hair.h"
#include "abc_writer_mball.h"
#include "abc_writer_mesh.h"
#include "abc_writer_nurbs.h"
#include "abc_writer_points.h"
#include "abc_writer_transform.h"
#include <memory>
#include <string>
#include "BLI_assert.h"
#include "DEG_depsgraph_query.h"
#include "DNA_ID.h"
#include "DNA_layer_types.h"
#include "DNA_object_types.h"
namespace blender {
namespace io {
namespace alembic {
ABCHierarchyIterator::ABCHierarchyIterator(Depsgraph *depsgraph,
ABCArchive *abc_archive,
const AlembicExportParams &params)
: AbstractHierarchyIterator(depsgraph), abc_archive_(abc_archive), params_(params)
{
}
void ABCHierarchyIterator::iterate_and_write()
{
AbstractHierarchyIterator::iterate_and_write();
update_archive_bounding_box();
}
void ABCHierarchyIterator::update_archive_bounding_box()
{
Imath::Box3d bounds;
update_bounding_box_recursive(bounds, HierarchyContext::root());
abc_archive_->update_bounding_box(bounds);
}
void ABCHierarchyIterator::update_bounding_box_recursive(Imath::Box3d &bounds,
const HierarchyContext *context)
{
if (context != nullptr) {
AbstractHierarchyWriter *abstract_writer = writers_[context->export_path];
ABCAbstractWriter *abc_writer = static_cast<ABCAbstractWriter *>(abstract_writer);
if (abc_writer != nullptr) {
bounds.extendBy(abc_writer->bounding_box());
}
}
for (HierarchyContext *child_context : graph_children(context)) {
update_bounding_box_recursive(bounds, child_context);
}
}
bool ABCHierarchyIterator::mark_as_weak_export(const Object *object) const
{
if (params_.selected_only && (object->base_flag & BASE_SELECTED) == 0) {
return true;
}
/* TODO(Sybren): handle other flags too? */
return false;
}
void ABCHierarchyIterator::delete_object_writer(AbstractHierarchyWriter *writer)
{
delete writer;
}
std::string ABCHierarchyIterator::make_valid_name(const std::string &name) const
{
std::string abc_name(name);
std::replace(abc_name.begin(), abc_name.end(), ' ', '_');
std::replace(abc_name.begin(), abc_name.end(), '.', '_');
std::replace(abc_name.begin(), abc_name.end(), ':', '_');
return abc_name;
}
AbstractHierarchyIterator::ExportGraph::key_type ABCHierarchyIterator::
determine_graph_index_object(const HierarchyContext *context)
{
if (params_.flatten_hierarchy) {
return std::make_pair(nullptr, nullptr);
}
return AbstractHierarchyIterator::determine_graph_index_object(context);
}
AbstractHierarchyIterator::ExportGraph::key_type ABCHierarchyIterator::determine_graph_index_dupli(
const HierarchyContext *context, const std::set<Object *> &dupli_set)
{
if (params_.flatten_hierarchy) {
return std::make_pair(nullptr, nullptr);
}
return AbstractHierarchyIterator::determine_graph_index_dupli(context, dupli_set);
}
Alembic::Abc::OObject ABCHierarchyIterator::get_alembic_parent(
const HierarchyContext *context) const
{
Alembic::Abc::OObject parent;
if (!context->higher_up_export_path.empty()) {
AbstractHierarchyWriter *writer = get_writer(context->higher_up_export_path);
ABCAbstractWriter *abc_writer = static_cast<ABCAbstractWriter *>(writer);
parent = abc_writer->get_alembic_object();
}
if (!parent.valid()) {
/* An invalid parent object means "no parent", which should be translated to Alembic's top
* archive object. */
return abc_archive_->archive->getTop();
}
return parent;
}
ABCWriterConstructorArgs ABCHierarchyIterator::writer_constructor_args(
const HierarchyContext *context) const
{
ABCWriterConstructorArgs constructor_args;
constructor_args.depsgraph = depsgraph_;
constructor_args.abc_archive = abc_archive_;
constructor_args.abc_parent = get_alembic_parent(context);
constructor_args.abc_name = context->export_name;
constructor_args.abc_path = context->export_path;
constructor_args.hierarchy_iterator = this;
constructor_args.export_params = &params_;
return constructor_args;
}
AbstractHierarchyWriter *ABCHierarchyIterator::create_transform_writer(
const HierarchyContext *context)
{
ABCAbstractWriter *transform_writer = new ABCTransformWriter(writer_constructor_args(context));
transform_writer->create_alembic_objects(context);
return transform_writer;
}
AbstractHierarchyWriter *ABCHierarchyIterator::create_data_writer(const HierarchyContext *context)
{
const ABCWriterConstructorArgs writer_args = writer_constructor_args(context);
ABCAbstractWriter *data_writer = nullptr;
switch (context->object->type) {
case OB_MESH:
data_writer = new ABCMeshWriter(writer_args);
break;
case OB_CAMERA:
data_writer = new ABCCameraWriter(writer_args);
break;
case OB_CURVE:
if (params_.curves_as_mesh) {
data_writer = new ABCCurveMeshWriter(writer_args);
}
else {
data_writer = new ABCCurveWriter(writer_args);
}
break;
case OB_SURF:
if (params_.curves_as_mesh) {
data_writer = new ABCCurveMeshWriter(writer_args);
}
else {
data_writer = new ABCNurbsWriter(writer_args);
}
break;
case OB_MBALL:
data_writer = new ABCMetaballWriter(writer_args);
break;
case OB_EMPTY:
case OB_LAMP:
case OB_FONT:
case OB_SPEAKER:
case OB_LIGHTPROBE:
case OB_LATTICE:
case OB_ARMATURE:
case OB_GPENCIL:
return nullptr;
case OB_TYPE_MAX:
BLI_assert(!"OB_TYPE_MAX should not be used");
return nullptr;
}
if (!data_writer->is_supported(context)) {
delete data_writer;
return nullptr;
}
data_writer->create_alembic_objects(context);
return data_writer;
}
AbstractHierarchyWriter *ABCHierarchyIterator::create_hair_writer(const HierarchyContext *context)
{
if (!params_.export_hair) {
return nullptr;
}
const ABCWriterConstructorArgs writer_args = writer_constructor_args(context);
ABCAbstractWriter *hair_writer = new ABCHairWriter(writer_args);
if (!hair_writer->is_supported(context)) {
delete hair_writer;
return nullptr;
}
hair_writer->create_alembic_objects(context);
return hair_writer;
}
AbstractHierarchyWriter *ABCHierarchyIterator::create_particle_writer(
const HierarchyContext *context)
{
if (!params_.export_particles) {
return nullptr;
}
const ABCWriterConstructorArgs writer_args = writer_constructor_args(context);
std::unique_ptr<ABCPointsWriter> particle_writer(std::make_unique<ABCPointsWriter>(writer_args));
if (!particle_writer->is_supported(context)) {
return nullptr;
}
particle_writer->create_alembic_objects(context);
return particle_writer.release();
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -0,0 +1,90 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#pragma once
#include "ABC_alembic.h"
#include "abc_archive.h"
#include "IO_abstract_hierarchy_iterator.h"
#include <string>
#include <Alembic/Abc/OArchive.h>
#include <Alembic/Abc/OObject.h>
struct Depsgraph;
struct ID;
struct Object;
namespace blender {
namespace io {
namespace alembic {
class ABCHierarchyIterator;
struct ABCWriterConstructorArgs {
Depsgraph *depsgraph;
ABCArchive *abc_archive;
Alembic::Abc::OObject abc_parent;
std::string abc_name;
std::string abc_path;
const ABCHierarchyIterator *hierarchy_iterator;
const AlembicExportParams *export_params;
};
class ABCHierarchyIterator : public AbstractHierarchyIterator {
private:
ABCArchive *abc_archive_;
const AlembicExportParams &params_;
public:
ABCHierarchyIterator(Depsgraph *depsgraph,
ABCArchive *abc_archive_,
const AlembicExportParams &params);
virtual void iterate_and_write() override;
virtual std::string make_valid_name(const std::string &name) const override;
protected:
virtual bool mark_as_weak_export(const Object *object) const override;
virtual ExportGraph::key_type determine_graph_index_object(
const HierarchyContext *context) override;
virtual AbstractHierarchyIterator::ExportGraph::key_type determine_graph_index_dupli(
const HierarchyContext *context, const std::set<Object *> &dupli_set) override;
virtual AbstractHierarchyWriter *create_transform_writer(
const HierarchyContext *context) override;
virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) override;
virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) override;
virtual AbstractHierarchyWriter *create_particle_writer(
const HierarchyContext *context) override;
virtual void delete_object_writer(AbstractHierarchyWriter *writer) override;
private:
Alembic::Abc::OObject get_alembic_parent(const HierarchyContext *context) const;
ABCWriterConstructorArgs writer_constructor_args(const HierarchyContext *context) const;
void update_archive_bounding_box();
void update_bounding_box_recursive(Imath::Box3d &bounds, const HierarchyContext *context);
};
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -0,0 +1,107 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#include "abc_subdiv_disabler.h"
#include <stdio.h>
#include "BLI_listbase.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "DNA_layer_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "BKE_modifier.h"
namespace blender {
namespace io {
namespace alembic {
SubdivModifierDisabler::SubdivModifierDisabler(Depsgraph *depsgraph) : depsgraph_(depsgraph)
{
}
SubdivModifierDisabler::~SubdivModifierDisabler()
{
for (ModifierData *modifier : disabled_modifiers_) {
modifier->mode &= ~eModifierMode_DisableTemporary;
}
}
void SubdivModifierDisabler::disable_modifiers()
{
Scene *scene = DEG_get_input_scene(depsgraph_);
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph_);
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
Object *object = base->object;
if (object->type != OB_MESH) {
continue;
}
ModifierData *subdiv = get_subdiv_modifier(scene, object);
if (subdiv == nullptr) {
continue;
}
/* This disables more modifiers than necessary, as it doesn't take restrictions like
* "export selected objects only" into account. However, with the subsurfs disabled,
* moving to a different frame is also going to be faster, so in the end this is probably
* a good thing to do. */
subdiv->mode |= eModifierMode_DisableTemporary;
disabled_modifiers_.insert(subdiv);
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
}
}
/* Check if the mesh is a subsurf, ignoring disabled modifiers and
* displace if it's after subsurf. */
ModifierData *SubdivModifierDisabler::get_subdiv_modifier(Scene *scene, Object *ob)
{
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
for (; md; md = md->prev) {
if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Render)) {
continue;
}
if (md->type == eModifierType_Subsurf) {
SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
if (smd->subdivType == ME_CC_SUBSURF) {
return md;
}
}
/* mesh is not a subsurf. break */
if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) {
return nullptr;
}
}
return nullptr;
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -13,40 +13,41 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2016 Kévin Dietrich.
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup balembic
*/
#include <set>
#include <Alembic/Abc/All.h>
#include <Alembic/AbcCoreOgawa/All.h>
#include <fstream>
struct Main;
struct Depsgraph;
struct ModifierData;
struct Object;
struct Scene;
namespace blender {
namespace io {
namespace alembic {
/* Wrappers around input and output archives. The goal is to be able to use
* streams so that unicode paths work on Windows (T49112), and to make sure that
* the stream objects remain valid as long as the archives are open.
/**
* Temporarily all subdivision modifiers on mesh objects.
* The destructor restores all disabled modifiers.
*
* This is used to export unsubdivided meshes to Alembic. It is done in a separate step before the
* exporter starts iterating over all the frames, so that it only has to happen once per export.
*/
class ArchiveWriter {
std::ofstream m_outfile;
Alembic::Abc::OArchive m_archive;
class SubdivModifierDisabler final {
private:
Depsgraph *depsgraph_;
std::set<ModifierData *> disabled_modifiers_;
public:
ArchiveWriter(const char *filename, const std::string &abc_scene_name, const Scene *scene);
explicit SubdivModifierDisabler(Depsgraph *depsgraph);
~SubdivModifierDisabler();
Alembic::Abc::OArchive &archive();
void disable_modifiers();
static ModifierData *get_subdiv_modifier(Scene *scene, Object *ob);
};
} // namespace alembic

View File

@ -0,0 +1,101 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#include "abc_writer_abstract.h"
#include "abc_hierarchy_iterator.h"
#include "BKE_animsys.h"
#include "BKE_key.h"
#include "BKE_object.h"
#include "DNA_modifier_types.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
namespace blender {
namespace io {
namespace alembic {
using Alembic::Abc::OObject;
using Alembic::Abc::TimeSamplingPtr;
ABCAbstractWriter::ABCAbstractWriter(const ABCWriterConstructorArgs &args)
: args_(args),
frame_has_been_written_(false),
is_animated_(false),
timesample_index_(args_.abc_archive->time_sampling_index_shapes())
{
}
ABCAbstractWriter::~ABCAbstractWriter()
{
}
bool ABCAbstractWriter::is_supported(const HierarchyContext * /*context*/) const
{
return true;
}
void ABCAbstractWriter::write(HierarchyContext &context)
{
if (!frame_has_been_written_) {
is_animated_ = (args_.export_params->frame_start != args_.export_params->frame_end) &&
check_is_animated(context);
}
else if (!is_animated_) {
/* A frame has already been written, and without animation one frame is enough. */
return;
}
do_write(context);
frame_has_been_written_ = true;
}
const Imath::Box3d &ABCAbstractWriter::bounding_box() const
{
return bounding_box_;
}
void ABCAbstractWriter::update_bounding_box(Object *object)
{
BoundBox *bb = BKE_object_boundbox_get(object);
if (!bb) {
if (object->type != OB_CAMERA) {
CLOG_WARN(&LOG, "Bounding box is null!\n");
}
bounding_box_.min.x = bounding_box_.min.y = bounding_box_.min.z = 0;
bounding_box_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0;
return;
}
/* Convert Z-up to Y-up. This also changes which vector goes into which min/max property. */
bounding_box_.min.x = bb->vec[0][0];
bounding_box_.min.y = bb->vec[0][2];
bounding_box_.min.z = -bb->vec[6][1];
bounding_box_.max.x = bb->vec[6][0];
bounding_box_.max.y = bb->vec[6][2];
bounding_box_.max.z = -bb->vec[0][1];
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -0,0 +1,77 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#pragma once
#include "IO_abstract_hierarchy_iterator.h"
#include "abc_hierarchy_iterator.h"
#include <Alembic/Abc/OObject.h>
#include <vector>
#include "DEG_depsgraph_query.h"
#include "DNA_material_types.h"
struct Material;
struct Object;
namespace blender {
namespace io {
namespace alembic {
class ABCAbstractWriter : public AbstractHierarchyWriter {
protected:
const ABCWriterConstructorArgs args_;
bool frame_has_been_written_;
bool is_animated_;
uint32_t timesample_index_;
Imath::Box3d bounding_box_;
public:
explicit ABCAbstractWriter(const ABCWriterConstructorArgs &args);
virtual ~ABCAbstractWriter();
virtual void write(HierarchyContext &context) override;
/* Returns true if the data to be written is actually supported. This would, for example, allow a
* hypothetical camera writer accept a perspective camera but reject an orthogonal one.
*
* Returning false from a transform writer will prevent the object and all its decendants from
* being exported. Returning false from a data writer (object data, hair, or particles) will
* only prevent that data from being written (and thus cause the object to be exported as an
* Empty). */
virtual bool is_supported(const HierarchyContext *context) const;
const Imath::Box3d &bounding_box() const;
/* Called by AlembicHierarchyCreator after checking that the data is supported via
* is_supported(). */
virtual void create_alembic_objects(const HierarchyContext *context) = 0;
virtual const Alembic::Abc::OObject get_alembic_object() const = 0;
protected:
virtual void do_write(HierarchyContext &context) = 0;
virtual void update_bounding_box(Object *object);
};
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -1,106 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2016 Kévin Dietrich.
* All rights reserved.
*/
/** \file
* \ingroup balembic
*/
#include "abc_writer_archive.h"
#include "BKE_blender_version.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "DNA_scene_types.h"
#ifdef WIN32
# include "utfconv.h"
#endif
#include <fstream>
using Alembic::Abc::ErrorHandler;
using Alembic::Abc::kWrapExisting;
using Alembic::Abc::OArchive;
namespace blender {
namespace io {
namespace alembic {
/* This kinda duplicates CreateArchiveWithInfo, but Alembic does not seem to
* have a version supporting streams. */
static OArchive create_archive(std::ostream *ostream,
const std::string &scene_name,
double scene_fps)
{
Alembic::Abc::MetaData abc_metadata;
abc_metadata.set(Alembic::Abc::kApplicationNameKey, "Blender");
abc_metadata.set(Alembic::Abc::kUserDescriptionKey, scene_name);
abc_metadata.set("blender_version", std::string("v") + BKE_blender_version_string());
abc_metadata.set("FramesPerTimeUnit", std::to_string(scene_fps));
time_t raw_time;
time(&raw_time);
char buffer[128];
#if defined _WIN32 || defined _WIN64
ctime_s(buffer, 128, &raw_time);
#else
ctime_r(&raw_time, buffer);
#endif
const std::size_t buffer_len = strlen(buffer);
if (buffer_len > 0 && buffer[buffer_len - 1] == '\n') {
buffer[buffer_len - 1] = '\0';
}
abc_metadata.set(Alembic::Abc::kDateWrittenKey, buffer);
ErrorHandler::Policy policy = ErrorHandler::kThrowPolicy;
Alembic::AbcCoreOgawa::WriteArchive archive_writer;
return OArchive(archive_writer(ostream, abc_metadata), kWrapExisting, policy);
}
ArchiveWriter::ArchiveWriter(const char *filename,
const std::string &abc_scene_name,
const Scene *scene)
{
/* Use stream to support unicode character paths on Windows. */
#ifdef WIN32
UTF16_ENCODE(filename);
std::wstring wstr(filename_16);
m_outfile.open(wstr.c_str(), std::ios::out | std::ios::binary);
UTF16_UN_ENCODE(filename);
#else
m_outfile.open(filename, std::ios::out | std::ios::binary);
#endif
m_archive = create_archive(&m_outfile, abc_scene_name, FPS);
}
OArchive &ArchiveWriter::archive()
{
return m_archive;
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -19,67 +19,90 @@
*/
#include "abc_writer_camera.h"
#include "abc_writer_transform.h"
#include "abc_hierarchy_iterator.h"
#include "BKE_camera.h"
#include "BLI_assert.h"
#include "DNA_camera_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
using Alembic::AbcGeom::OCamera;
using Alembic::AbcGeom::OFloatProperty;
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
namespace blender {
namespace io {
namespace alembic {
AbcCameraWriter::AbcCameraWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings)
: AbcObjectWriter(ob, time_sampling, settings, parent)
{
OCamera camera(parent->alembicXform(), m_name, m_time_sampling);
m_camera_schema = camera.getSchema();
using Alembic::AbcGeom::CameraSample;
using Alembic::AbcGeom::OCamera;
using Alembic::AbcGeom::OFloatProperty;
m_custom_data_container = m_camera_schema.getUserProperties();
m_stereo_distance = OFloatProperty(m_custom_data_container, "stereoDistance", m_time_sampling);
m_eye_separation = OFloatProperty(m_custom_data_container, "eyeSeparation", m_time_sampling);
ABCCameraWriter::ABCCameraWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args)
{
}
void AbcCameraWriter::do_write()
bool ABCCameraWriter::is_supported(const HierarchyContext *context) const
{
Camera *cam = static_cast<Camera *>(m_object->data);
Camera *camera = static_cast<Camera *>(context->object->data);
return camera->type == CAM_PERSP;
}
m_stereo_distance.set(cam->stereo.convergence_distance);
m_eye_separation.set(cam->stereo.interocular_distance);
void ABCCameraWriter::create_alembic_objects(const HierarchyContext * /*context*/)
{
CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str());
abc_camera_ = OCamera(args_.abc_parent, args_.abc_name, timesample_index_);
abc_camera_schema_ = abc_camera_.getSchema();
abc_custom_data_container_ = abc_camera_schema_.getUserProperties();
abc_stereo_distance_ = OFloatProperty(
abc_custom_data_container_, "stereoDistance", timesample_index_);
abc_eye_separation_ = OFloatProperty(
abc_custom_data_container_, "eyeSeparation", timesample_index_);
}
const Alembic::Abc::OObject ABCCameraWriter::get_alembic_object() const
{
return abc_camera_;
}
void ABCCameraWriter::do_write(HierarchyContext &context)
{
Camera *cam = static_cast<Camera *>(context.object->data);
abc_stereo_distance_.set(cam->stereo.convergence_distance);
abc_eye_separation_.set(cam->stereo.interocular_distance);
const double apperture_x = cam->sensor_x / 10.0;
const double apperture_y = cam->sensor_y / 10.0;
const double film_aspect = apperture_x / apperture_y;
m_camera_sample.setFocalLength(cam->lens);
m_camera_sample.setHorizontalAperture(apperture_x);
m_camera_sample.setVerticalAperture(apperture_y);
m_camera_sample.setHorizontalFilmOffset(apperture_x * cam->shiftx);
m_camera_sample.setVerticalFilmOffset(apperture_y * cam->shifty * film_aspect);
m_camera_sample.setNearClippingPlane(cam->clip_start);
m_camera_sample.setFarClippingPlane(cam->clip_end);
CameraSample camera_sample;
camera_sample.setFocalLength(cam->lens);
camera_sample.setHorizontalAperture(apperture_x);
camera_sample.setVerticalAperture(apperture_y);
camera_sample.setHorizontalFilmOffset(apperture_x * cam->shiftx);
camera_sample.setVerticalFilmOffset(apperture_y * cam->shifty * film_aspect);
camera_sample.setNearClippingPlane(cam->clip_start);
camera_sample.setFarClippingPlane(cam->clip_end);
if (cam->dof.focus_object) {
Imath::V3f v(m_object->loc[0] - cam->dof.focus_object->loc[0],
m_object->loc[1] - cam->dof.focus_object->loc[1],
m_object->loc[2] - cam->dof.focus_object->loc[2]);
m_camera_sample.setFocusDistance(v.length());
Imath::V3f v(context.object->loc[0] - cam->dof.focus_object->loc[0],
context.object->loc[1] - cam->dof.focus_object->loc[1],
context.object->loc[2] - cam->dof.focus_object->loc[2]);
camera_sample.setFocusDistance(v.length());
}
else {
m_camera_sample.setFocusDistance(cam->dof.focus_distance);
camera_sample.setFocusDistance(cam->dof.focus_distance);
}
/* Blender camera does not have an fstop param, so try to find a custom prop
* instead. */
m_camera_sample.setFStop(cam->dof.aperture_fstop);
camera_sample.setFStop(cam->dof.aperture_fstop);
m_camera_sample.setLensSqueezeRatio(1.0);
m_camera_schema.set(m_camera_sample);
camera_sample.setLensSqueezeRatio(1.0);
abc_camera_schema_.set(camera_sample);
}
} // namespace alembic

View File

@ -19,29 +19,32 @@
* \ingroup balembic
*/
#include "abc_writer_object.h"
#include "abc_writer_abstract.h"
/* ************************************************************************** */
#include <Alembic/AbcGeom/OCamera.h>
namespace blender {
namespace io {
namespace alembic {
class AbcCameraWriter : public AbcObjectWriter {
Alembic::AbcGeom::OCameraSchema m_camera_schema;
Alembic::AbcGeom::CameraSample m_camera_sample;
Alembic::AbcGeom::OCompoundProperty m_custom_data_container;
Alembic::AbcGeom::OFloatProperty m_stereo_distance;
Alembic::AbcGeom::OFloatProperty m_eye_separation;
class ABCCameraWriter : public ABCAbstractWriter {
private:
Alembic::AbcGeom::OCamera abc_camera_;
Alembic::AbcGeom::OCameraSchema abc_camera_schema_;
Alembic::AbcGeom::OCompoundProperty abc_custom_data_container_;
Alembic::AbcGeom::OFloatProperty abc_stereo_distance_;
Alembic::AbcGeom::OFloatProperty abc_eye_separation_;
public:
AbcCameraWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings);
explicit ABCCameraWriter(const ABCWriterConstructorArgs &args);
private:
virtual void do_write();
virtual void create_alembic_objects(const HierarchyContext *context) override;
virtual const Alembic::Abc::OObject get_alembic_object() const override;
protected:
virtual bool is_supported(const HierarchyContext *context) const override;
virtual void do_write(HierarchyContext &context) override;
};
} // namespace alembic

View File

@ -22,9 +22,7 @@
*/
#include "abc_writer_curves.h"
#include "abc_writer_transform.h"
#include "intern/abc_axis_conversion.h"
#include "intern/abc_reader_curves.h"
#include "DNA_curve_types.h"
#include "DNA_object_types.h"
@ -33,6 +31,9 @@
#include "BKE_mesh.h"
#include "BKE_object.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
using Alembic::AbcGeom::OCompoundProperty;
using Alembic::AbcGeom::OCurves;
using Alembic::AbcGeom::OCurvesSchema;
@ -44,24 +45,32 @@ namespace blender {
namespace io {
namespace alembic {
AbcCurveWriter::AbcCurveWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings)
: AbcObjectWriter(ob, time_sampling, settings, parent)
{
OCurves curves(parent->alembicXform(), m_name, m_time_sampling);
m_schema = curves.getSchema();
const std::string ABC_CURVE_RESOLUTION_U_PROPNAME("blender:resolution");
Curve *cu = static_cast<Curve *>(m_object->data);
OCompoundProperty user_props = m_schema.getUserProperties();
ABCCurveWriter::ABCCurveWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args)
{
}
void ABCCurveWriter::create_alembic_objects(const HierarchyContext *context)
{
CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str());
abc_curve_ = OCurves(args_.abc_parent, args_.abc_name, timesample_index_);
abc_curve_schema_ = abc_curve_.getSchema();
Curve *cu = static_cast<Curve *>(context->object->data);
OCompoundProperty user_props = abc_curve_schema_.getUserProperties();
OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME);
user_prop_resolu.set(cu->resolu);
}
void AbcCurveWriter::do_write()
const Alembic::Abc::OObject ABCCurveWriter::get_alembic_object() const
{
Curve *curve = static_cast<Curve *>(m_object->data);
return abc_curve_;
}
void ABCCurveWriter::do_write(HierarchyContext &context)
{
Curve *curve = static_cast<Curve *>(context.object->data);
std::vector<Imath::V3f> verts;
std::vector<int32_t> vert_counts;
@ -152,35 +161,31 @@ void AbcCurveWriter::do_write()
Alembic::AbcGeom::OFloatGeomParam::Sample width_sample;
width_sample.setVals(widths);
m_sample = OCurvesSchema::Sample(verts,
vert_counts,
curve_type,
periodicity,
width_sample,
OV2fGeomParam::Sample(), /* UVs */
ON3fGeomParam::Sample(), /* normals */
curve_basis,
weights,
orders,
knots);
OCurvesSchema::Sample sample(verts,
vert_counts,
curve_type,
periodicity,
width_sample,
OV2fGeomParam::Sample(), /* UVs */
ON3fGeomParam::Sample(), /* normals */
curve_basis,
weights,
orders,
knots);
m_sample.setSelfBounds(bounds());
m_schema.set(m_sample);
update_bounding_box(context.object);
sample.setSelfBounds(bounding_box_);
abc_curve_schema_.set(sample);
}
AbcCurveMeshWriter::AbcCurveMeshWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings)
: AbcGenericMeshWriter(ob, parent, time_sampling, settings)
ABCCurveMeshWriter::ABCCurveMeshWriter(const ABCWriterConstructorArgs &args)
: ABCGenericMeshWriter(args)
{
}
Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/,
Object *ob_eval,
bool &r_needsfree)
Mesh *ABCCurveMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
{
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
if (mesh_eval != NULL) {
/* Mesh_eval only exists when generative modifiers are in use. */
r_needsfree = false;
@ -188,7 +193,7 @@ Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/,
}
r_needsfree = true;
return BKE_mesh_new_nomain_from_curve(ob_eval);
return BKE_mesh_new_nomain_from_curve(object_eval);
}
} // namespace alembic

View File

@ -22,36 +22,38 @@
* \ingroup balembic
*/
#include "abc_writer_abstract.h"
#include "abc_writer_mesh.h"
#include "abc_writer_object.h"
#include <Alembic/AbcGeom/OCurves.h>
namespace blender {
namespace io {
namespace alembic {
class AbcCurveWriter : public AbcObjectWriter {
Alembic::AbcGeom::OCurvesSchema m_schema;
Alembic::AbcGeom::OCurvesSchema::Sample m_sample;
extern const std::string ABC_CURVE_RESOLUTION_U_PROPNAME;
class ABCCurveWriter : public ABCAbstractWriter {
private:
Alembic::AbcGeom::OCurves abc_curve_;
Alembic::AbcGeom::OCurvesSchema abc_curve_schema_;
public:
AbcCurveWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings);
explicit ABCCurveWriter(const ABCWriterConstructorArgs &args);
virtual void create_alembic_objects(const HierarchyContext *context) override;
virtual const Alembic::Abc::OObject get_alembic_object() const override;
protected:
void do_write();
virtual void do_write(HierarchyContext &context) override;
};
class AbcCurveMeshWriter : public AbcGenericMeshWriter {
class ABCCurveMeshWriter : public ABCGenericMeshWriter {
public:
AbcCurveMeshWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings);
ABCCurveMeshWriter(const ABCWriterConstructorArgs &args);
protected:
Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree);
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
};
} // namespace alembic

View File

@ -19,7 +19,6 @@
*/
#include "abc_writer_hair.h"
#include "abc_writer_transform.h"
#include "intern/abc_axis_conversion.h"
#include <cstdio>
@ -35,40 +34,46 @@
#include "BKE_mesh_runtime.h"
#include "BKE_particle.h"
using Alembic::Abc::P3fArraySamplePtr;
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
using Alembic::Abc::P3fArraySamplePtr;
using Alembic::AbcGeom::OCurves;
using Alembic::AbcGeom::OCurvesSchema;
using Alembic::AbcGeom::ON3fGeomParam;
using Alembic::AbcGeom::OV2fGeomParam;
/* ************************************************************************** */
namespace blender {
namespace io {
namespace alembic {
AbcHairWriter::AbcHairWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings,
ParticleSystem *psys)
: AbcObjectWriter(ob, time_sampling, settings, parent), m_uv_warning_shown(false)
ABCHairWriter::ABCHairWriter(const ABCWriterConstructorArgs &args)
: ABCAbstractWriter(args), uv_warning_shown_(false)
{
m_psys = psys;
std::string psys_name = get_valid_abc_name(psys->name);
OCurves curves(parent->alembicXform(), psys_name, m_time_sampling);
m_schema = curves.getSchema();
}
void AbcHairWriter::do_write()
void ABCHairWriter::create_alembic_objects(const HierarchyContext * /*context*/)
{
if (!m_psys) {
return;
}
Mesh *mesh = mesh_get_eval_final(
m_settings.depsgraph, m_settings.scene, m_object, &CD_MASK_MESH);
CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str());
abc_curves_ = OCurves(args_.abc_parent, args_.abc_name, timesample_index_);
abc_curves_schema_ = abc_curves_.getSchema();
}
const Alembic::Abc::OObject ABCHairWriter::get_alembic_object() const
{
return abc_curves_;
}
bool ABCHairWriter::check_is_animated(const HierarchyContext & /*context*/) const
{
/* We assume that hair particles are always animated. */
return true;
}
void ABCHairWriter::do_write(HierarchyContext &context)
{
Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph);
Mesh *mesh = mesh_get_eval_final(args_.depsgraph, scene_eval, context.object, &CD_MASK_MESH);
BKE_mesh_tessface_ensure(mesh);
std::vector<Imath::V3f> verts;
@ -76,44 +81,45 @@ void AbcHairWriter::do_write()
std::vector<Imath::V2f> uv_values;
std::vector<Imath::V3f> norm_values;
if (m_psys->pathcache) {
ParticleSettings *part = m_psys->part;
bool export_children = m_settings.export_child_hairs && m_psys->childcache &&
part->childtype != 0;
ParticleSystem *psys = context.particle_system;
if (psys->pathcache) {
ParticleSettings *part = psys->part;
bool export_children = psys->childcache && part->childtype != 0;
if (!export_children || part->draw & PART_DRAW_PARENT) {
write_hair_sample(mesh, part, verts, norm_values, uv_values, hvertices);
write_hair_sample(context, mesh, verts, norm_values, uv_values, hvertices);
}
if (export_children) {
write_hair_child_sample(mesh, part, verts, norm_values, uv_values, hvertices);
write_hair_child_sample(context, mesh, verts, norm_values, uv_values, hvertices);
}
}
Alembic::Abc::P3fArraySample iPos(verts);
m_sample = OCurvesSchema::Sample(iPos, hvertices);
m_sample.setBasis(Alembic::AbcGeom::kNoBasis);
m_sample.setType(Alembic::AbcGeom::kLinear);
m_sample.setWrap(Alembic::AbcGeom::kNonPeriodic);
OCurvesSchema::Sample sample(iPos, hvertices);
sample.setBasis(Alembic::AbcGeom::kNoBasis);
sample.setType(Alembic::AbcGeom::kLinear);
sample.setWrap(Alembic::AbcGeom::kNonPeriodic);
if (!uv_values.empty()) {
OV2fGeomParam::Sample uv_smp;
uv_smp.setVals(uv_values);
m_sample.setUVs(uv_smp);
sample.setUVs(uv_smp);
}
if (!norm_values.empty()) {
ON3fGeomParam::Sample norm_smp;
norm_smp.setVals(norm_values);
m_sample.setNormals(norm_smp);
sample.setNormals(norm_smp);
}
m_sample.setSelfBounds(bounds());
m_schema.set(m_sample);
update_bounding_box(context.object);
sample.setSelfBounds(bounding_box_);
abc_curves_schema_.set(sample);
}
void AbcHairWriter::write_hair_sample(Mesh *mesh,
ParticleSettings *part,
void ABCHairWriter::write_hair_sample(const HierarchyContext &context,
Mesh *mesh,
std::vector<Imath::V3f> &verts,
std::vector<Imath::V3f> &norm_values,
std::vector<Imath::V2f> &uv_values,
@ -121,28 +127,30 @@ void AbcHairWriter::write_hair_sample(Mesh *mesh,
{
/* Get untransformed vertices, there's a xform under the hair. */
float inv_mat[4][4];
invert_m4_m4_safe(inv_mat, m_object->obmat);
invert_m4_m4_safe(inv_mat, context.object->obmat);
MTFace *mtface = mesh->mtface;
MFace *mface = mesh->mface;
MVert *mverts = mesh->mvert;
if ((!mtface || !mface) && !m_uv_warning_shown) {
if ((!mtface || !mface) && !uv_warning_shown_) {
std::fprintf(stderr,
"Warning, no UV set found for underlying geometry of %s.\n",
m_object->id.name + 2);
m_uv_warning_shown = true;
context.object->id.name + 2);
uv_warning_shown_ = true;
}
ParticleData *pa = m_psys->particles;
ParticleSystem *psys = context.particle_system;
ParticleSettings *part = psys->part;
ParticleData *pa = psys->particles;
int k;
ParticleCacheKey **cache = m_psys->pathcache;
ParticleCacheKey **cache = psys->pathcache;
ParticleCacheKey *path;
float normal[3];
Imath::V3f tmp_nor;
for (int p = 0; p < m_psys->totpart; p++, pa++) {
for (int p = 0; p < psys->totpart; p++, pa++) {
/* underlying info for faces-only emission */
path = cache[p];
@ -224,8 +232,8 @@ void AbcHairWriter::write_hair_sample(Mesh *mesh,
}
}
void AbcHairWriter::write_hair_child_sample(Mesh *mesh,
ParticleSettings *part,
void ABCHairWriter::write_hair_child_sample(const HierarchyContext &context,
Mesh *mesh,
std::vector<Imath::V3f> &verts,
std::vector<Imath::V3f> &norm_values,
std::vector<Imath::V2f> &uv_values,
@ -233,26 +241,30 @@ void AbcHairWriter::write_hair_child_sample(Mesh *mesh,
{
/* Get untransformed vertices, there's a xform under the hair. */
float inv_mat[4][4];
invert_m4_m4_safe(inv_mat, m_object->obmat);
invert_m4_m4_safe(inv_mat, context.object->obmat);
MTFace *mtface = mesh->mtface;
MVert *mverts = mesh->mvert;
ParticleCacheKey **cache = m_psys->childcache;
ParticleSystem *psys = context.particle_system;
ParticleSettings *part = psys->part;
ParticleCacheKey **cache = psys->childcache;
ParticleCacheKey *path;
ChildParticle *pc = m_psys->child;
ChildParticle *pc = psys->child;
for (int p = 0; p < m_psys->totchild; p++, pc++) {
for (int p = 0; p < psys->totchild; p++, pc++) {
path = cache[p];
if (part->from == PART_FROM_FACE && part->childtype != PART_CHILD_PARTICLES && mtface) {
const int num = pc->num;
if (num < 0) {
ABC_LOG(m_settings.logger)
<< "Warning, child particle of hair system " << m_psys->name
<< " has unknown face index of geometry of " << (m_object->id.name + 2)
<< ", skipping child hair." << std::endl;
CLOG_WARN(
&LOG,
"Child particle of hair system %s has unknown face index of geometry of %s, skipping "
"child hair.",
psys->name,
context.object->id.name + 2);
continue;
}

View File

@ -19,9 +19,10 @@
* \ingroup balembic
*/
#include "abc_writer_object.h"
#include "abc_writer_abstract.h"
#include <Alembic/AbcGeom/OCurves.h>
#include <vector>
struct Mesh;
struct ParticleSettings;
struct ParticleSystem;
@ -29,33 +30,33 @@ namespace blender {
namespace io {
namespace alembic {
class AbcHairWriter : public AbcObjectWriter {
ParticleSystem *m_psys;
class ABCHairWriter : public ABCAbstractWriter {
private:
Alembic::AbcGeom::OCurves abc_curves_;
Alembic::AbcGeom::OCurvesSchema abc_curves_schema_;
Alembic::AbcGeom::OCurvesSchema m_schema;
Alembic::AbcGeom::OCurvesSchema::Sample m_sample;
bool m_uv_warning_shown;
bool uv_warning_shown_;
public:
AbcHairWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings,
ParticleSystem *psys);
explicit ABCHairWriter(const ABCWriterConstructorArgs &args);
virtual void create_alembic_objects(const HierarchyContext *context) override;
virtual const Alembic::Abc::OObject get_alembic_object() const override;
protected:
virtual void do_write(HierarchyContext &context) override;
virtual bool check_is_animated(const HierarchyContext &context) const override;
private:
virtual void do_write();
void write_hair_sample(struct Mesh *mesh,
ParticleSettings *part,
void write_hair_sample(const HierarchyContext &context,
struct Mesh *mesh,
std::vector<Imath::V3f> &verts,
std::vector<Imath::V3f> &norm_values,
std::vector<Imath::V2f> &uv_values,
std::vector<int32_t> &hvertices);
void write_hair_child_sample(struct Mesh *mesh,
ParticleSettings *part,
void write_hair_child_sample(const HierarchyContext &context,
struct Mesh *mesh,
std::vector<Imath::V3f> &verts,
std::vector<Imath::V3f> &norm_values,
std::vector<Imath::V2f> &uv_values,

View File

@ -19,11 +19,9 @@
*/
#include "abc_writer_mball.h"
#include "abc_writer_mesh.h"
#include "abc_hierarchy_iterator.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
#include "DNA_object_types.h"
#include "BLI_assert.h"
#include "BKE_displist.h"
#include "BKE_lib_id.h"
@ -31,68 +29,57 @@
#include "BKE_mesh.h"
#include "BKE_object.h"
#include "BLI_utildefines.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
namespace blender {
namespace io {
namespace alembic {
AbcMBallWriter::AbcMBallWriter(Main *bmain,
Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings)
: AbcGenericMeshWriter(ob, parent, time_sampling, settings), m_bmain(bmain)
{
m_is_animated = isAnimated();
}
AbcMBallWriter::~AbcMBallWriter()
ABCMetaballWriter::ABCMetaballWriter(const ABCWriterConstructorArgs &args)
: ABCGenericMeshWriter(args)
{
}
bool AbcMBallWriter::isAnimated() const
bool ABCMetaballWriter::is_supported(const HierarchyContext *context) const
{
Scene *scene = DEG_get_input_scene(args_.depsgraph);
bool supported = is_basis_ball(scene, context->object) &&
ABCGenericMeshWriter::is_supported(context);
return supported;
}
bool ABCMetaballWriter::check_is_animated(const HierarchyContext & /*context*/) const
{
/* We assume that metaballs are always animated, as the current object may
* not be animated but another ball in the same group may be. */
return true;
}
Mesh *AbcMBallWriter::getEvaluatedMesh(Scene * /*scene_eval*/, Object *ob_eval, bool &r_needsfree)
bool ABCMetaballWriter::export_as_subdivision_surface(Object * /*ob_eval*/) const
{
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
if (mesh_eval != NULL) {
/* Metaballs should be exported to subdivision surfaces, if the export options allow. */
return true;
}
Mesh *ABCMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
{
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
if (mesh_eval != nullptr) {
/* Mesh_eval only exists when generative modifiers are in use. */
r_needsfree = false;
return mesh_eval;
}
r_needsfree = true;
/* The approach below is copied from BKE_mesh_new_from_object() */
Mesh *tmpmesh = BKE_mesh_add(m_bmain, ((ID *)m_object->data)->name + 2);
BLI_assert(tmpmesh != NULL);
/* BKE_mesh_add gives us a user count we don't need */
id_us_min(&tmpmesh->id);
ListBase disp = {NULL, NULL};
/* TODO(sergey): This is gonna to work for until Depsgraph
* only contains for_render flag. As soon as CoW is
* implemented, this is to be rethought.
*/
BKE_displist_make_mball_forRender(m_settings.depsgraph, m_settings.scene, m_object, &disp);
BKE_mesh_from_metaball(&disp, tmpmesh);
BKE_displist_free(&disp);
BKE_mesh_texspace_copy_from_object(tmpmesh, m_object);
return tmpmesh;
return BKE_mesh_new_from_object(args_.depsgraph, object_eval, false);
}
void AbcMBallWriter::freeEvaluatedMesh(struct Mesh *mesh)
void ABCMetaballWriter::free_export_mesh(Mesh *mesh)
{
BKE_id_free(m_bmain, mesh);
BKE_id_free(nullptr, mesh);
}
bool AbcMBallWriter::isBasisBall(Scene *scene, Object *ob)
bool ABCMetaballWriter::is_basis_ball(Scene *scene, Object *ob) const
{
Object *basis_ob = BKE_mball_basis_find(scene, ob);
return ob == basis_ob;

View File

@ -20,39 +20,24 @@
*/
#include "abc_writer_mesh.h"
#include "abc_writer_object.h"
struct Main;
struct Object;
namespace blender {
namespace io {
namespace alembic {
/* AbcMBallWriter converts the metaballs to meshes at every frame,
* and defers to AbcGenericMeshWriter to perform the writing
* to the Alembic file. Only the basis balls are exported, as this
* results in the entire shape as one mesh. */
class AbcMBallWriter : public AbcGenericMeshWriter {
Main *m_bmain;
class ABCMetaballWriter : public ABCGenericMeshWriter {
public:
explicit AbcMBallWriter(Main *bmain,
Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings);
~AbcMBallWriter();
static bool isBasisBall(Scene *scene, Object *ob);
explicit ABCMetaballWriter(const ABCWriterConstructorArgs &args);
protected:
Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override;
void freeEvaluatedMesh(struct Mesh *mesh) override;
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
virtual void free_export_mesh(Mesh *mesh) override;
virtual bool is_supported(const HierarchyContext *context) const override;
virtual bool check_is_animated(const HierarchyContext &context) const override;
virtual bool export_as_subdivision_surface(Object *ob_eval) const override;
private:
bool isAnimated() const override;
bool is_basis_ball(Scene *scene, Object *ob) const;
};
} // namespace alembic

View File

@ -19,29 +19,37 @@
*/
#include "abc_writer_mesh.h"
#include "abc_writer_transform.h"
#include "abc_hierarchy_iterator.h"
#include "intern/abc_axis_conversion.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_fluidsim_types.h"
#include "BLI_assert.h"
#include "BLI_math_vector.h"
#include "BKE_anim_data.h"
#include "BKE_key.h"
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "bmesh.h"
#include "bmesh_tools.h"
#include "DEG_depsgraph_query.h"
#include "DEG_depsgraph.h"
#include "DNA_layer_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_fluidsim_types.h"
#include "DNA_particle_types.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
using Alembic::Abc::FloatArraySample;
using Alembic::Abc::Int32ArraySample;
using Alembic::Abc::OObject;
using Alembic::Abc::V2fArraySample;
using Alembic::Abc::V3fArraySample;
@ -64,6 +72,394 @@ namespace alembic {
/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */
static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points);
static void get_topology(struct Mesh *mesh,
std::vector<int32_t> &poly_verts,
std::vector<int32_t> &loop_counts,
bool &r_has_flat_shaded_poly);
static void get_creases(struct Mesh *mesh,
std::vector<int32_t> &indices,
std::vector<int32_t> &lengths,
std::vector<float> &sharpnesses);
static void get_loop_normals(struct Mesh *mesh,
std::vector<Imath::V3f> &normals,
bool has_flat_shaded_poly);
ABCGenericMeshWriter::ABCGenericMeshWriter(const ABCWriterConstructorArgs &args)
: ABCAbstractWriter(args), is_subd_(false)
{
}
void ABCGenericMeshWriter::create_alembic_objects(const HierarchyContext *context)
{
if (!args_.export_params->apply_subdiv && export_as_subdivision_surface(context->object)) {
is_subd_ = args_.export_params->use_subdiv_schema;
}
if (is_subd_) {
CLOG_INFO(&LOG, 2, "exporting OSubD %s", args_.abc_path.c_str());
abc_subdiv_ = OSubD(args_.abc_parent, args_.abc_name, timesample_index_);
abc_subdiv_schema_ = abc_subdiv_.getSchema();
}
else {
CLOG_INFO(&LOG, 2, "exporting OPolyMesh %s", args_.abc_path.c_str());
abc_poly_mesh_ = OPolyMesh(args_.abc_parent, args_.abc_name, timesample_index_);
abc_poly_mesh_schema_ = abc_poly_mesh_.getSchema();
OCompoundProperty typeContainer = abc_poly_mesh_.getSchema().getUserProperties();
OBoolProperty type(typeContainer, "meshtype");
type.set(subsurf_modifier_ == nullptr);
}
Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph);
liquid_sim_modifier_ = get_liquid_sim_modifier(scene_eval, context->object);
}
ABCGenericMeshWriter::~ABCGenericMeshWriter()
{
}
const Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const
{
if (is_subd_) {
return abc_subdiv_;
}
return abc_poly_mesh_;
}
bool ABCGenericMeshWriter::export_as_subdivision_surface(Object *ob_eval) const
{
ModifierData *md = static_cast<ModifierData *>(ob_eval->modifiers.last);
for (; md; md = md->prev) {
/* This modifier has been temporarily disabled by SubdivModifierDisabler,
* so this indicates this is to be exported as subdivision surface. */
if (md->type == eModifierType_Subsurf && (md->mode & eModifierMode_DisableTemporary)) {
return true;
}
}
return false;
}
ModifierData *ABCGenericMeshWriter::get_liquid_sim_modifier(Scene *scene, Object *ob)
{
ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluidsim);
if (md && (BKE_modifier_is_enabled(scene, md, eModifierMode_Render))) {
FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) {
return md;
}
}
return nullptr;
}
bool ABCGenericMeshWriter::is_supported(const HierarchyContext *context) const
{
Object *object = context->object;
bool is_dupli = context->duplicator != nullptr;
int base_flag;
if (is_dupli) {
/* Construct the object's base flags from its dupli-parent, just like is done in
* deg_objects_dupli_iterator_next(). Without this, the visibility check below will fail. Doing
* this here, instead of a more suitable location in AbstractHierarchyIterator, prevents
* copying the Object for every dupli. */
base_flag = object->base_flag;
object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI;
}
int visibility = BKE_object_visibility(
object, DAG_EVAL_RENDER /* TODO(Sybren): add evaluation mode to export options? */);
if (is_dupli) {
object->base_flag = base_flag;
}
return (visibility & OB_VISIBLE_SELF) != 0;
}
void ABCGenericMeshWriter::do_write(HierarchyContext &context)
{
Object *object = context.object;
bool needsfree = false;
Mesh *mesh = get_export_mesh(object, needsfree);
if (mesh == nullptr) {
return;
}
if (args_.export_params->triangulate) {
const bool tag_only = false;
const int quad_method = args_.export_params->quad_method;
const int ngon_method = args_.export_params->ngon_method;
struct BMeshCreateParams bmcp = {false};
struct BMeshFromMeshParams bmfmp = {true, false, false, 0};
BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmcp, &bmfmp);
BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr);
Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
BM_mesh_free(bm);
if (needsfree) {
free_export_mesh(mesh);
}
mesh = triangulated_mesh;
needsfree = true;
}
m_custom_data_config.pack_uvs = args_.export_params->packuv;
m_custom_data_config.mpoly = mesh->mpoly;
m_custom_data_config.mloop = mesh->mloop;
m_custom_data_config.totpoly = mesh->totpoly;
m_custom_data_config.totloop = mesh->totloop;
m_custom_data_config.totvert = mesh->totvert;
try {
if (is_subd_) {
write_subd(context, mesh);
}
else {
write_mesh(context, mesh);
}
if (needsfree) {
free_export_mesh(mesh);
}
}
catch (...) {
if (needsfree) {
free_export_mesh(mesh);
}
throw;
}
}
void ABCGenericMeshWriter::free_export_mesh(Mesh *mesh)
{
BKE_id_free(nullptr, mesh);
}
void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
{
std::vector<Imath::V3f> points, normals;
std::vector<int32_t> poly_verts, loop_counts;
std::vector<Imath::V3f> velocities;
bool has_flat_shaded_poly = false;
get_vertices(mesh, points);
get_topology(mesh, poly_verts, loop_counts, has_flat_shaded_poly);
if (!frame_has_been_written_ && args_.export_params->face_sets) {
write_face_sets(context.object, mesh, abc_poly_mesh_schema_);
}
OPolyMeshSchema::Sample mesh_sample = OPolyMeshSchema::Sample(
V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts));
UVSample uvs_and_indices;
if (!frame_has_been_written_ && args_.export_params->uvs) {
const char *name = get_uv_sample(uvs_and_indices, m_custom_data_config, &mesh->ldata);
if (!uvs_and_indices.indices.empty() && !uvs_and_indices.uvs.empty()) {
OV2fGeomParam::Sample uv_sample;
uv_sample.setVals(V2fArraySample(uvs_and_indices.uvs));
uv_sample.setIndices(UInt32ArraySample(uvs_and_indices.indices));
uv_sample.setScope(kFacevaryingScope);
abc_poly_mesh_schema_.setUVSourceName(name);
mesh_sample.setUVs(uv_sample);
}
write_custom_data(
abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV);
}
if (args_.export_params->normals) {
get_loop_normals(mesh, normals, has_flat_shaded_poly);
ON3fGeomParam::Sample normals_sample;
if (!normals.empty()) {
normals_sample.setScope(kFacevaryingScope);
normals_sample.setVals(V3fArraySample(normals));
}
mesh_sample.setNormals(normals_sample);
}
if (liquid_sim_modifier_ != nullptr) {
get_velocities(mesh, velocities);
mesh_sample.setVelocities(V3fArraySample(velocities));
}
update_bounding_box(context.object);
mesh_sample.setSelfBounds(bounding_box_);
abc_poly_mesh_schema_.set(mesh_sample);
write_arb_geo_params(mesh);
}
void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *mesh)
{
std::vector<float> crease_sharpness;
std::vector<Imath::V3f> points;
std::vector<int32_t> poly_verts, loop_counts;
std::vector<int32_t> crease_indices, crease_lengths;
bool has_flat_poly = false;
get_vertices(mesh, points);
get_topology(mesh, poly_verts, loop_counts, has_flat_poly);
get_creases(mesh, crease_indices, crease_lengths, crease_sharpness);
if (!frame_has_been_written_ && args_.export_params->face_sets) {
write_face_sets(context.object, mesh, abc_subdiv_schema_);
}
OSubDSchema::Sample subdiv_sample = OSubDSchema::Sample(
V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts));
UVSample sample;
if (!frame_has_been_written_ && args_.export_params->uvs) {
const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata);
if (!sample.indices.empty() && !sample.uvs.empty()) {
OV2fGeomParam::Sample uv_sample;
uv_sample.setVals(V2fArraySample(sample.uvs));
uv_sample.setIndices(UInt32ArraySample(sample.indices));
uv_sample.setScope(kFacevaryingScope);
abc_subdiv_schema_.setUVSourceName(name);
subdiv_sample.setUVs(uv_sample);
}
write_custom_data(
abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV);
}
if (!crease_indices.empty()) {
subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices));
subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths));
subdiv_sample.setCreaseSharpnesses(FloatArraySample(crease_sharpness));
}
update_bounding_box(context.object);
subdiv_sample.setSelfBounds(bounding_box_);
abc_subdiv_schema_.set(subdiv_sample);
write_arb_geo_params(mesh);
}
template<typename Schema>
void ABCGenericMeshWriter::write_face_sets(Object *object, struct Mesh *mesh, Schema &schema)
{
std::map<std::string, std::vector<int32_t>> geo_groups;
get_geo_groups(object, mesh, geo_groups);
std::map<std::string, std::vector<int32_t>>::iterator it;
for (it = geo_groups.begin(); it != geo_groups.end(); ++it) {
OFaceSet face_set = schema.createFaceSet(it->first);
OFaceSetSchema::Sample samp;
samp.setFaces(Int32ArraySample(it->second));
face_set.getSchema().set(samp);
}
}
void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me)
{
if (liquid_sim_modifier_ != nullptr) {
/* We don't need anything more for liquid meshes. */
return;
}
if (frame_has_been_written_ || !args_.export_params->vcolors) {
return;
}
OCompoundProperty arb_geom_params;
if (is_subd_) {
arb_geom_params = abc_subdiv_.getSchema().getArbGeomParams();
}
else {
arb_geom_params = abc_poly_mesh_.getSchema().getArbGeomParams();
}
write_custom_data(arb_geom_params, m_custom_data_config, &me->ldata, CD_MLOOPCOL);
}
void ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels)
{
const int totverts = mesh->totvert;
vels.clear();
vels.resize(totverts);
FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(liquid_sim_modifier_);
FluidsimSettings *fss = fmd->fss;
if (fss->meshVelocities) {
float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities);
for (int i = 0; i < totverts; i++) {
copy_yup_from_zup(vels[i].getValue(), mesh_vels);
mesh_vels += 3;
}
}
else {
std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f));
}
}
void ABCGenericMeshWriter::get_geo_groups(Object *object,
struct Mesh *mesh,
std::map<std::string, std::vector<int32_t>> &geo_groups)
{
const int num_poly = mesh->totpoly;
MPoly *polygons = mesh->mpoly;
for (int i = 0; i < num_poly; i++) {
MPoly &current_poly = polygons[i];
short mnr = current_poly.mat_nr;
Material *mat = BKE_object_material_get(object, mnr + 1);
if (!mat) {
continue;
}
std::string name = args_.hierarchy_iterator->get_id_name(&mat->id);
if (geo_groups.find(name) == geo_groups.end()) {
std::vector<int32_t> faceArray;
geo_groups[name] = faceArray;
}
geo_groups[name].push_back(i);
}
if (geo_groups.size() == 0) {
Material *mat = BKE_object_material_get(object, 1);
std::string name = (mat) ? args_.hierarchy_iterator->get_id_name(&mat->id) : "default";
std::vector<int32_t> faceArray;
for (int i = 0, e = mesh->totface; i < e; i++) {
faceArray.push_back(i);
}
geo_groups[name] = faceArray;
}
}
/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */
static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points)
{
points.clear();
@ -148,7 +544,7 @@ static void get_loop_normals(struct Mesh *mesh,
BKE_mesh_calc_normals_split(mesh);
const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL));
BLI_assert(lnors != NULL || !"BKE_mesh_calc_normals_split() should have computed CD_NORMAL");
BLI_assert(lnors != nullptr || !"BKE_mesh_calc_normals_split() should have computed CD_NORMAL");
normals.resize(mesh->totloop);
@ -163,435 +559,13 @@ static void get_loop_normals(struct Mesh *mesh,
}
}
/* *************** Modifiers *************** */
/* check if the mesh is a subsurf, ignoring disabled modifiers and
* displace if it's after subsurf. */
static ModifierData *get_subsurf_modifier(Scene *scene, Object *ob)
{
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
for (; md; md = md->prev) {
if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Render)) {
continue;
}
if (md->type == eModifierType_Subsurf) {
SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
if (smd->subdivType == ME_CC_SUBSURF) {
return md;
}
}
/* mesh is not a subsurf. break */
if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) {
return NULL;
}
}
return NULL;
}
static ModifierData *get_liquid_sim_modifier(Scene *scene, Object *ob)
{
ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluidsim);
if (md && (BKE_modifier_is_enabled(scene, md, eModifierMode_Render))) {
FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) {
return md;
}
}
return NULL;
}
/* ************************************************************************** */
AbcGenericMeshWriter::AbcGenericMeshWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings)
: AbcObjectWriter(ob, time_sampling, settings, parent)
{
m_is_animated = isAnimated();
m_subsurf_mod = NULL;
m_is_subd = false;
/* If the object is static, use the default static time sampling. */
if (!m_is_animated) {
time_sampling = 0;
}
if (!m_settings.apply_subdiv) {
m_subsurf_mod = get_subsurf_modifier(m_settings.scene, m_object);
m_is_subd = (m_subsurf_mod != NULL);
}
m_is_liquid = (get_liquid_sim_modifier(m_settings.scene, m_object) != NULL);
while (parent->alembicXform().getChildHeader(m_name)) {
m_name.append("_");
}
if (m_settings.use_subdiv_schema && m_is_subd) {
OSubD subd(parent->alembicXform(), m_name, m_time_sampling);
m_subdiv_schema = subd.getSchema();
}
else {
OPolyMesh mesh(parent->alembicXform(), m_name, m_time_sampling);
m_mesh_schema = mesh.getSchema();
OCompoundProperty typeContainer = m_mesh_schema.getUserProperties();
OBoolProperty type(typeContainer, "meshtype");
type.set(m_is_subd);
}
}
AbcGenericMeshWriter::~AbcGenericMeshWriter()
{
if (m_subsurf_mod) {
m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary;
}
}
bool AbcGenericMeshWriter::isAnimated() const
{
if (BKE_animdata_id_is_animated(static_cast<ID *>(m_object->data))) {
return true;
}
if (BKE_key_from_object(m_object) != NULL) {
return true;
}
/* Test modifiers. */
ModifierData *md = static_cast<ModifierData *>(m_object->modifiers.first);
while (md) {
if (md->type != eModifierType_Subsurf) {
return true;
}
md = md->next;
}
return false;
}
void AbcGenericMeshWriter::setIsAnimated(bool is_animated)
{
m_is_animated = is_animated;
}
void AbcGenericMeshWriter::do_write()
{
/* We have already stored a sample for this object. */
if (!m_first_frame && !m_is_animated) {
return;
}
bool needsfree;
struct Mesh *mesh = getFinalMesh(needsfree);
try {
if (m_settings.use_subdiv_schema && m_subdiv_schema.valid()) {
writeSubD(mesh);
}
else {
writeMesh(mesh);
}
if (needsfree) {
freeEvaluatedMesh(mesh);
}
}
catch (...) {
if (needsfree) {
freeEvaluatedMesh(mesh);
}
throw;
}
}
void AbcGenericMeshWriter::freeEvaluatedMesh(struct Mesh *mesh)
{
BKE_id_free(NULL, mesh);
}
void AbcGenericMeshWriter::writeMesh(struct Mesh *mesh)
{
std::vector<Imath::V3f> points, normals;
std::vector<int32_t> poly_verts, loop_counts;
std::vector<Imath::V3f> velocities;
bool has_flat_shaded_poly = false;
get_vertices(mesh, points);
get_topology(mesh, poly_verts, loop_counts, has_flat_shaded_poly);
if (m_first_frame && m_settings.export_face_sets) {
writeFaceSets(mesh, m_mesh_schema);
}
m_mesh_sample = OPolyMeshSchema::Sample(
V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts));
UVSample sample;
if (m_settings.export_uvs) {
const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata);
if (!sample.indices.empty() && !sample.uvs.empty()) {
OV2fGeomParam::Sample uv_sample;
uv_sample.setVals(V2fArraySample(sample.uvs));
uv_sample.setIndices(UInt32ArraySample(sample.indices));
uv_sample.setScope(kFacevaryingScope);
m_mesh_schema.setUVSourceName(name);
m_mesh_sample.setUVs(uv_sample);
}
write_custom_data(
m_mesh_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV);
}
if (m_settings.export_normals) {
get_loop_normals(mesh, normals, has_flat_shaded_poly);
ON3fGeomParam::Sample normals_sample;
if (!normals.empty()) {
normals_sample.setScope(kFacevaryingScope);
normals_sample.setVals(V3fArraySample(normals));
}
m_mesh_sample.setNormals(normals_sample);
}
if (m_is_liquid) {
getVelocities(mesh, velocities);
m_mesh_sample.setVelocities(V3fArraySample(velocities));
}
m_mesh_sample.setSelfBounds(bounds());
m_mesh_schema.set(m_mesh_sample);
writeArbGeoParams(mesh);
}
void AbcGenericMeshWriter::writeSubD(struct Mesh *mesh)
{
std::vector<float> crease_sharpness;
std::vector<Imath::V3f> points;
std::vector<int32_t> poly_verts, loop_counts;
std::vector<int32_t> crease_indices, crease_lengths;
bool has_flat_poly = false;
get_vertices(mesh, points);
get_topology(mesh, poly_verts, loop_counts, has_flat_poly);
get_creases(mesh, crease_indices, crease_lengths, crease_sharpness);
if (m_first_frame && m_settings.export_face_sets) {
writeFaceSets(mesh, m_subdiv_schema);
}
m_subdiv_sample = OSubDSchema::Sample(
V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts));
UVSample sample;
if (m_first_frame && m_settings.export_uvs) {
const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata);
if (!sample.indices.empty() && !sample.uvs.empty()) {
OV2fGeomParam::Sample uv_sample;
uv_sample.setVals(V2fArraySample(sample.uvs));
uv_sample.setIndices(UInt32ArraySample(sample.indices));
uv_sample.setScope(kFacevaryingScope);
m_subdiv_schema.setUVSourceName(name);
m_subdiv_sample.setUVs(uv_sample);
}
write_custom_data(
m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV);
}
if (!crease_indices.empty()) {
m_subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices));
m_subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths));
m_subdiv_sample.setCreaseSharpnesses(FloatArraySample(crease_sharpness));
}
m_subdiv_sample.setSelfBounds(bounds());
m_subdiv_schema.set(m_subdiv_sample);
writeArbGeoParams(mesh);
}
template<typename Schema> void AbcGenericMeshWriter::writeFaceSets(struct Mesh *me, Schema &schema)
{
std::map<std::string, std::vector<int32_t>> geo_groups;
getGeoGroups(me, geo_groups);
std::map<std::string, std::vector<int32_t>>::iterator it;
for (it = geo_groups.begin(); it != geo_groups.end(); ++it) {
OFaceSet face_set = schema.createFaceSet(it->first);
OFaceSetSchema::Sample samp;
samp.setFaces(Int32ArraySample(it->second));
face_set.getSchema().set(samp);
}
}
Mesh *AbcGenericMeshWriter::getFinalMesh(bool &r_needsfree)
{
/* We don't want subdivided mesh data */
if (m_subsurf_mod) {
m_subsurf_mod->mode |= eModifierMode_DisableTemporary;
}
r_needsfree = false;
Scene *scene = DEG_get_evaluated_scene(m_settings.depsgraph);
Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object);
struct Mesh *mesh = getEvaluatedMesh(scene, ob_eval, r_needsfree);
if (m_subsurf_mod) {
m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary;
}
if (m_settings.triangulate) {
const bool tag_only = false;
const int quad_method = m_settings.quad_method;
const int ngon_method = m_settings.ngon_method;
struct BMeshCreateParams bmcp = {false};
struct BMeshFromMeshParams bmfmp = {true, false, false, 0};
BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmcp, &bmfmp);
BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, NULL, NULL, NULL);
Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh);
BM_mesh_free(bm);
if (r_needsfree) {
BKE_id_free(NULL, mesh);
}
mesh = result;
r_needsfree = true;
}
m_custom_data_config.pack_uvs = m_settings.pack_uv;
m_custom_data_config.mpoly = mesh->mpoly;
m_custom_data_config.mloop = mesh->mloop;
m_custom_data_config.totpoly = mesh->totpoly;
m_custom_data_config.totloop = mesh->totloop;
m_custom_data_config.totvert = mesh->totvert;
return mesh;
}
void AbcGenericMeshWriter::writeArbGeoParams(struct Mesh *me)
{
if (m_is_liquid) {
/* We don't need anything more for liquid meshes. */
return;
}
if (m_first_frame && m_settings.export_vcols) {
if (m_subdiv_schema.valid()) {
write_custom_data(
m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL);
}
else {
write_custom_data(
m_mesh_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL);
}
}
}
void AbcGenericMeshWriter::getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels)
{
const int totverts = mesh->totvert;
vels.clear();
vels.resize(totverts);
ModifierData *md = get_liquid_sim_modifier(m_settings.scene, m_object);
FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(md);
FluidsimSettings *fss = fmd->fss;
if (fss->meshVelocities) {
float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities);
for (int i = 0; i < totverts; i++) {
copy_yup_from_zup(vels[i].getValue(), mesh_vels);
mesh_vels += 3;
}
}
else {
std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f));
}
}
void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh,
std::map<std::string, std::vector<int32_t>> &geo_groups)
{
const int num_poly = mesh->totpoly;
MPoly *polygons = mesh->mpoly;
for (int i = 0; i < num_poly; i++) {
MPoly &current_poly = polygons[i];
short mnr = current_poly.mat_nr;
Material *mat = BKE_object_material_get(m_object, mnr + 1);
if (!mat) {
continue;
}
std::string name = get_id_name(&mat->id);
if (geo_groups.find(name) == geo_groups.end()) {
std::vector<int32_t> faceArray;
geo_groups[name] = faceArray;
}
geo_groups[name].push_back(i);
}
if (geo_groups.size() == 0) {
Material *mat = BKE_object_material_get(m_object, 1);
std::string name = (mat) ? get_id_name(&mat->id) : "default";
std::vector<int32_t> faceArray;
for (int i = 0, e = mesh->totface; i < e; i++) {
faceArray.push_back(i);
}
geo_groups[name] = faceArray;
}
}
AbcMeshWriter::AbcMeshWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings)
: AbcGenericMeshWriter(ob, parent, time_sampling, settings)
ABCMeshWriter::ABCMeshWriter(const ABCWriterConstructorArgs &args) : ABCGenericMeshWriter(args)
{
}
AbcMeshWriter::~AbcMeshWriter()
Mesh *ABCMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
{
}
Mesh *AbcMeshWriter::getEvaluatedMesh(Scene *scene_eval,
Object *ob_eval,
bool &UNUSED(r_needsfree))
{
return mesh_get_eval_final(m_settings.depsgraph, scene_eval, ob_eval, &CD_MASK_MESH);
return BKE_object_get_evaluated_mesh(object_eval);
}
} // namespace alembic

View File

@ -19,75 +19,75 @@
* \ingroup balembic
*/
#include "abc_writer_object.h"
#include "abc_writer_abstract.h"
#include "intern/abc_customdata.h"
struct Mesh;
#include <Alembic/AbcGeom/OPolyMesh.h>
#include <Alembic/AbcGeom/OSubD.h>
struct ModifierData;
namespace blender {
namespace io {
namespace alembic {
/* Writer for Alembic meshes. Does not assume the object is a mesh object. */
class AbcGenericMeshWriter : public AbcObjectWriter {
protected:
Alembic::AbcGeom::OPolyMeshSchema m_mesh_schema;
Alembic::AbcGeom::OPolyMeshSchema::Sample m_mesh_sample;
/* Writer for Alembic geometry. Does not assume the object is a mesh object. */
class ABCGenericMeshWriter : public ABCAbstractWriter {
private:
/* Either polymesh or subd is used, depending on is_subd_.
* References to the schema must be kept, or Alembic will not properly write. */
Alembic::AbcGeom::OPolyMesh abc_poly_mesh_;
Alembic::AbcGeom::OPolyMeshSchema abc_poly_mesh_schema_;
Alembic::AbcGeom::OSubDSchema m_subdiv_schema;
Alembic::AbcGeom::OSubDSchema::Sample m_subdiv_sample;
Alembic::AbcGeom::OSubD abc_subdiv_;
Alembic::AbcGeom::OSubDSchema abc_subdiv_schema_;
Alembic::Abc::OArrayProperty m_mat_indices;
bool m_is_animated;
ModifierData *m_subsurf_mod;
/* Determines whether a poly mesh or a subdivision surface is exported.
* The value is set by an export option but only true if there is a subsdivision modifier on the
* exported object. */
bool is_subd_;
ModifierData *subsurf_modifier_;
ModifierData *liquid_sim_modifier_;
CDStreamConfig m_custom_data_config;
bool m_is_liquid;
bool m_is_subd;
public:
AbcGenericMeshWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings);
explicit ABCGenericMeshWriter(const ABCWriterConstructorArgs &args);
virtual ~ABCGenericMeshWriter();
~AbcGenericMeshWriter();
void setIsAnimated(bool is_animated);
virtual void create_alembic_objects(const HierarchyContext *context) override;
virtual const Alembic::Abc::OObject get_alembic_object() const;
protected:
virtual void do_write();
virtual bool isAnimated() const;
virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) = 0;
virtual void freeEvaluatedMesh(struct Mesh *mesh);
virtual bool is_supported(const HierarchyContext *context) const override;
virtual void do_write(HierarchyContext &context) override;
Mesh *getFinalMesh(bool &r_needsfree);
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0;
virtual void free_export_mesh(Mesh *mesh);
void writeMesh(struct Mesh *mesh);
void writeSubD(struct Mesh *mesh);
virtual bool export_as_subdivision_surface(Object *ob_eval) const;
void writeArbGeoParams(struct Mesh *mesh);
void getGeoGroups(struct Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geoGroups);
private:
void write_mesh(HierarchyContext &context, Mesh *mesh);
void write_subd(HierarchyContext &context, Mesh *mesh);
template<typename Schema> void write_face_sets(Object *object, Mesh *mesh, Schema &schema);
/* fluid surfaces support */
void getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels);
ModifierData *get_liquid_sim_modifier(Scene *scene_eval, Object *ob_eval);
template<typename Schema> void writeFaceSets(struct Mesh *mesh, Schema &schema);
void write_arb_geo_params(Mesh *me);
void get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels);
void get_geo_groups(Object *object,
Mesh *mesh,
std::map<std::string, std::vector<int32_t>> &geo_groups);
};
class AbcMeshWriter : public AbcGenericMeshWriter {
/* Writer for Alembic geometry of Blender Mesh objects. */
class ABCMeshWriter : public ABCGenericMeshWriter {
public:
AbcMeshWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings);
~AbcMeshWriter();
ABCMeshWriter(const ABCWriterConstructorArgs &args);
protected:
virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override;
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
};
} // namespace alembic

View File

@ -19,7 +19,6 @@
*/
#include "abc_writer_nurbs.h"
#include "abc_writer_transform.h"
#include "intern/abc_axis_conversion.h"
#include "DNA_curve_types.h"
@ -29,52 +28,70 @@
#include "BKE_curve.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
namespace blender {
namespace io {
namespace alembic {
using Alembic::Abc::OObject;
using Alembic::AbcGeom::FloatArraySample;
using Alembic::AbcGeom::OBoolProperty;
using Alembic::AbcGeom::OCompoundProperty;
using Alembic::AbcGeom::ONuPatch;
using Alembic::AbcGeom::ONuPatchSchema;
namespace blender {
namespace io {
namespace alembic {
AbcNurbsWriter::AbcNurbsWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings)
: AbcObjectWriter(ob, time_sampling, settings, parent)
ABCNurbsWriter::ABCNurbsWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args)
{
m_is_animated = isAnimated();
}
/* if the object is static, use the default static time sampling */
if (!m_is_animated) {
m_time_sampling = 0;
}
void ABCNurbsWriter::create_alembic_objects(const HierarchyContext *context)
{
Curve *curve = static_cast<Curve *>(context->object->data);
size_t num_nurbs = BLI_listbase_count(&curve->nurb);
OObject abc_parent = args_.abc_parent;
const char *abc_parent_path = abc_parent.getFullName().c_str();
Curve *curve = static_cast<Curve *>(m_object->data);
size_t numNurbs = BLI_listbase_count(&curve->nurb);
for (size_t i = 0; i < num_nurbs; i++) {
std::stringstream patch_name_stream;
patch_name_stream << args_.abc_name << '_' << i;
for (size_t i = 0; i < numNurbs; i++) {
std::stringstream str;
str << m_name << '_' << i;
while (parent->alembicXform().getChildHeader(str.str())) {
str << "_";
while (abc_parent.getChildHeader(patch_name_stream.str())) {
patch_name_stream << "_";
}
ONuPatch nurbs(parent->alembicXform(), str.str().c_str(), m_time_sampling);
m_nurbs_schema.push_back(nurbs.getSchema());
std::string patch_name = patch_name_stream.str();
CLOG_INFO(&LOG, 2, "exporting %s/%s", abc_parent_path, patch_name.c_str());
ONuPatch nurbs(abc_parent, patch_name.c_str(), timesample_index_);
abc_nurbs_.push_back(nurbs);
abc_nurbs_schemas_.push_back(nurbs.getSchema());
}
}
bool AbcNurbsWriter::isAnimated() const
const OObject ABCNurbsWriter::get_alembic_object() const
{
/* check if object has shape keys */
Curve *cu = static_cast<Curve *>(m_object->data);
if (abc_nurbs_.empty()) {
return OObject();
}
/* For parenting purposes within the Alembic file, all NURBS patches are equal, so just use the
* first one. */
return abc_nurbs_[0];
}
bool ABCNurbsWriter::check_is_animated(const HierarchyContext &context) const
{
/* Check if object has shape keys. */
Curve *cu = static_cast<Curve *>(context.object->data);
return (cu->key != NULL);
}
bool ABCNurbsWriter::is_supported(const HierarchyContext *context) const
{
return ELEM(context->object->type, OB_SURF, OB_CURVE);
}
static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_knots)
{
if (num_knots <= 1) {
@ -95,22 +112,13 @@ static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_
knots.push_back(2.0f * knots[num_knots] - knots[num_knots - 1]);
}
void AbcNurbsWriter::do_write()
void ABCNurbsWriter::do_write(HierarchyContext &context)
{
/* we have already stored a sample for this object. */
if (!m_first_frame && !m_is_animated) {
return;
}
if (!ELEM(m_object->type, OB_SURF, OB_CURVE)) {
return;
}
Curve *curve = static_cast<Curve *>(m_object->data);
Curve *curve = static_cast<Curve *>(context.object->data);
ListBase *nulb;
if (m_object->runtime.curve_cache->deformed_nurbs.first != NULL) {
nulb = &m_object->runtime.curve_cache->deformed_nurbs;
if (context.object->runtime.curve_cache->deformed_nurbs.first != NULL) {
nulb = &context.object->runtime.curve_cache->deformed_nurbs;
}
else {
nulb = BKE_curve_nurbs_get(curve);
@ -147,7 +155,7 @@ void AbcNurbsWriter::do_write()
/* TODO(kevin): to accommodate other software we should duplicate control
* points to indicate that a NURBS is cyclic. */
OCompoundProperty user_props = m_nurbs_schema[count].getUserProperties();
OCompoundProperty user_props = abc_nurbs_schemas_[count].getUserProperties();
if ((nu->flagu & CU_NURB_ENDPOINT) != 0) {
OBoolProperty prop(user_props, "endpoint_u");
@ -169,7 +177,7 @@ void AbcNurbsWriter::do_write()
prop.set(true);
}
m_nurbs_schema[count].set(sample);
abc_nurbs_schemas_[count].set(sample);
}
}

View File

@ -19,26 +19,37 @@
* \ingroup balembic
*/
#include "abc_writer_object.h"
#include "abc_writer_abstract.h"
#include "abc_writer_mesh.h"
#include <vector>
namespace blender {
namespace io {
namespace alembic {
class AbcNurbsWriter : public AbcObjectWriter {
std::vector<Alembic::AbcGeom::ONuPatchSchema> m_nurbs_schema;
bool m_is_animated;
class ABCNurbsWriter : public ABCAbstractWriter {
private:
std::vector<Alembic::AbcGeom::ONuPatch> abc_nurbs_;
std::vector<Alembic::AbcGeom::ONuPatchSchema> abc_nurbs_schemas_;
public:
AbcNurbsWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings);
explicit ABCNurbsWriter(const ABCWriterConstructorArgs &args);
private:
virtual void do_write();
virtual void create_alembic_objects(const HierarchyContext *context) override;
virtual const Alembic::Abc::OObject get_alembic_object() const override;
bool isAnimated() const;
protected:
virtual bool is_supported(const HierarchyContext *context) const override;
virtual void do_write(HierarchyContext &context) override;
virtual bool check_is_animated(const HierarchyContext &context) const override;
};
class ABCNurbsMeshWriter : public ABCGenericMeshWriter {
public:
explicit ABCNurbsMeshWriter(const ABCWriterConstructorArgs &args);
protected:
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
};
} // namespace alembic

View File

@ -1,95 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup balembic
*/
#include "abc_writer_object.h"
#include "DNA_object_types.h"
#include "BKE_object.h"
namespace blender {
namespace io {
namespace alembic {
AbcObjectWriter::AbcObjectWriter(Object *ob,
uint32_t time_sampling,
ExportSettings &settings,
AbcObjectWriter *parent)
: m_object(ob), m_settings(settings), m_time_sampling(time_sampling), m_first_frame(true)
{
/* This class is used as superclass for objects themselves (i.e. transforms) and for object
* data (meshes, curves, cameras, etc.). However, when writing transforms, the m_name field is
* ignored. This is a temporary tweak to get the exporter to write object data with the data
* name instead of the object name in a safe way. */
if (m_object->data == nullptr) {
m_name = get_id_name(m_object);
}
else {
ID *ob_data = static_cast<ID *>(m_object->data);
m_name = get_id_name(ob_data);
}
if (parent) {
parent->addChild(this);
}
}
AbcObjectWriter::~AbcObjectWriter()
{
}
void AbcObjectWriter::addChild(AbcObjectWriter *child)
{
m_children.push_back(child);
}
Imath::Box3d AbcObjectWriter::bounds()
{
BoundBox *bb = BKE_object_boundbox_get(this->m_object);
if (!bb) {
if (this->m_object->type != OB_CAMERA) {
ABC_LOG(m_settings.logger) << "Bounding box is null!\n";
}
return Imath::Box3d();
}
/* Convert Z-up to Y-up. This also changes which vector goes into which min/max property. */
this->m_bounds.min.x = bb->vec[0][0];
this->m_bounds.min.y = bb->vec[0][2];
this->m_bounds.min.z = -bb->vec[6][1];
this->m_bounds.max.x = bb->vec[6][0];
this->m_bounds.max.y = bb->vec[6][2];
this->m_bounds.max.z = -bb->vec[0][1];
return this->m_bounds;
}
void AbcObjectWriter::write()
{
do_write();
m_first_frame = false;
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -1,73 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup balembic
*/
#include <Alembic/Abc/All.h>
#include <Alembic/AbcGeom/All.h>
#include "abc_exporter.h"
#include "DNA_ID.h"
struct Main;
struct Object;
namespace blender {
namespace io {
namespace alembic {
class AbcTransformWriter;
class AbcObjectWriter {
protected:
Object *m_object;
ExportSettings &m_settings;
uint32_t m_time_sampling;
Imath::Box3d m_bounds;
std::vector<AbcObjectWriter *> m_children;
std::vector<std::pair<std::string, IDProperty *>> m_props;
bool m_first_frame;
std::string m_name;
public:
AbcObjectWriter(Object *ob,
uint32_t time_sampling,
ExportSettings &settings,
AbcObjectWriter *parent = NULL);
virtual ~AbcObjectWriter();
void addChild(AbcObjectWriter *child);
virtual Imath::Box3d bounds();
void write();
private:
virtual void do_write() = 0;
};
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -22,9 +22,6 @@
*/
#include "abc_writer_points.h"
#include "abc_writer_mesh.h"
#include "abc_writer_transform.h"
#include "intern/abc_util.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
@ -36,81 +33,102 @@
#include "DEG_depsgraph_query.h"
using Alembic::AbcGeom::kVertexScope;
using Alembic::AbcGeom::OPoints;
using Alembic::AbcGeom::OPointsSchema;
/* ************************************************************************** */
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
namespace blender {
namespace io {
namespace alembic {
AbcPointsWriter::AbcPointsWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings,
ParticleSystem *psys)
: AbcObjectWriter(ob, time_sampling, settings, parent)
{
m_psys = psys;
using Alembic::AbcGeom::kVertexScope;
using Alembic::AbcGeom::OPoints;
using Alembic::AbcGeom::OPointsSchema;
std::string psys_name = get_valid_abc_name(psys->name);
OPoints points(parent->alembicXform(), psys_name, m_time_sampling);
m_schema = points.getSchema();
ABCPointsWriter::ABCPointsWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args)
{
}
void AbcPointsWriter::do_write()
void ABCPointsWriter::create_alembic_objects(const HierarchyContext * /*context*/)
{
if (!m_psys) {
return;
}
CLOG_INFO(&LOG, 2, "exporting OPoints %s", args_.abc_path.c_str());
abc_points_ = OPoints(args_.abc_parent, args_.abc_name, timesample_index_);
abc_points_schema_ = abc_points_.getSchema();
}
const Alembic::Abc::OObject ABCPointsWriter::get_alembic_object() const
{
return abc_points_;
}
bool ABCPointsWriter::is_supported(const HierarchyContext *context) const
{
return ELEM(context->particle_system->part->type,
PART_EMITTER,
PART_FLUID_FLIP,
PART_FLUID_SPRAY,
PART_FLUID_BUBBLE,
PART_FLUID_FOAM,
PART_FLUID_TRACER,
PART_FLUID_SPRAYFOAM,
PART_FLUID_SPRAYBUBBLE,
PART_FLUID_FOAMBUBBLE,
PART_FLUID_SPRAYFOAMBUBBLE);
}
bool ABCPointsWriter::check_is_animated(const HierarchyContext & /*context*/) const
{
/* We assume that particles are always animated. */
return true;
}
void ABCPointsWriter::do_write(HierarchyContext &context)
{
BLI_assert(context.particle_system != nullptr);
std::vector<Imath::V3f> points;
std::vector<Imath::V3f> velocities;
std::vector<float> widths;
std::vector<uint64_t> ids;
ParticleSystem *psys = context.particle_system;
ParticleKey state;
ParticleSimulationData sim;
sim.depsgraph = m_settings.depsgraph;
sim.scene = m_settings.scene;
sim.ob = m_object;
sim.psys = m_psys;
sim.depsgraph = args_.depsgraph;
sim.scene = DEG_get_evaluated_scene(args_.depsgraph);
sim.ob = context.object;
sim.psys = psys;
m_psys->lattice_deform_data = psys_create_lattice_deform_data(&sim);
psys->lattice_deform_data = psys_create_lattice_deform_data(&sim);
uint64_t index = 0;
for (int p = 0; p < m_psys->totpart; p++) {
for (int p = 0; p < psys->totpart; p++) {
float pos[3], vel[3];
if (m_psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) {
if (psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) {
continue;
}
state.time = DEG_get_ctime(m_settings.depsgraph);
state.time = DEG_get_ctime(args_.depsgraph);
if (psys_get_particle_state(&sim, p, &state, 0) == 0) {
continue;
}
/* location */
mul_v3_m4v3(pos, m_object->imat, state.co);
mul_v3_m4v3(pos, context.object->imat, state.co);
/* velocity */
sub_v3_v3v3(vel, state.co, m_psys->particles[p].prev_state.co);
sub_v3_v3v3(vel, state.co, psys->particles[p].prev_state.co);
/* Convert Z-up to Y-up. */
points.push_back(Imath::V3f(pos[0], pos[2], -pos[1]));
velocities.push_back(Imath::V3f(vel[0], vel[2], -vel[1]));
widths.push_back(m_psys->particles[p].size);
widths.push_back(psys->particles[p].size);
ids.push_back(index++);
}
if (m_psys->lattice_deform_data) {
BKE_lattice_deform_data_destroy(m_psys->lattice_deform_data);
m_psys->lattice_deform_data = NULL;
if (psys->lattice_deform_data) {
BKE_lattice_deform_data_destroy(psys->lattice_deform_data);
psys->lattice_deform_data = NULL;
}
Alembic::Abc::P3fArraySample psample(points);
@ -119,10 +137,10 @@ void AbcPointsWriter::do_write()
Alembic::Abc::FloatArraySample wsample_array(widths);
Alembic::AbcGeom::OFloatGeomParam::Sample wsample(wsample_array, kVertexScope);
m_sample = OPointsSchema::Sample(psample, idsample, vsample, wsample);
m_sample.setSelfBounds(bounds());
m_schema.set(m_sample);
OPointsSchema::Sample sample(psample, idsample, vsample, wsample);
update_bounding_box(context.object);
sample.setSelfBounds(bounding_box_);
abc_points_schema_.set(sample);
}
} // namespace alembic

View File

@ -22,30 +22,29 @@
* \ingroup balembic
*/
#include "abc_writer_object.h"
#include "intern/abc_customdata.h"
#include "abc_writer_abstract.h"
struct ParticleSystem;
/* ************************************************************************** */
#include <Alembic/AbcGeom/OPoints.h>
namespace blender {
namespace io {
namespace alembic {
class AbcPointsWriter : public AbcObjectWriter {
Alembic::AbcGeom::OPointsSchema m_schema;
Alembic::AbcGeom::OPointsSchema::Sample m_sample;
ParticleSystem *m_psys;
class ABCPointsWriter : public ABCAbstractWriter {
Alembic::AbcGeom::OPoints abc_points_;
Alembic::AbcGeom::OPointsSchema abc_points_schema_;
public:
AbcPointsWriter(Object *ob,
AbcTransformWriter *parent,
uint32_t time_sampling,
ExportSettings &settings,
ParticleSystem *psys);
explicit ABCPointsWriter(const ABCWriterConstructorArgs &args);
void do_write();
virtual void create_alembic_objects(const HierarchyContext *context) override;
virtual const Alembic::Abc::OObject get_alembic_object() const override;
virtual bool is_supported(const HierarchyContext *context) const override;
protected:
virtual bool check_is_animated(const HierarchyContext &context) const override;
virtual void do_write(HierarchyContext &context) override;
};
} // namespace alembic

View File

@ -19,112 +19,95 @@
*/
#include "abc_writer_transform.h"
#include "abc_hierarchy_iterator.h"
#include "intern/abc_axis_conversion.h"
#include "intern/abc_util.h"
#include <OpenEXR/ImathBoxAlgo.h>
#include "BKE_object.h"
#include "DNA_object_types.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math.h"
#include "DNA_layer_types.h"
#include "DEG_depsgraph_query.h"
using Alembic::AbcGeom::OObject;
using Alembic::AbcGeom::OXform;
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.alembic"};
namespace blender {
namespace io {
namespace alembic {
AbcTransformWriter::AbcTransformWriter(Object *ob,
const OObject &abc_parent,
AbcTransformWriter *parent,
unsigned int time_sampling,
ExportSettings &settings)
: AbcObjectWriter(ob, time_sampling, settings, parent), m_proxy_from(NULL)
using Alembic::Abc::OObject;
using Alembic::AbcGeom::OXform;
using Alembic::AbcGeom::OXformSchema;
using Alembic::AbcGeom::XformSample;
ABCTransformWriter::ABCTransformWriter(const ABCWriterConstructorArgs &args)
: ABCAbstractWriter(args)
{
m_is_animated = hasAnimation(m_object);
if (!m_is_animated) {
time_sampling = 0;
}
m_xform = OXform(abc_parent, get_id_name(m_object), time_sampling);
m_schema = m_xform.getSchema();
/* Blender objects can't have a parent without inheriting the transform. */
m_inherits_xform = parent != NULL;
timesample_index_ = args_.abc_archive->time_sampling_index_transforms();
}
void AbcTransformWriter::do_write()
void ABCTransformWriter::create_alembic_objects(const HierarchyContext * /*context*/)
{
Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object);
CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str());
abc_xform_ = OXform(args_.abc_parent, args_.abc_name, timesample_index_);
abc_xform_schema_ = abc_xform_.getSchema();
}
if (m_first_frame) {
m_visibility = Alembic::AbcGeom::CreateVisibilityProperty(
m_xform, m_xform.getSchema().getTimeSampling());
}
void ABCTransformWriter::do_write(HierarchyContext &context)
{
float parent_relative_matrix[4][4]; // The object matrix relative to the parent.
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
m_visibility.set(!(ob_eval->restrictflag & OB_RESTRICT_VIEWPORT));
if (!m_first_frame && !m_is_animated) {
return;
}
float yup_mat[4][4];
create_transform_matrix(
ob_eval, yup_mat, m_inherits_xform ? ABC_MATRIX_LOCAL : ABC_MATRIX_WORLD, m_proxy_from);
// After this, parent_relative_matrix uses Y=up.
copy_m44_axis_swap(parent_relative_matrix, parent_relative_matrix, ABC_YUP_FROM_ZUP);
/* If the parent is a camera, undo its to-Maya rotation (see below). */
bool is_root_object = !m_inherits_xform || ob_eval->parent == nullptr;
if (!is_root_object && ob_eval->parent->type == OB_CAMERA) {
bool is_root_object = context.export_parent == nullptr;
if (!is_root_object && context.export_parent->type == OB_CAMERA) {
float rot_mat[4][4];
axis_angle_to_mat4_single(rot_mat, 'X', M_PI_2);
mul_m4_m4m4(yup_mat, rot_mat, yup_mat);
mul_m4_m4m4(parent_relative_matrix, rot_mat, parent_relative_matrix);
}
/* If the object is a camera, apply an extra rotation to Maya camera orientation. */
if (ob_eval->type == OB_CAMERA) {
if (context.object->type == OB_CAMERA) {
float rot_mat[4][4];
axis_angle_to_mat4_single(rot_mat, 'X', -M_PI_2);
mul_m4_m4m4(yup_mat, yup_mat, rot_mat);
mul_m4_m4m4(parent_relative_matrix, parent_relative_matrix, rot_mat);
}
if (is_root_object) {
/* Only apply scaling to root objects, parenting will propagate it. */
float scale_mat[4][4];
scale_m4_fl(scale_mat, m_settings.global_scale);
scale_mat[3][3] = m_settings.global_scale; /* also scale translation */
mul_m4_m4m4(yup_mat, yup_mat, scale_mat);
yup_mat[3][3] /= m_settings.global_scale; /* normalise the homogeneous component */
scale_m4_fl(scale_mat, args_.export_params->global_scale);
scale_mat[3][3] = args_.export_params->global_scale; /* also scale translation */
mul_m4_m4m4(parent_relative_matrix, parent_relative_matrix, scale_mat);
parent_relative_matrix[3][3] /=
args_.export_params->global_scale; /* normalise the homogeneous component */
}
m_matrix = convert_matrix_datatype(yup_mat);
m_sample.setMatrix(m_matrix);
/* Always export as "inherits transform", as this is the only way in which Blender works. The
* above code has already taken care of writing the correct matrix so that this option is not
* necessary. However, certain packages (for example the USD Alembic exporter) are incompatible
* with non-inheriting transforms and will completely ignore the transform if that is used. */
m_sample.setInheritsXforms(true);
m_schema.set(m_sample);
XformSample xform_sample;
xform_sample.setMatrix(convert_matrix_datatype(parent_relative_matrix));
xform_sample.setInheritsXforms(true);
abc_xform_schema_.set(xform_sample);
}
Imath::Box3d AbcTransformWriter::bounds()
const OObject ABCTransformWriter::get_alembic_object() const
{
Imath::Box3d bounds;
return abc_xform_;
}
for (int i = 0; i < m_children.size(); i++) {
Imath::Box3d box(m_children[i]->bounds());
bounds.extendBy(box);
bool ABCTransformWriter::check_is_animated(const HierarchyContext &context) const
{
if (context.duplicator != NULL) {
/* This object is being duplicated, so could be emitted by a particle system and thus
* influenced by forces. TODO(Sybren): Make this more strict. Probably better to get from the
* depsgraph whether this object instance has a time source. */
return true;
}
return Imath::transform(bounds, m_matrix);
}
bool AbcTransformWriter::hasAnimation(Object * /*ob*/) const
{
return true;
return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
}
} // namespace alembic

View File

@ -19,44 +19,27 @@
* \ingroup balembic
*/
#include "abc_writer_object.h"
#include "abc_writer_abstract.h"
#include <Alembic/AbcGeom/All.h>
#include <Alembic/AbcGeom/OXform.h>
namespace blender {
namespace io {
namespace alembic {
class AbcTransformWriter : public AbcObjectWriter {
Alembic::AbcGeom::OXform m_xform;
Alembic::AbcGeom::OXformSchema m_schema;
Alembic::AbcGeom::XformSample m_sample;
Alembic::AbcGeom::OVisibilityProperty m_visibility;
Alembic::Abc::M44d m_matrix;
bool m_is_animated;
bool m_inherits_xform;
public:
Object *m_proxy_from;
public:
AbcTransformWriter(Object *ob,
const Alembic::AbcGeom::OObject &abc_parent,
AbcTransformWriter *parent,
unsigned int time_sampling,
ExportSettings &settings);
Alembic::AbcGeom::OXform &alembicXform()
{
return m_xform;
}
virtual Imath::Box3d bounds();
class ABCTransformWriter : public ABCAbstractWriter {
private:
virtual void do_write();
Alembic::AbcGeom::OXform abc_xform_;
Alembic::AbcGeom::OXformSchema abc_xform_schema_;
bool hasAnimation(Object *ob) const;
public:
explicit ABCTransformWriter(const ABCWriterConstructorArgs &args);
virtual void create_alembic_objects(const HierarchyContext *context) override;
protected:
virtual void do_write(HierarchyContext &context) override;
virtual bool check_is_animated(const HierarchyContext &context) const override;
virtual const Alembic::Abc::OObject get_alembic_object() const override;
};
} // namespace alembic

View File

@ -70,36 +70,19 @@
#include "WM_api.h"
#include "WM_types.h"
using Alembic::Abc::Int32ArraySamplePtr;
using Alembic::Abc::ObjectHeader;
using Alembic::AbcGeom::kWrapExisting;
using Alembic::AbcGeom::MetaData;
using Alembic::AbcGeom::P3fArraySamplePtr;
using Alembic::AbcGeom::ICamera;
using Alembic::AbcGeom::ICompoundProperty;
using Alembic::AbcGeom::ICurves;
using Alembic::AbcGeom::ICurvesSchema;
using Alembic::AbcGeom::IFaceSet;
using Alembic::AbcGeom::ILight;
using Alembic::AbcGeom::IN3fArrayProperty;
using Alembic::AbcGeom::IN3fGeomParam;
using Alembic::AbcGeom::INuPatch;
using Alembic::AbcGeom::IObject;
using Alembic::AbcGeom::IPoints;
using Alembic::AbcGeom::IPointsSchema;
using Alembic::AbcGeom::IPolyMesh;
using Alembic::AbcGeom::IPolyMeshSchema;
using Alembic::AbcGeom::ISampleSelector;
using Alembic::AbcGeom::ISubD;
using Alembic::AbcGeom::IV2fGeomParam;
using Alembic::AbcGeom::IXform;
using Alembic::AbcGeom::IXformSchema;
using Alembic::AbcGeom::N3fArraySamplePtr;
using Alembic::AbcGeom::V3fArraySamplePtr;
using Alembic::AbcGeom::XformSample;
using Alembic::AbcGeom::MetaData;
using Alembic::AbcMaterial::IMaterial;
using namespace blender::io::alembic;

View File

@ -197,8 +197,8 @@ class AbstractHierarchyIterator {
/* Iterate over the depsgraph, create writers, and tell the writers to write.
* Main entry point for the AbstractHierarchyIterator, must be called for every to-be-exported
* frame. */
void iterate_and_write();
* (sub)frame. */
virtual void iterate_and_write();
/* Release all writers. Call after all frames have been exported. */
void release_writers();

View File

@ -24,6 +24,8 @@ set(INC
../../../source/blender/blenlib
../../../source/blender/blenkernel
../../../source/blender/io/alembic
../../../source/blender/io/common
../../../source/blender/io/usd/intern
../../../source/blender/makesdna
../../../source/blender/depsgraph
${ALEMBIC_INCLUDE_DIRS}

View File

@ -1,11 +1,12 @@
#include "testing/testing.h"
// Keep first since utildefines defines AT which conflicts with STL
#include "exporter/abc_exporter.h"
#include "exporter/abc_archive.h"
#include "intern/abc_util.h"
extern "C" {
#include "BKE_main.h"
#include "BLI_fileops.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "DNA_scene_types.h"
@ -13,140 +14,150 @@ extern "C" {
#include "DEG_depsgraph.h"
using namespace blender::io::alembic;
class TestableAbcExporter : public AbcExporter {
public:
TestableAbcExporter(Main *bmain, const char *filename, ExportSettings &settings)
: AbcExporter(bmain, filename, settings)
{
}
void getShutterSamples(unsigned int nr_of_samples,
bool time_relative,
std::vector<double> &samples)
{
AbcExporter::getShutterSamples(nr_of_samples, time_relative, samples);
}
void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames)
{
AbcExporter::getFrameSet(nr_of_samples, frames);
}
};
namespace blender {
namespace io {
namespace alembic {
class AlembicExportTest : public testing::Test {
protected:
ExportSettings settings;
ABCArchive *abc_archive;
AlembicExportParams params;
Scene scene;
Depsgraph *depsgraph;
TestableAbcExporter *exporter;
Main *bmain;
virtual void SetUp()
{
settings.frame_start = 31.0;
settings.frame_end = 223.0;
abc_archive = nullptr;
/* Fake a 25 FPS scene with a nonzero base (because that's sometimes forgotten) */
scene.r.frs_sec = 50;
scene.r.frs_sec_base = 2;
strcpy(scene.id.name, "SCTestScene");
bmain = BKE_main_new();
/* TODO(sergey): Pass scene layer somehow? */
ViewLayer *view_layer = (ViewLayer *)scene.view_layers.first;
settings.depsgraph = depsgraph = DEG_graph_new(bmain, &scene, view_layer, DAG_EVAL_VIEWPORT);
settings.scene = &scene;
settings.view_layer = view_layer;
exporter = NULL;
depsgraph = DEG_graph_new(bmain, &scene, view_layer, DAG_EVAL_RENDER);
}
virtual void TearDown()
{
BKE_main_free(bmain);
DEG_graph_free(depsgraph);
delete exporter;
deleteArchive();
}
// Call after setting up the settings.
void createExporter()
// Call after setting up the parameters.
void createArchive()
{
exporter = new TestableAbcExporter(bmain, "somefile.abc", settings);
if (abc_archive != nullptr) {
deleteArchive();
}
abc_archive = new ABCArchive(bmain, &scene, params, "somefile.abc");
}
void deleteArchive()
{
delete abc_archive;
if (BLI_exists("somefile.abc")) {
BLI_delete("somefile.abc", false, false);
}
abc_archive = nullptr;
}
};
TEST_F(AlembicExportTest, TimeSamplesFullShutter)
TEST_F(AlembicExportTest, TimeSamplesFullShutterUniform)
{
settings.shutter_open = 0.0;
settings.shutter_close = 1.0;
/* Test 5 samples per frame, for 2 frames. */
params.shutter_open = 0.0;
params.shutter_close = 1.0;
params.frame_start = 31.0;
params.frame_end = 32.0;
params.frame_samples_xform = params.frame_samples_shape = 5;
createArchive();
std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end());
EXPECT_EQ(10, frames.size());
EXPECT_NEAR(31.0, frames[0], 1e-5);
EXPECT_NEAR(31.2, frames[1], 1e-5);
EXPECT_NEAR(31.4, frames[2], 1e-5);
EXPECT_NEAR(31.6, frames[3], 1e-5);
EXPECT_NEAR(31.8, frames[4], 1e-5);
EXPECT_NEAR(32.0, frames[5], 1e-5);
EXPECT_NEAR(32.2, frames[6], 1e-5);
EXPECT_NEAR(32.4, frames[7], 1e-5);
EXPECT_NEAR(32.6, frames[8], 1e-5);
EXPECT_NEAR(32.8, frames[9], 1e-5);
createExporter();
std::vector<double> samples;
for (double frame : frames) {
EXPECT_TRUE(abc_archive->is_xform_frame(frame));
EXPECT_TRUE(abc_archive->is_shape_frame(frame));
}
}
/* test 5 samples per frame */
exporter->getShutterSamples(5, true, samples);
EXPECT_EQ(5, samples.size());
EXPECT_NEAR(1.240, samples[0], 1e-5f);
EXPECT_NEAR(1.248, samples[1], 1e-5f);
EXPECT_NEAR(1.256, samples[2], 1e-5f);
EXPECT_NEAR(1.264, samples[3], 1e-5f);
EXPECT_NEAR(1.272, samples[4], 1e-5f);
/* test same, but using frame number offset instead of time */
exporter->getShutterSamples(5, false, samples);
EXPECT_EQ(5, samples.size());
EXPECT_NEAR(0.0, samples[0], 1e-5f);
EXPECT_NEAR(0.2, samples[1], 1e-5f);
EXPECT_NEAR(0.4, samples[2], 1e-5f);
EXPECT_NEAR(0.6, samples[3], 1e-5f);
EXPECT_NEAR(0.8, samples[4], 1e-5f);
/* use the same setup to test getFrameSet() */
std::set<double> frames;
exporter->getFrameSet(5, frames);
EXPECT_EQ(965, frames.size());
EXPECT_EQ(1, frames.count(31.0));
EXPECT_EQ(1, frames.count(31.2));
EXPECT_EQ(1, frames.count(31.4));
EXPECT_EQ(1, frames.count(31.6));
EXPECT_EQ(1, frames.count(31.8));
TEST_F(AlembicExportTest, TimeSamplesFullShutterDifferent)
{
/* Test 3 samples per frame for transforms, and 2 per frame for shapes, for 2 frames. */
params.shutter_open = 0.0;
params.shutter_close = 1.0;
params.frame_start = 31.0;
params.frame_end = 32.0;
params.frame_samples_xform = 3;
params.frame_samples_shape = 2;
createArchive();
std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end());
EXPECT_EQ(8, frames.size());
EXPECT_NEAR(31.0, frames[0], 1e-5); // transform + shape
EXPECT_TRUE(abc_archive->is_xform_frame(frames[0]));
EXPECT_TRUE(abc_archive->is_shape_frame(frames[0]));
EXPECT_NEAR(31.33333, frames[1], 1e-5); // transform
EXPECT_TRUE(abc_archive->is_xform_frame(frames[1]));
EXPECT_FALSE(abc_archive->is_shape_frame(frames[1]));
EXPECT_NEAR(31.5, frames[2], 1e-5); // shape
EXPECT_FALSE(abc_archive->is_xform_frame(frames[2]));
EXPECT_TRUE(abc_archive->is_shape_frame(frames[2]));
EXPECT_NEAR(31.66666, frames[3], 1e-5); // transform
EXPECT_TRUE(abc_archive->is_xform_frame(frames[3]));
EXPECT_FALSE(abc_archive->is_shape_frame(frames[3]));
EXPECT_NEAR(32.0, frames[4], 1e-5); // transform + shape
EXPECT_TRUE(abc_archive->is_xform_frame(frames[4]));
EXPECT_TRUE(abc_archive->is_shape_frame(frames[4]));
EXPECT_NEAR(32.33333, frames[5], 1e-5); // transform
EXPECT_TRUE(abc_archive->is_xform_frame(frames[5]));
EXPECT_FALSE(abc_archive->is_shape_frame(frames[5]));
EXPECT_NEAR(32.5, frames[6], 1e-5); // shape
EXPECT_FALSE(abc_archive->is_xform_frame(frames[6]));
EXPECT_TRUE(abc_archive->is_shape_frame(frames[6]));
EXPECT_NEAR(32.66666, frames[7], 1e-5); // transform
EXPECT_TRUE(abc_archive->is_xform_frame(frames[7]));
EXPECT_FALSE(abc_archive->is_shape_frame(frames[7]));
}
TEST_F(AlembicExportTest, TimeSamples180degShutter)
{
settings.shutter_open = -0.25;
settings.shutter_close = 0.25;
createExporter();
std::vector<double> samples;
/* test 5 samples per frame */
exporter->getShutterSamples(5, true, samples);
EXPECT_EQ(5, samples.size());
EXPECT_NEAR(1.230, samples[0], 1e-5f);
EXPECT_NEAR(1.234, samples[1], 1e-5f);
EXPECT_NEAR(1.238, samples[2], 1e-5f);
EXPECT_NEAR(1.242, samples[3], 1e-5f);
EXPECT_NEAR(1.246, samples[4], 1e-5f);
/* test same, but using frame number offset instead of time */
exporter->getShutterSamples(5, false, samples);
EXPECT_EQ(5, samples.size());
EXPECT_NEAR(-0.25, samples[0], 1e-5f);
EXPECT_NEAR(-0.15, samples[1], 1e-5f);
EXPECT_NEAR(-0.05, samples[2], 1e-5f);
EXPECT_NEAR(0.05, samples[3], 1e-5f);
EXPECT_NEAR(0.15, samples[4], 1e-5f);
/* Use the same setup to test getFrameSet().
* Here only a few numbers are tested, due to rounding issues. */
std::set<double> frames;
exporter->getFrameSet(5, frames);
EXPECT_EQ(965, frames.size());
EXPECT_EQ(1, frames.count(30.75));
EXPECT_EQ(1, frames.count(30.95));
EXPECT_EQ(1, frames.count(31.15));
/* Test 5 samples per frame, for 2 frames. */
params.shutter_open = -0.25;
params.shutter_close = 0.25;
params.frame_start = 31.0;
params.frame_end = 32.0;
params.frame_samples_xform = params.frame_samples_shape = 5;
createArchive();
std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end());
EXPECT_EQ(10, frames.size());
EXPECT_NEAR(31 - 0.25, frames[0], 1e-5);
EXPECT_NEAR(31 - 0.15, frames[1], 1e-5);
EXPECT_NEAR(31 - 0.05, frames[2], 1e-5);
EXPECT_NEAR(31 + 0.05, frames[3], 1e-5);
EXPECT_NEAR(31 + 0.15, frames[4], 1e-5);
EXPECT_NEAR(32 - 0.25, frames[5], 1e-5);
EXPECT_NEAR(32 - 0.15, frames[6], 1e-5);
EXPECT_NEAR(32 - 0.05, frames[7], 1e-5);
EXPECT_NEAR(32 + 0.05, frames[8], 1e-5);
EXPECT_NEAR(32 + 0.15, frames[9], 1e-5);
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -8,7 +8,9 @@ extern "C" {
#include "BLI_utildefines.h"
}
using namespace blender::io::alembic;
namespace blender {
namespace io {
namespace alembic {
TEST(abc_matrix, CreateRotationMatrixY_YfromZ)
{
@ -286,3 +288,7 @@ TEST(abc_matrix, CopyM44AxisSwapWithScale_gimbal_ZfromY)
EXPECT_M4_NEAR(expect, result, 1e-5f);
}
} // namespace alembic
} // namespace io
} // namespace blender

View File

@ -214,7 +214,7 @@ class DupliGroupExportTest(AbstractAlembicTest):
self.run_blender('dupligroup-scene.blend', script)
# Now check the resulting Alembic file.
xform = self.abcprop(abc, '/Real_Cube/Linked_Suzanne/Cylinder/Suzanne/.xform')
xform = self.abcprop(abc, '/Real_Cube/Linked_Suzanne/Cylinder-0/Suzanne-1/.xform')
self.assertEqual(1, xform['.inherits'])
self.assertAlmostEqualFloatArray(
xform['.vals'],
@ -232,7 +232,7 @@ class DupliGroupExportTest(AbstractAlembicTest):
self.run_blender('dupligroup-scene.blend', script)
# Now check the resulting Alembic file.
xform = self.abcprop(abc, '/Suzanne/.xform')
xform = self.abcprop(abc, '/Suzanne-1/.xform')
self.assertEqual(1, xform['.inherits'])
self.assertAlmostEqualFloatArray(