Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/io/alembic/exporter')
-rw-r--r--source/blender/io/alembic/exporter/abc_export_capi.cc239
-rw-r--r--source/blender/io/alembic/exporter/abc_exporter.cc673
-rw-r--r--source/blender/io/alembic/exporter/abc_exporter.h127
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_archive.cc98
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_archive.h50
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_camera.cc79
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_camera.h45
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_curves.cc188
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_curves.h55
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_hair.cc291
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_hair.h62
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_mball.cc95
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_mball.h56
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_mesh.cc591
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_mesh.h91
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_nurbs.cc170
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_nurbs.h42
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_object.cc87
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_object.h69
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_points.cc122
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_points.h49
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_transform.cc124
-rw-r--r--source/blender/io/alembic/exporter/abc_writer_transform.h60
23 files changed, 3463 insertions, 0 deletions
diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc
new file mode 100644
index 00000000000..3f8e95a6128
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_export_capi.cc
@@ -0,0 +1,239 @@
+/*
+ * 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_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 "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+#include "DNA_modifier_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_blender_version.h"
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_main.h"
+#include "BKE_scene.h"
+
+#include "BLI_fileops.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+}
+
+struct ExportJobData {
+ ViewLayer *view_layer;
+ Main *bmain;
+ wmWindowManager *wm;
+
+ char filename[1024];
+ ExportSettings settings;
+
+ short *stop;
+ short *do_update;
+ float *progress;
+
+ bool was_canceled;
+ bool export_ok;
+};
+
+static void export_startjob(void *customdata, short *stop, short *do_update, float *progress)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ 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);
+
+ try {
+ AbcExporter exporter(data->bmain, data->filename, data->settings);
+
+ Scene *scene = data->settings.scene; /* for the CFRA macro */
+ const int orig_frame = CFRA;
+
+ data->was_canceled = false;
+ exporter(do_update, progress, &data->was_canceled);
+
+ if (CFRA != orig_frame) {
+ CFRA = orig_frame;
+
+ BKE_scene_graph_update_for_newframe(data->settings.depsgraph, data->bmain);
+ }
+
+ 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";
+ }
+}
+
+static void export_endjob(void *customdata)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ DEG_graph_free(data->settings.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,
+ const char *filepath,
+ const struct AlembicExportParams *params,
+ bool as_background_job)
+{
+ 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);
+
+ /* 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);
+ }
+
+ 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);
+
+ /* 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_start(CTX_wm_manager(C), wm_job);
+ }
+ else {
+ /* Fake a job context, so that we don't need NULL pointer checks while exporting. */
+ short stop = 0, do_update = 0;
+ float progress = 0.f;
+
+ export_startjob(job, &stop, &do_update, &progress);
+ export_endjob(job);
+ export_ok = job->export_ok;
+
+ MEM_freeN(job);
+ }
+
+ return export_ok;
+}
diff --git a/source/blender/io/alembic/exporter/abc_exporter.cc b/source/blender/io/alembic/exporter/abc_exporter.cc
new file mode 100644
index 00000000000..0de6c393e5a
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_exporter.cc
@@ -0,0 +1,673 @@
+/*
+ * 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;
+
+/* ************************************************************************** */
+
+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 ob: 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 dupligroup).
+ * 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);
+}
diff --git a/source/blender/io/alembic/exporter/abc_exporter.h b/source/blender/io/alembic/exporter/abc_exporter.h
new file mode 100644
index 00000000000..c1c54cb2208
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_exporter.h
@@ -0,0 +1,127 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_EXPORTER_H__
+#define __ABC_EXPORTER_H__
+
+#include <Alembic/Abc/All.h>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "intern/abc_util.h"
+
+class AbcObjectWriter;
+class AbcTransformWriter;
+class ArchiveWriter;
+
+struct Base;
+struct Depsgraph;
+struct Main;
+struct Object;
+struct Scene;
+struct ViewLayer;
+
+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);
+};
+
+#endif /* __ABC_EXPORTER_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_archive.cc b/source/blender/io/alembic/exporter/abc_writer_archive.cc
new file mode 100644
index 00000000000..40926532f85
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_archive.cc
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+/* 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;
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_archive.h b/source/blender/io/alembic/exporter/abc_writer_archive.h
new file mode 100644
index 00000000000..737717c1710
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_archive.h
@@ -0,0 +1,50 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_ARCHIVE_H__
+#define __ABC_WRITER_ARCHIVE_H__
+
+#include <Alembic/Abc/All.h>
+#include <Alembic/AbcCoreOgawa/All.h>
+
+#include <fstream>
+
+struct Main;
+struct Scene;
+
+/* 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.
+ */
+
+class ArchiveWriter {
+ std::ofstream m_outfile;
+ Alembic::Abc::OArchive m_archive;
+
+ public:
+ ArchiveWriter(const char *filename, const std::string &abc_scene_name, const Scene *scene);
+
+ Alembic::Abc::OArchive &archive();
+};
+
+#endif /* __ABC_WRITER_ARCHIVE_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_camera.cc b/source/blender/io/alembic/exporter/abc_writer_camera.cc
new file mode 100644
index 00000000000..07ae81e584f
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_camera.cc
@@ -0,0 +1,79 @@
+/*
+ * 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_camera.h"
+#include "abc_writer_transform.h"
+
+#include "DNA_camera_types.h"
+#include "DNA_object_types.h"
+
+using Alembic::AbcGeom::OCamera;
+using Alembic::AbcGeom::OFloatProperty;
+
+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();
+
+ 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);
+}
+
+void AbcCameraWriter::do_write()
+{
+ Camera *cam = static_cast<Camera *>(m_object->data);
+
+ m_stereo_distance.set(cam->stereo.convergence_distance);
+ m_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);
+
+ 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());
+ }
+ else {
+ m_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);
+
+ m_camera_sample.setLensSqueezeRatio(1.0);
+ m_camera_schema.set(m_camera_sample);
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_camera.h b/source/blender/io/alembic/exporter/abc_writer_camera.h
new file mode 100644
index 00000000000..3b515911a48
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_camera.h
@@ -0,0 +1,45 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_CAMERA_H__
+#define __ABC_WRITER_CAMERA_H__
+
+#include "abc_writer_object.h"
+
+/* ************************************************************************** */
+
+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;
+
+ public:
+ AbcCameraWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ private:
+ virtual void do_write();
+};
+
+#endif /* __ABC_WRITER_CAMERA_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.cc b/source/blender/io/alembic/exporter/abc_writer_curves.cc
new file mode 100644
index 00000000000..3f9a53cffc6
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_curves.cc
@@ -0,0 +1,188 @@
+/*
+ * 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_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"
+
+#include "BKE_curve.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+
+using Alembic::AbcGeom::OCompoundProperty;
+using Alembic::AbcGeom::OCurves;
+using Alembic::AbcGeom::OCurvesSchema;
+using Alembic::AbcGeom::OInt16Property;
+using Alembic::AbcGeom::ON3fGeomParam;
+using Alembic::AbcGeom::OV2fGeomParam;
+
+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();
+
+ Curve *cu = static_cast<Curve *>(m_object->data);
+ OCompoundProperty user_props = m_schema.getUserProperties();
+ OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME);
+ user_prop_resolu.set(cu->resolu);
+}
+
+void AbcCurveWriter::do_write()
+{
+ Curve *curve = static_cast<Curve *>(m_object->data);
+
+ std::vector<Imath::V3f> verts;
+ std::vector<int32_t> vert_counts;
+ std::vector<float> widths;
+ std::vector<float> weights;
+ std::vector<float> knots;
+ std::vector<uint8_t> orders;
+ Imath::V3f temp_vert;
+
+ Alembic::AbcGeom::BasisType curve_basis;
+ Alembic::AbcGeom::CurveType curve_type;
+ Alembic::AbcGeom::CurvePeriodicity periodicity;
+
+ Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first);
+ for (; nurbs; nurbs = nurbs->next) {
+ if (nurbs->bp) {
+ curve_basis = Alembic::AbcGeom::kNoBasis;
+ curve_type = Alembic::AbcGeom::kVariableOrder;
+
+ const int totpoint = nurbs->pntsu * nurbs->pntsv;
+
+ const BPoint *point = nurbs->bp;
+
+ for (int i = 0; i < totpoint; i++, point++) {
+ copy_yup_from_zup(temp_vert.getValue(), point->vec);
+ verts.push_back(temp_vert);
+ weights.push_back(point->vec[3]);
+ widths.push_back(point->radius);
+ }
+ }
+ else if (nurbs->bezt) {
+ curve_basis = Alembic::AbcGeom::kBezierBasis;
+ curve_type = Alembic::AbcGeom::kCubic;
+
+ const int totpoint = nurbs->pntsu;
+
+ const BezTriple *bezier = nurbs->bezt;
+
+ /* TODO(kevin): store info about handles, Alembic doesn't have this. */
+ for (int i = 0; i < totpoint; i++, bezier++) {
+ copy_yup_from_zup(temp_vert.getValue(), bezier->vec[1]);
+ verts.push_back(temp_vert);
+ widths.push_back(bezier->radius);
+ }
+ }
+
+ if ((nurbs->flagu & CU_NURB_ENDPOINT) != 0) {
+ periodicity = Alembic::AbcGeom::kNonPeriodic;
+ }
+ else if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
+ periodicity = Alembic::AbcGeom::kPeriodic;
+
+ /* Duplicate the start points to indicate that the curve is actually
+ * cyclic since other software need those.
+ */
+
+ for (int i = 0; i < nurbs->orderu; i++) {
+ verts.push_back(verts[i]);
+ }
+ }
+
+ if (nurbs->knotsu != NULL) {
+ const size_t num_knots = KNOTSU(nurbs);
+
+ /* Add an extra knot at the beginning and end of the array since most apps
+ * require/expect them. */
+ knots.resize(num_knots + 2);
+
+ for (int i = 0; i < num_knots; i++) {
+ knots[i + 1] = nurbs->knotsu[i];
+ }
+
+ if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
+ knots[0] = nurbs->knotsu[0];
+ knots[num_knots - 1] = nurbs->knotsu[num_knots - 1];
+ }
+ else {
+ knots[0] = (2.0f * nurbs->knotsu[0] - nurbs->knotsu[1]);
+ knots[num_knots - 1] = (2.0f * nurbs->knotsu[num_knots - 1] -
+ nurbs->knotsu[num_knots - 2]);
+ }
+ }
+
+ orders.push_back(nurbs->orderu);
+ vert_counts.push_back(verts.size());
+ }
+
+ 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);
+
+ m_sample.setSelfBounds(bounds());
+ m_schema.set(m_sample);
+}
+
+AbcCurveMeshWriter::AbcCurveMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcGenericMeshWriter(ob, parent, time_sampling, settings)
+{
+}
+
+Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/,
+ Object *ob_eval,
+ bool &r_needsfree)
+{
+ Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ if (mesh_eval != NULL) {
+ /* Mesh_eval only exists when generative modifiers are in use. */
+ r_needsfree = false;
+ return mesh_eval;
+ }
+
+ r_needsfree = true;
+ return BKE_mesh_new_nomain_from_curve(ob_eval);
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.h b/source/blender/io/alembic/exporter/abc_writer_curves.h
new file mode 100644
index 00000000000..83f0289dd2d
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_curves.h
@@ -0,0 +1,55 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_CURVES_H__
+#define __ABC_WRITER_CURVES_H__
+
+#include "abc_writer_mesh.h"
+#include "abc_writer_object.h"
+
+class AbcCurveWriter : public AbcObjectWriter {
+ Alembic::AbcGeom::OCurvesSchema m_schema;
+ Alembic::AbcGeom::OCurvesSchema::Sample m_sample;
+
+ public:
+ AbcCurveWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ protected:
+ void do_write();
+};
+
+class AbcCurveMeshWriter : public AbcGenericMeshWriter {
+ public:
+ AbcCurveMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ protected:
+ Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree);
+};
+
+#endif /* __ABC_WRITER_CURVES_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.cc b/source/blender/io/alembic/exporter/abc_writer_hair.cc
new file mode 100644
index 00000000000..ceb2eb7d316
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_hair.cc
@@ -0,0 +1,291 @@
+/*
+ * 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_hair.h"
+#include "abc_writer_transform.h"
+#include "intern/abc_axis_conversion.h"
+
+#include <cstdio>
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_math_geom.h"
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_particle.h"
+
+using Alembic::Abc::P3fArraySamplePtr;
+
+using Alembic::AbcGeom::OCurves;
+using Alembic::AbcGeom::OCurvesSchema;
+using Alembic::AbcGeom::ON3fGeomParam;
+using Alembic::AbcGeom::OV2fGeomParam;
+
+/* ************************************************************************** */
+
+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)
+{
+ 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()
+{
+ if (!m_psys) {
+ return;
+ }
+ Mesh *mesh = mesh_get_eval_final(
+ m_settings.depsgraph, m_settings.scene, m_object, &CD_MASK_MESH);
+ BKE_mesh_tessface_ensure(mesh);
+
+ std::vector<Imath::V3f> verts;
+ std::vector<int32_t> hvertices;
+ 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;
+
+ if (!export_children || part->draw & PART_DRAW_PARENT) {
+ write_hair_sample(mesh, part, verts, norm_values, uv_values, hvertices);
+ }
+
+ if (export_children) {
+ write_hair_child_sample(mesh, part, 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);
+
+ if (!uv_values.empty()) {
+ OV2fGeomParam::Sample uv_smp;
+ uv_smp.setVals(uv_values);
+ m_sample.setUVs(uv_smp);
+ }
+
+ if (!norm_values.empty()) {
+ ON3fGeomParam::Sample norm_smp;
+ norm_smp.setVals(norm_values);
+ m_sample.setNormals(norm_smp);
+ }
+
+ m_sample.setSelfBounds(bounds());
+ m_schema.set(m_sample);
+}
+
+void AbcHairWriter::write_hair_sample(Mesh *mesh,
+ ParticleSettings *part,
+ std::vector<Imath::V3f> &verts,
+ std::vector<Imath::V3f> &norm_values,
+ std::vector<Imath::V2f> &uv_values,
+ std::vector<int32_t> &hvertices)
+{
+ /* Get untransformed vertices, there's a xform under the hair. */
+ float inv_mat[4][4];
+ invert_m4_m4_safe(inv_mat, m_object->obmat);
+
+ MTFace *mtface = mesh->mtface;
+ MFace *mface = mesh->mface;
+ MVert *mverts = mesh->mvert;
+
+ if ((!mtface || !mface) && !m_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;
+ }
+
+ ParticleData *pa = m_psys->particles;
+ int k;
+
+ ParticleCacheKey **cache = m_psys->pathcache;
+ ParticleCacheKey *path;
+ float normal[3];
+ Imath::V3f tmp_nor;
+
+ for (int p = 0; p < m_psys->totpart; p++, pa++) {
+ /* underlying info for faces-only emission */
+ path = cache[p];
+
+ /* Write UV and normal vectors */
+ if (part->from == PART_FROM_FACE && mtface) {
+ const int num = pa->num_dmcache >= 0 ? pa->num_dmcache : pa->num;
+
+ if (num < mesh->totface) {
+ /* TODO(Sybren): check whether the NULL check here and if(mface) are actually required */
+ MFace *face = mface == NULL ? NULL : &mface[num];
+ MTFace *tface = mtface + num;
+
+ if (mface) {
+ float r_uv[2], mapfw[4], vec[3];
+
+ psys_interpolate_uvs(tface, face->v4, pa->fuv, r_uv);
+ uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1]));
+
+ psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, normal, NULL, NULL, NULL);
+
+ copy_yup_from_zup(tmp_nor.getValue(), normal);
+ norm_values.push_back(tmp_nor);
+ }
+ }
+ else {
+ std::fprintf(stderr, "Particle to faces overflow (%d/%d)\n", num, mesh->totface);
+ }
+ }
+ else if (part->from == PART_FROM_VERT && mtface) {
+ /* vertex id */
+ const int num = (pa->num_dmcache >= 0) ? pa->num_dmcache : pa->num;
+
+ /* iterate over all faces to find a corresponding underlying UV */
+ for (int n = 0; n < mesh->totface; n++) {
+ MFace *face = &mface[n];
+ MTFace *tface = mtface + n;
+ unsigned int vtx[4];
+ vtx[0] = face->v1;
+ vtx[1] = face->v2;
+ vtx[2] = face->v3;
+ vtx[3] = face->v4;
+ bool found = false;
+
+ for (int o = 0; o < 4; o++) {
+ if (o > 2 && vtx[o] == 0) {
+ break;
+ }
+
+ if (vtx[o] == num) {
+ uv_values.push_back(Imath::V2f(tface->uv[o][0], tface->uv[o][1]));
+
+ MVert *mv = mverts + vtx[o];
+
+ normal_short_to_float_v3(normal, mv->no);
+ copy_yup_from_zup(tmp_nor.getValue(), normal);
+ norm_values.push_back(tmp_nor);
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+ }
+
+ int steps = path->segments + 1;
+ hvertices.push_back(steps);
+
+ for (k = 0; k < steps; k++, path++) {
+ float vert[3];
+ copy_v3_v3(vert, path->co);
+ mul_m4_v3(inv_mat, vert);
+
+ /* Convert Z-up to Y-up. */
+ verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1]));
+ }
+ }
+}
+
+void AbcHairWriter::write_hair_child_sample(Mesh *mesh,
+ ParticleSettings *part,
+ std::vector<Imath::V3f> &verts,
+ std::vector<Imath::V3f> &norm_values,
+ std::vector<Imath::V2f> &uv_values,
+ std::vector<int32_t> &hvertices)
+{
+ /* Get untransformed vertices, there's a xform under the hair. */
+ float inv_mat[4][4];
+ invert_m4_m4_safe(inv_mat, m_object->obmat);
+
+ MTFace *mtface = mesh->mtface;
+ MVert *mverts = mesh->mvert;
+
+ ParticleCacheKey **cache = m_psys->childcache;
+ ParticleCacheKey *path;
+
+ ChildParticle *pc = m_psys->child;
+
+ for (int p = 0; p < m_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;
+ continue;
+ }
+
+ MFace *face = &mesh->mface[num];
+ MTFace *tface = mtface + num;
+
+ float r_uv[2], tmpnor[3], mapfw[4], vec[3];
+
+ psys_interpolate_uvs(tface, face->v4, pc->fuv, r_uv);
+ uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1]));
+
+ psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, tmpnor, NULL, NULL, NULL);
+
+ /* Convert Z-up to Y-up. */
+ norm_values.push_back(Imath::V3f(tmpnor[0], tmpnor[2], -tmpnor[1]));
+ }
+ else {
+ if (uv_values.size()) {
+ uv_values.push_back(uv_values[pc->parent]);
+ }
+ if (norm_values.size()) {
+ norm_values.push_back(norm_values[pc->parent]);
+ }
+ }
+
+ int steps = path->segments + 1;
+ hvertices.push_back(steps);
+
+ for (int k = 0; k < steps; k++) {
+ float vert[3];
+ copy_v3_v3(vert, path->co);
+ mul_m4_v3(inv_mat, vert);
+
+ /* Convert Z-up to Y-up. */
+ verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1]));
+
+ path++;
+ }
+ }
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.h b/source/blender/io/alembic/exporter/abc_writer_hair.h
new file mode 100644
index 00000000000..67d1b7b3d23
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_hair.h
@@ -0,0 +1,62 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_HAIR_H__
+#define __ABC_WRITER_HAIR_H__
+
+#include "abc_writer_object.h"
+
+struct ParticleSettings;
+struct ParticleSystem;
+
+class AbcHairWriter : public AbcObjectWriter {
+ ParticleSystem *m_psys;
+
+ Alembic::AbcGeom::OCurvesSchema m_schema;
+ Alembic::AbcGeom::OCurvesSchema::Sample m_sample;
+
+ bool m_uv_warning_shown;
+
+ public:
+ AbcHairWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ ParticleSystem *psys);
+
+ private:
+ virtual void do_write();
+
+ void write_hair_sample(struct Mesh *mesh,
+ ParticleSettings *part,
+ 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,
+ std::vector<Imath::V3f> &verts,
+ std::vector<Imath::V3f> &norm_values,
+ std::vector<Imath::V2f> &uv_values,
+ std::vector<int32_t> &hvertices);
+};
+
+#endif /* __ABC_WRITER_HAIR_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_mball.cc b/source/blender/io/alembic/exporter/abc_writer_mball.cc
new file mode 100644
index 00000000000..3593acf18b0
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_mball.cc
@@ -0,0 +1,95 @@
+/*
+ * 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_mball.h"
+#include "abc_writer_mesh.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meta_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_displist.h"
+#include "BKE_lib_id.h"
+#include "BKE_mball.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+
+#include "BLI_utildefines.h"
+
+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()
+{
+}
+
+bool AbcMBallWriter::isAnimated() const
+{
+ return true;
+}
+
+Mesh *AbcMBallWriter::getEvaluatedMesh(Scene * /*scene_eval*/, Object *ob_eval, bool &r_needsfree)
+{
+ Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ if (mesh_eval != NULL) {
+ /* 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;
+}
+
+void AbcMBallWriter::freeEvaluatedMesh(struct Mesh *mesh)
+{
+ BKE_id_free(m_bmain, mesh);
+}
+
+bool AbcMBallWriter::isBasisBall(Scene *scene, Object *ob)
+{
+ Object *basis_ob = BKE_mball_basis_find(scene, ob);
+ return ob == basis_ob;
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_mball.h b/source/blender/io/alembic/exporter/abc_writer_mball.h
new file mode 100644
index 00000000000..e3ac1e69cae
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_mball.h
@@ -0,0 +1,56 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_MBALL_H__
+#define __ABC_WRITER_MBALL_H__
+
+#include "abc_writer_mesh.h"
+#include "abc_writer_object.h"
+
+struct Main;
+struct Object;
+
+/* 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;
+
+ public:
+ explicit AbcMBallWriter(Main *bmain,
+ Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ ~AbcMBallWriter();
+
+ static bool isBasisBall(Scene *scene, Object *ob);
+
+ protected:
+ Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override;
+ void freeEvaluatedMesh(struct Mesh *mesh) override;
+
+ private:
+ bool isAnimated() const override;
+};
+
+#endif /* __ABC_WRITER_MBALL_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc
new file mode 100644
index 00000000000..b17e123b426
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc
@@ -0,0 +1,591 @@
+/*
+ * 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_mesh.h"
+#include "abc_writer_transform.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 "BKE_anim_data.h"
+#include "BKE_key.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_modifier.h"
+
+#include "bmesh.h"
+#include "bmesh_tools.h"
+
+#include "DEG_depsgraph_query.h"
+
+using Alembic::Abc::FloatArraySample;
+using Alembic::Abc::Int32ArraySample;
+using Alembic::Abc::V2fArraySample;
+using Alembic::Abc::V3fArraySample;
+
+using Alembic::AbcGeom::kFacevaryingScope;
+using Alembic::AbcGeom::OBoolProperty;
+using Alembic::AbcGeom::OCompoundProperty;
+using Alembic::AbcGeom::OFaceSet;
+using Alembic::AbcGeom::OFaceSetSchema;
+using Alembic::AbcGeom::ON3fGeomParam;
+using Alembic::AbcGeom::OPolyMesh;
+using Alembic::AbcGeom::OPolyMeshSchema;
+using Alembic::AbcGeom::OSubD;
+using Alembic::AbcGeom::OSubDSchema;
+using Alembic::AbcGeom::OV2fGeomParam;
+using Alembic::AbcGeom::UInt32ArraySample;
+
+/* 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();
+ points.resize(mesh->totvert);
+
+ MVert *verts = mesh->mvert;
+
+ for (int i = 0, e = mesh->totvert; i < e; i++) {
+ copy_yup_from_zup(points[i].getValue(), verts[i].co);
+ }
+}
+
+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)
+{
+ const int num_poly = mesh->totpoly;
+ const int num_loops = mesh->totloop;
+ MLoop *mloop = mesh->mloop;
+ MPoly *mpoly = mesh->mpoly;
+ r_has_flat_shaded_poly = false;
+
+ poly_verts.clear();
+ loop_counts.clear();
+ poly_verts.reserve(num_loops);
+ loop_counts.reserve(num_poly);
+
+ /* NOTE: data needs to be written in the reverse order. */
+ for (int i = 0; i < num_poly; i++) {
+ MPoly &poly = mpoly[i];
+ loop_counts.push_back(poly.totloop);
+
+ r_has_flat_shaded_poly |= (poly.flag & ME_SMOOTH) == 0;
+
+ MLoop *loop = mloop + poly.loopstart + (poly.totloop - 1);
+
+ for (int j = 0; j < poly.totloop; j++, loop--) {
+ poly_verts.push_back(loop->v);
+ }
+ }
+}
+
+static void get_creases(struct Mesh *mesh,
+ std::vector<int32_t> &indices,
+ std::vector<int32_t> &lengths,
+ std::vector<float> &sharpnesses)
+{
+ const float factor = 1.0f / 255.0f;
+
+ indices.clear();
+ lengths.clear();
+ sharpnesses.clear();
+
+ MEdge *edge = mesh->medge;
+
+ for (int i = 0, e = mesh->totedge; i < e; i++) {
+ const float sharpness = static_cast<float>(edge[i].crease) * factor;
+
+ if (sharpness != 0.0f) {
+ indices.push_back(edge[i].v1);
+ indices.push_back(edge[i].v2);
+ sharpnesses.push_back(sharpness);
+ }
+ }
+
+ lengths.resize(sharpnesses.size(), 2);
+}
+
+static void get_loop_normals(struct Mesh *mesh,
+ std::vector<Imath::V3f> &normals,
+ bool has_flat_shaded_poly)
+{
+ normals.clear();
+
+ /* If all polygons are smooth shaded, and there are no custom normals, we don't need to export
+ * normals at all. This is also done by other software, see T71246. */
+ if (!has_flat_shaded_poly && !CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL) &&
+ (mesh->flag & ME_AUTOSMOOTH) == 0) {
+ return;
+ }
+
+ 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");
+
+ normals.resize(mesh->totloop);
+
+ /* NOTE: data needs to be written in the reverse order. */
+ int abc_index = 0;
+ MPoly *mp = mesh->mpoly;
+ for (int i = 0, e = mesh->totpoly; i < e; i++, mp++) {
+ for (int j = mp->totloop - 1; j >= 0; j--, abc_index++) {
+ int blender_index = mp->loopstart + j;
+ copy_yup_from_zup(normals[abc_index].getValue(), lnors[blender_index]);
+ }
+ }
+}
+
+/* *************** 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()
+{
+}
+
+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);
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.h b/source/blender/io/alembic/exporter/abc_writer_mesh.h
new file mode 100644
index 00000000000..264696333a3
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_mesh.h
@@ -0,0 +1,91 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_MESH_H__
+#define __ABC_WRITER_MESH_H__
+
+#include "abc_writer_object.h"
+#include "intern/abc_customdata.h"
+
+struct Mesh;
+struct ModifierData;
+
+/* 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;
+
+ Alembic::AbcGeom::OSubDSchema m_subdiv_schema;
+ Alembic::AbcGeom::OSubDSchema::Sample m_subdiv_sample;
+
+ Alembic::Abc::OArrayProperty m_mat_indices;
+
+ bool m_is_animated;
+ ModifierData *m_subsurf_mod;
+
+ 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);
+
+ ~AbcGenericMeshWriter();
+ void setIsAnimated(bool is_animated);
+
+ 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);
+
+ Mesh *getFinalMesh(bool &r_needsfree);
+
+ void writeMesh(struct Mesh *mesh);
+ void writeSubD(struct Mesh *mesh);
+
+ void writeArbGeoParams(struct Mesh *mesh);
+ void getGeoGroups(struct Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geoGroups);
+
+ /* fluid surfaces support */
+ void getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels);
+
+ template<typename Schema> void writeFaceSets(struct Mesh *mesh, Schema &schema);
+};
+
+class AbcMeshWriter : public AbcGenericMeshWriter {
+ public:
+ AbcMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ ~AbcMeshWriter();
+
+ protected:
+ virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override;
+};
+
+#endif /* __ABC_WRITER_MESH_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc
new file mode 100644
index 00000000000..c856f894f0e
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc
@@ -0,0 +1,170 @@
+/*
+ * 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_nurbs.h"
+#include "abc_writer_transform.h"
+#include "intern/abc_axis_conversion.h"
+
+#include "DNA_curve_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_listbase.h"
+
+#include "BKE_curve.h"
+
+using Alembic::AbcGeom::FloatArraySample;
+using Alembic::AbcGeom::OBoolProperty;
+using Alembic::AbcGeom::OCompoundProperty;
+using Alembic::AbcGeom::ONuPatch;
+using Alembic::AbcGeom::ONuPatchSchema;
+
+AbcNurbsWriter::AbcNurbsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcObjectWriter(ob, time_sampling, settings, parent)
+{
+ m_is_animated = isAnimated();
+
+ /* if the object is static, use the default static time sampling */
+ if (!m_is_animated) {
+ m_time_sampling = 0;
+ }
+
+ Curve *curve = static_cast<Curve *>(m_object->data);
+ size_t numNurbs = BLI_listbase_count(&curve->nurb);
+
+ for (size_t i = 0; i < numNurbs; i++) {
+ std::stringstream str;
+ str << m_name << '_' << i;
+
+ while (parent->alembicXform().getChildHeader(str.str())) {
+ str << "_";
+ }
+
+ ONuPatch nurbs(parent->alembicXform(), str.str().c_str(), m_time_sampling);
+ m_nurbs_schema.push_back(nurbs.getSchema());
+ }
+}
+
+bool AbcNurbsWriter::isAnimated() const
+{
+ /* check if object has shape keys */
+ Curve *cu = static_cast<Curve *>(m_object->data);
+ return (cu->key != NULL);
+}
+
+static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_knots)
+{
+ if (num_knots <= 1) {
+ return;
+ }
+
+ /* Add an extra knot at the beginning and end of the array since most apps
+ * require/expect them. */
+ knots.reserve(num_knots + 2);
+
+ knots.push_back(0.0f);
+
+ for (int i = 0; i < num_knots; i++) {
+ knots.push_back(nu_knots[i]);
+ }
+
+ knots[0] = 2.0f * knots[1] - knots[2];
+ knots.push_back(2.0f * knots[num_knots] - knots[num_knots - 1]);
+}
+
+void AbcNurbsWriter::do_write()
+{
+ /* 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);
+ ListBase *nulb;
+
+ if (m_object->runtime.curve_cache->deformed_nurbs.first != NULL) {
+ nulb = &m_object->runtime.curve_cache->deformed_nurbs;
+ }
+ else {
+ nulb = BKE_curve_nurbs_get(curve);
+ }
+
+ size_t count = 0;
+ for (Nurb *nu = static_cast<Nurb *>(nulb->first); nu; nu = nu->next, count++) {
+ std::vector<float> knotsU;
+ get_knots(knotsU, KNOTSU(nu), nu->knotsu);
+
+ std::vector<float> knotsV;
+ get_knots(knotsV, KNOTSV(nu), nu->knotsv);
+
+ const int size = nu->pntsu * nu->pntsv;
+ std::vector<Imath::V3f> positions(size);
+ std::vector<float> weights(size);
+
+ const BPoint *bp = nu->bp;
+
+ for (int i = 0; i < size; i++, bp++) {
+ copy_yup_from_zup(positions[i].getValue(), bp->vec);
+ weights[i] = bp->vec[3];
+ }
+
+ ONuPatchSchema::Sample sample;
+ sample.setUOrder(nu->orderu + 1);
+ sample.setVOrder(nu->orderv + 1);
+ sample.setPositions(positions);
+ sample.setPositionWeights(weights);
+ sample.setUKnot(FloatArraySample(knotsU));
+ sample.setVKnot(FloatArraySample(knotsV));
+ sample.setNu(nu->pntsu);
+ sample.setNv(nu->pntsv);
+
+ /* 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();
+
+ if ((nu->flagu & CU_NURB_ENDPOINT) != 0) {
+ OBoolProperty prop(user_props, "endpoint_u");
+ prop.set(true);
+ }
+
+ if ((nu->flagv & CU_NURB_ENDPOINT) != 0) {
+ OBoolProperty prop(user_props, "endpoint_v");
+ prop.set(true);
+ }
+
+ if ((nu->flagu & CU_NURB_CYCLIC) != 0) {
+ OBoolProperty prop(user_props, "cyclic_u");
+ prop.set(true);
+ }
+
+ if ((nu->flagv & CU_NURB_CYCLIC) != 0) {
+ OBoolProperty prop(user_props, "cyclic_v");
+ prop.set(true);
+ }
+
+ m_nurbs_schema[count].set(sample);
+ }
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.h b/source/blender/io/alembic/exporter/abc_writer_nurbs.h
new file mode 100644
index 00000000000..c6a3c399b66
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.h
@@ -0,0 +1,42 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_NURBS_H__
+#define __ABC_WRITER_NURBS_H__
+
+#include "abc_writer_object.h"
+
+class AbcNurbsWriter : public AbcObjectWriter {
+ std::vector<Alembic::AbcGeom::ONuPatchSchema> m_nurbs_schema;
+ bool m_is_animated;
+
+ public:
+ AbcNurbsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ private:
+ virtual void do_write();
+
+ bool isAnimated() const;
+};
+
+#endif /* __ABC_WRITER_NURBS_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_object.cc b/source/blender/io/alembic/exporter/abc_writer_object.cc
new file mode 100644
index 00000000000..3d280d9f0a4
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_object.cc
@@ -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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_object.h"
+
+#include "DNA_object_types.h"
+
+#include "BKE_object.h"
+
+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;
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_object.h b/source/blender/io/alembic/exporter/abc_writer_object.h
new file mode 100644
index 00000000000..830c4aee903
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_object.h
@@ -0,0 +1,69 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_OBJECT_H__
+#define __ABC_WRITER_OBJECT_H__
+
+#include <Alembic/Abc/All.h>
+#include <Alembic/AbcGeom/All.h>
+
+#include "abc_exporter.h"
+
+#include "DNA_ID.h"
+
+class AbcTransformWriter;
+
+struct Main;
+struct Object;
+
+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;
+};
+
+#endif /* __ABC_WRITER_OBJECT_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_points.cc b/source/blender/io/alembic/exporter/abc_writer_points.cc
new file mode 100644
index 00000000000..27f51586f47
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_points.cc
@@ -0,0 +1,122 @@
+/*
+ * 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_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"
+
+#include "BKE_lattice.h"
+#include "BKE_particle.h"
+
+#include "BLI_math.h"
+
+#include "DEG_depsgraph_query.h"
+
+using Alembic::AbcGeom::kVertexScope;
+using Alembic::AbcGeom::OPoints;
+using Alembic::AbcGeom::OPointsSchema;
+
+/* ************************************************************************** */
+
+AbcPointsWriter::AbcPointsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ ParticleSystem *psys)
+ : AbcObjectWriter(ob, time_sampling, settings, parent)
+{
+ m_psys = psys;
+
+ std::string psys_name = get_valid_abc_name(psys->name);
+ OPoints points(parent->alembicXform(), psys_name, m_time_sampling);
+ m_schema = points.getSchema();
+}
+
+void AbcPointsWriter::do_write()
+{
+ if (!m_psys) {
+ return;
+ }
+
+ std::vector<Imath::V3f> points;
+ std::vector<Imath::V3f> velocities;
+ std::vector<float> widths;
+ std::vector<uint64_t> ids;
+
+ ParticleKey state;
+
+ ParticleSimulationData sim;
+ sim.depsgraph = m_settings.depsgraph;
+ sim.scene = m_settings.scene;
+ sim.ob = m_object;
+ sim.psys = m_psys;
+
+ m_psys->lattice_deform_data = psys_create_lattice_deform_data(&sim);
+
+ uint64_t index = 0;
+ for (int p = 0; p < m_psys->totpart; p++) {
+ float pos[3], vel[3];
+
+ if (m_psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) {
+ continue;
+ }
+
+ state.time = DEG_get_ctime(m_settings.depsgraph);
+
+ if (psys_get_particle_state(&sim, p, &state, 0) == 0) {
+ continue;
+ }
+
+ /* location */
+ mul_v3_m4v3(pos, m_object->imat, state.co);
+
+ /* velocity */
+ sub_v3_v3v3(vel, state.co, m_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);
+ 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;
+ }
+
+ Alembic::Abc::P3fArraySample psample(points);
+ Alembic::Abc::UInt64ArraySample idsample(ids);
+ Alembic::Abc::V3fArraySample vsample(velocities);
+ 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);
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_points.h b/source/blender/io/alembic/exporter/abc_writer_points.h
new file mode 100644
index 00000000000..e5c1b81f266
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_points.h
@@ -0,0 +1,49 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_POINTS_H__
+#define __ABC_WRITER_POINTS_H__
+
+#include "abc_writer_object.h"
+#include "intern/abc_customdata.h"
+
+struct ParticleSystem;
+
+/* ************************************************************************** */
+
+class AbcPointsWriter : public AbcObjectWriter {
+ Alembic::AbcGeom::OPointsSchema m_schema;
+ Alembic::AbcGeom::OPointsSchema::Sample m_sample;
+ ParticleSystem *m_psys;
+
+ public:
+ AbcPointsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ ParticleSystem *psys);
+
+ void do_write();
+};
+
+#endif /* __ABC_WRITER_POINTS_H__ */
diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.cc b/source/blender/io/alembic/exporter/abc_writer_transform.cc
new file mode 100644
index 00000000000..46130cb8675
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_transform.cc
@@ -0,0 +1,124 @@
+/*
+ * 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_transform.h"
+#include "intern/abc_axis_conversion.h"
+
+#include <OpenEXR/ImathBoxAlgo.h>
+
+#include "DNA_object_types.h"
+
+#include "BLI_math.h"
+
+#include "DEG_depsgraph_query.h"
+
+using Alembic::AbcGeom::OObject;
+using Alembic::AbcGeom::OXform;
+
+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)
+{
+ 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;
+}
+
+void AbcTransformWriter::do_write()
+{
+ Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object);
+
+ if (m_first_frame) {
+ m_visibility = Alembic::AbcGeom::CreateVisibilityProperty(
+ m_xform, m_xform.getSchema().getTimeSampling());
+ }
+
+ 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);
+
+ /* 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) {
+ 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);
+ }
+
+ /* If the object is a camera, apply an extra rotation to Maya camera orientation. */
+ if (ob_eval->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);
+ }
+
+ 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 */
+ }
+
+ 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);
+}
+
+Imath::Box3d AbcTransformWriter::bounds()
+{
+ Imath::Box3d bounds;
+
+ for (int i = 0; i < m_children.size(); i++) {
+ Imath::Box3d box(m_children[i]->bounds());
+ bounds.extendBy(box);
+ }
+
+ return Imath::transform(bounds, m_matrix);
+}
+
+bool AbcTransformWriter::hasAnimation(Object * /*ob*/) const
+{
+ return true;
+}
diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.h b/source/blender/io/alembic/exporter/abc_writer_transform.h
new file mode 100644
index 00000000000..4397b220761
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_writer_transform.h
@@ -0,0 +1,60 @@
+/*
+ * 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
+ */
+
+#ifndef __ABC_WRITER_TRANSFORM_H__
+#define __ABC_WRITER_TRANSFORM_H__
+
+#include "abc_writer_object.h"
+
+#include <Alembic/AbcGeom/All.h>
+
+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();
+
+ private:
+ virtual void do_write();
+
+ bool hasAnimation(Object *ob) const;
+};
+
+#endif /* __ABC_WRITER_TRANSFORM_H__ */