diff options
37 files changed, 2019 insertions, 2146 deletions
diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt index c0c91099ad2..da36272b850 100644 --- a/source/blender/io/alembic/CMakeLists.txt +++ b/source/blender/io/alembic/CMakeLists.txt @@ -30,6 +30,7 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager + ../../../../intern/clog ../../../../intern/guardedalloc ../../../../intern/utfconv ) @@ -54,16 +55,17 @@ set(SRC intern/abc_util.cc intern/alembic_capi.cc + exporter/abc_archive.cc exporter/abc_export_capi.cc - exporter/abc_exporter.cc - exporter/abc_writer_archive.cc + exporter/abc_hierarchy_iterator.cc + exporter/abc_subdiv_disabler.cc + exporter/abc_writer_abstract.cc exporter/abc_writer_camera.cc exporter/abc_writer_curves.cc exporter/abc_writer_hair.cc - exporter/abc_writer_mball.cc exporter/abc_writer_mesh.cc + exporter/abc_writer_mball.cc exporter/abc_writer_nurbs.cc - exporter/abc_writer_object.cc exporter/abc_writer_points.cc exporter/abc_writer_transform.cc @@ -80,15 +82,16 @@ set(SRC intern/abc_reader_transform.h intern/abc_util.h - exporter/abc_exporter.h - exporter/abc_writer_archive.h + exporter/abc_archive.h + exporter/abc_hierarchy_iterator.h + exporter/abc_subdiv_disabler.h + exporter/abc_writer_abstract.h exporter/abc_writer_camera.h exporter/abc_writer_curves.h exporter/abc_writer_hair.h - exporter/abc_writer_mball.h exporter/abc_writer_mesh.h + exporter/abc_writer_mball.h exporter/abc_writer_nurbs.h - exporter/abc_writer_object.h exporter/abc_writer_points.h exporter/abc_writer_transform.h ) diff --git a/source/blender/io/alembic/exporter/abc_archive.cc b/source/blender/io/alembic/exporter/abc_archive.cc new file mode 100644 index 00000000000..dbf046e6dfe --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_archive.cc @@ -0,0 +1,255 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +#include "abc_archive.h" + +#include "BKE_blender_version.h" +#include "BKE_main.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "DNA_scene_types.h" + +#include <Alembic/AbcCoreOgawa/All.h> +#include <Alembic/AbcGeom/All.h> + +namespace blender { +namespace io { +namespace alembic { + +using Alembic::Abc::ErrorHandler; +using Alembic::Abc::kWrapExisting; +using Alembic::Abc::MetaData; +using Alembic::Abc::OArchive; +using Alembic::Abc::TimeSampling; +using Alembic::Abc::TimeSamplingPtr; +using Alembic::Abc::TimeSamplingType; + +static MetaData create_abc_metadata(const Main *bmain, double scene_fps) +{ + MetaData abc_metadata; + + std::string abc_user_description(bmain->name); + if (abc_user_description.empty()) { + abc_user_description = "unknown"; + } + + abc_metadata.set(Alembic::Abc::kApplicationNameKey, "Blender"); + abc_metadata.set(Alembic::Abc::kUserDescriptionKey, abc_user_description); + abc_metadata.set("blender_version", std::string("v") + BKE_blender_version_string()); + abc_metadata.set("FramesPerTimeUnit", std::to_string(scene_fps)); + + time_t raw_time; + time(&raw_time); + char buffer[128]; + +#if defined _WIN32 || defined _WIN64 + ctime_s(buffer, 128, &raw_time); +#else + ctime_r(&raw_time, buffer); +#endif + + const std::size_t buffer_len = strlen(buffer); + if (buffer_len > 0 && buffer[buffer_len - 1] == '\n') { + buffer[buffer_len - 1] = '\0'; + } + + abc_metadata.set(Alembic::Abc::kDateWrittenKey, buffer); + return abc_metadata; +} + +static OArchive *create_archive(std::ofstream *abc_ostream, + const std::string &filename, + MetaData &abc_metadata) +{ + /* Use stream to support unicode character paths on Windows. */ +#ifdef WIN32 + UTF16_ENCODE(filename); + std::wstring wstr(filename_16); + abc_ostream->open(wstr.c_str(), std::ios::out | std::ios::binary); + UTF16_UN_ENCODE(filename); +#else + abc_ostream->open(filename, std::ios::out | std::ios::binary); +#endif + + ErrorHandler::Policy policy = ErrorHandler::kThrowPolicy; + + Alembic::AbcCoreOgawa::WriteArchive archive_writer; + return new OArchive(archive_writer(abc_ostream, abc_metadata), kWrapExisting, policy); +} + +/* Construct list of shutter samples. + * + * These are taken from the interval [shutter open, shutter close), + * uniformly sampled with 'nr_of_samples' samples. + * + * TODO(Sybren): test that the above interval is indeed half-open. + * + * If 'time_relative' is true, samples are returned as time (in seconds) from params.frame_start. + * If 'time_relative' is false, samples are returned as fractional frames from 0. + * */ +static void get_shutter_samples(double scene_fps, + const AlembicExportParams ¶ms, + int nr_of_samples, + bool time_relative, + std::vector<double> &r_samples) +{ + int frame_offset = time_relative ? params.frame_start : 0; + double time_factor = time_relative ? scene_fps : 1.0; + double shutter_open = params.shutter_open; + double shutter_close = params.shutter_close; + double time_inc = (shutter_close - shutter_open) / nr_of_samples; + + /* sample between shutter open & close */ + for (int sample = 0; sample < nr_of_samples; sample++) { + double sample_time = shutter_open + time_inc * sample; + double time = (frame_offset + sample_time) / time_factor; + + r_samples.push_back(time); + } +} + +static TimeSamplingPtr create_time_sampling(double scene_fps, + const AlembicExportParams ¶ms, + int nr_of_samples) +{ + std::vector<double> samples; + + if (params.frame_start == params.frame_end) { + return TimeSamplingPtr(new TimeSampling()); + } + + get_shutter_samples(scene_fps, params, nr_of_samples, true, samples); + + TimeSamplingType ts(static_cast<uint32_t>(samples.size()), 1.0 / scene_fps); + return TimeSamplingPtr(new TimeSampling(ts, samples)); +} + +static void get_frames(double scene_fps, + const AlembicExportParams ¶ms, + unsigned int nr_of_samples, + std::set<double> &r_frames) +{ + /* Get one set of shutter samples, then add those around each frame to export. */ + std::vector<double> shutter_samples; + get_shutter_samples(scene_fps, params, nr_of_samples, false, shutter_samples); + + for (double frame = params.frame_start; frame <= params.frame_end; frame += 1.0) { + for (size_t j = 0; j < nr_of_samples; j++) { + r_frames.insert(frame + shutter_samples[j]); + } + } +} + +/* ****************************************************************** */ + +ABCArchive::ABCArchive(const Main *bmain, + const Scene *scene, + AlembicExportParams params, + std::string filename) + : archive(nullptr) +{ + double scene_fps = FPS; + MetaData abc_metadata = create_abc_metadata(bmain, scene_fps); + + // Create the Archive. + archive = create_archive(&abc_ostream_, filename, abc_metadata); + + // Create time samples for transforms and shapes. + TimeSamplingPtr ts_xform; + TimeSamplingPtr ts_shapes; + + ts_xform = create_time_sampling(scene_fps, params, params.frame_samples_xform); + time_sampling_index_transforms_ = archive->addTimeSampling(*ts_xform); + + const bool export_animation = params.frame_start != params.frame_end; + if (!export_animation || params.frame_samples_shape == params.frame_samples_xform) { + ts_shapes = ts_xform; + time_sampling_index_shapes_ = time_sampling_index_transforms_; + } + else { + ts_shapes = create_time_sampling(scene_fps, params, params.frame_samples_shape); + time_sampling_index_shapes_ = archive->addTimeSampling(*ts_shapes); + } + + // Construct the frames to export. + get_frames(scene_fps, params, params.frame_samples_xform, xform_frames_); + get_frames(scene_fps, params, params.frame_samples_shape, shape_frames_); + + // Merge all frames to get the final set of frames to export. + export_frames_.insert(xform_frames_.begin(), xform_frames_.end()); + export_frames_.insert(shape_frames_.begin(), shape_frames_.end()); + + abc_archive_bbox_ = Alembic::AbcGeom::CreateOArchiveBounds(*archive, + time_sampling_index_transforms_); +} + +ABCArchive::~ABCArchive() +{ + delete archive; +} + +uint32_t ABCArchive::time_sampling_index_transforms() const +{ + return time_sampling_index_transforms_; +} + +uint32_t ABCArchive::time_sampling_index_shapes() const +{ + return time_sampling_index_shapes_; +} + +ABCArchive::Frames::const_iterator ABCArchive::frames_begin() const +{ + return export_frames_.begin(); +} +ABCArchive::Frames::const_iterator ABCArchive::frames_end() const +{ + return export_frames_.end(); +} +size_t ABCArchive::total_frame_count() const +{ + return export_frames_.size(); +} + +bool ABCArchive::is_xform_frame(double frame) const +{ + return xform_frames_.find(frame) != xform_frames_.end(); +} +bool ABCArchive::is_shape_frame(double frame) const +{ + return shape_frames_.find(frame) != shape_frames_.end(); +} +ExportSubset ABCArchive::export_subset_for_frame(double frame) const +{ + ExportSubset subset; + subset.transforms = is_xform_frame(frame); + subset.shapes = is_shape_frame(frame); + return subset; +} + +void ABCArchive::update_bounding_box(const Imath::Box3d &bounds) +{ + abc_archive_bbox_.set(bounds); +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_archive.h b/source/blender/io/alembic/exporter/abc_archive.h new file mode 100644 index 00000000000..43d0acf2520 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_archive.h @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup Alembic + */ + +#pragma once + +#include "ABC_alembic.h" +#include "IO_abstract_hierarchy_iterator.h" + +#include <Alembic/Abc/OArchive.h> +#include <Alembic/Abc/OTypedScalarProperty.h> + +#include <fstream> +#include <set> +#include <string> + +struct Main; +struct Scene; + +namespace blender { +namespace io { +namespace alembic { + +/* Container for an Alembic archive and time sampling info. + * + * Constructor arguments are used to create the correct output stream and to set the archive's + * metadata. */ +class ABCArchive { + public: + typedef std::set<double> Frames; + + Alembic::Abc::OArchive *archive; + + ABCArchive(const Main *bmain, + const Scene *scene, + AlembicExportParams params, + std::string filename); + ~ABCArchive(); + + uint32_t time_sampling_index_transforms() const; + uint32_t time_sampling_index_shapes() const; + + Frames::const_iterator frames_begin() const; + Frames::const_iterator frames_end() const; + size_t total_frame_count() const; + + bool is_xform_frame(double frame) const; + bool is_shape_frame(double frame) const; + + ExportSubset export_subset_for_frame(double frame) const; + + void update_bounding_box(const Imath::Box3d &bounds); + + private: + std::ofstream abc_ostream_; + uint32_t time_sampling_index_transforms_; + uint32_t time_sampling_index_shapes_; + + Frames xform_frames_; + Frames shape_frames_; + Frames export_frames_; + + Alembic::Abc::OBox3dProperty abc_archive_bbox_; +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc index c0d04b50acd..fbc5b2d5c02 100644 --- a/source/blender/io/alembic/exporter/abc_export_capi.cc +++ b/source/blender/io/alembic/exporter/abc_export_capi.cc @@ -18,18 +18,15 @@ */ #include "ABC_alembic.h" -#include "abc_writer_camera.h" -#include "abc_writer_curves.h" -#include "abc_writer_hair.h" -#include "abc_writer_mesh.h" -#include "abc_writer_nurbs.h" -#include "abc_writer_points.h" -#include "abc_writer_transform.h" +#include "abc_archive.h" +#include "abc_hierarchy_iterator.h" +#include "abc_subdiv_disabler.h" #include "MEM_guardedalloc.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" #include "DNA_modifier_types.h" #include "DNA_scene_types.h" @@ -47,179 +44,163 @@ #include "WM_api.h" #include "WM_types.h" -using namespace blender::io::alembic; +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + +#include <algorithm> struct ExportJobData { - ViewLayer *view_layer; Main *bmain; + Depsgraph *depsgraph; wmWindowManager *wm; - char filename[1024]; - ExportSettings settings; - - short *stop; - short *do_update; - float *progress; + char filename[FILE_MAX]; + AlembicExportParams params; bool was_canceled; bool export_ok; }; +namespace blender { +namespace io { +namespace alembic { + +// Construct the depsgraph for exporting. +static void build_depsgraph(Depsgraph *depsgraph, Main *bmain) +{ + Scene *scene = DEG_get_input_scene(depsgraph); + ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); + DEG_graph_build_from_view_layer(depsgraph, bmain, scene, view_layer); +} + static void export_startjob(void *customdata, short *stop, short *do_update, float *progress) { ExportJobData *data = static_cast<ExportJobData *>(customdata); + data->was_canceled = false; - data->stop = stop; - data->do_update = do_update; - data->progress = progress; - - /* XXX annoying hack: needed to prevent data corruption when changing - * scene frame in separate threads - */ G.is_rendering = true; WM_set_locked_interface(data->wm, true); G.is_break = false; - DEG_graph_build_from_view_layer( - data->settings.depsgraph, data->bmain, data->settings.scene, data->view_layer); - BKE_scene_graph_update_tagged(data->settings.depsgraph, data->bmain); + *progress = 0.0f; + *do_update = true; - try { - AbcExporter exporter(data->bmain, data->filename, data->settings); + build_depsgraph(data->depsgraph, data->bmain); + SubdivModifierDisabler subdiv_disabler(data->depsgraph); + if (!data->params.apply_subdiv) { + subdiv_disabler.disable_modifiers(); + } + BKE_scene_graph_update_tagged(data->depsgraph, data->bmain); - Scene *scene = data->settings.scene; /* for the CFRA macro */ - const int orig_frame = CFRA; + // For restoring the current frame after exporting animation is done. + Scene *scene = DEG_get_input_scene(data->depsgraph); + const int orig_frame = CFRA; + const bool export_animation = (data->params.frame_start != data->params.frame_end); - data->was_canceled = false; - exporter(do_update, progress, &data->was_canceled); + // Create the Alembic archive. + ABCArchive abc_archive(data->bmain, scene, data->params, std::string(data->filename)); - if (CFRA != orig_frame) { - CFRA = orig_frame; + ABCHierarchyIterator iter(data->depsgraph, &abc_archive, data->params); - BKE_scene_graph_update_for_newframe(data->settings.depsgraph, data->bmain); - } + if (export_animation) { + CLOG_INFO(&LOG, 2, "Exporting animation"); + + // Writing the animated frames is not 100% of the work, but it's our best guess. + const float progress_per_frame = 1.0f / std::max(size_t(1), abc_archive.total_frame_count()); + ABCArchive::Frames::const_iterator frame_it = abc_archive.frames_begin(); + const ABCArchive::Frames::const_iterator frames_end = abc_archive.frames_end(); + + for (; frame_it != frames_end; frame_it++) { + double frame = *frame_it; + + if (G.is_break || (stop != nullptr && *stop)) { + break; + } + + // Update the scene for the next frame to render. + scene->r.cfra = static_cast<int>(frame); + scene->r.subframe = frame - scene->r.cfra; + BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain); - data->export_ok = !data->was_canceled; + CLOG_INFO(&LOG, 2, "Exporting frame %.2f", frame); + ExportSubset export_subset = abc_archive.export_subset_for_frame(frame); + iter.set_export_subset(export_subset); + iter.iterate_and_write(); + + *progress += progress_per_frame; + *do_update = true; + } } - catch (const std::exception &e) { - ABC_LOG(data->settings.logger) << "Abc Export error: " << e.what() << '\n'; + else { + // If we're not animating, a single iteration over all objects is enough. + iter.iterate_and_write(); } - catch (...) { - ABC_LOG(data->settings.logger) << "Abc Export: unknown error...\n"; + + iter.release_writers(); + + // Finish up by going back to the keyframe that was current before we started. + if (CFRA != orig_frame) { + CFRA = orig_frame; + BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain); } + + data->export_ok = !data->was_canceled; + + *progress = 1.0f; + *do_update = true; } static void export_endjob(void *customdata) { ExportJobData *data = static_cast<ExportJobData *>(customdata); - DEG_graph_free(data->settings.depsgraph); + DEG_graph_free(data->depsgraph); if (data->was_canceled && BLI_exists(data->filename)) { BLI_delete(data->filename, false, false); } - std::string log = data->settings.logger.str(); - if (!log.empty()) { - std::cerr << log; - WM_report(RPT_ERROR, "Errors occurred during the export, look in the console to know more..."); - } - G.is_rendering = false; WM_set_locked_interface(data->wm, false); } -bool ABC_export(struct Scene *scene, - struct bContext *C, +} // namespace alembic +} // namespace io +} // namespace blender + +bool ABC_export(Scene *scene, + bContext *C, const char *filepath, - const struct AlembicExportParams *params, + const AlembicExportParams *params, bool as_background_job) { + ViewLayer *view_layer = CTX_data_view_layer(C); + ExportJobData *job = static_cast<ExportJobData *>( MEM_mallocN(sizeof(ExportJobData), "ExportJobData")); - job->view_layer = CTX_data_view_layer(C); job->bmain = CTX_data_main(C); job->wm = CTX_wm_manager(C); job->export_ok = false; - BLI_strncpy(job->filename, filepath, 1024); - - /* 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); - } + BLI_strncpy(job->filename, filepath, sizeof(job->filename)); + + job->depsgraph = DEG_graph_new( + job->bmain, scene, view_layer, DAG_EVAL_RENDER /* TODO(Sybren): params->evaluation_mode */); + job->params = *params; bool export_ok = false; if (as_background_job) { - wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), - CTX_wm_window(C), - job->settings.scene, - "Alembic Export", - WM_JOB_PROGRESS, - WM_JOB_TYPE_ALEMBIC); + wmJob *wm_job = WM_jobs_get( + job->wm, CTX_wm_window(C), scene, "Alembic Export", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC); /* setup job */ WM_jobs_customdata_set(wm_job, job, MEM_freeN); WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); - WM_jobs_callbacks(wm_job, export_startjob, NULL, NULL, export_endjob); + WM_jobs_callbacks(wm_job, + blender::io::alembic::export_startjob, + NULL, + NULL, + blender::io::alembic::export_endjob); WM_jobs_start(CTX_wm_manager(C), wm_job); } @@ -228,8 +209,8 @@ bool ABC_export(struct Scene *scene, short stop = 0, do_update = 0; float progress = 0.f; - export_startjob(job, &stop, &do_update, &progress); - export_endjob(job); + blender::io::alembic::export_startjob(job, &stop, &do_update, &progress); + blender::io::alembic::export_endjob(job); export_ok = job->export_ok; MEM_freeN(job); diff --git a/source/blender/io/alembic/exporter/abc_exporter.cc b/source/blender/io/alembic/exporter/abc_exporter.cc deleted file mode 100644 index 321bb5fcaba..00000000000 --- a/source/blender/io/alembic/exporter/abc_exporter.cc +++ /dev/null @@ -1,681 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup balembic - */ - -#include "abc_exporter.h" - -#include <cmath> - -#include "abc_writer_archive.h" -#include "abc_writer_camera.h" -#include "abc_writer_curves.h" -#include "abc_writer_hair.h" -#include "abc_writer_mball.h" -#include "abc_writer_mesh.h" -#include "abc_writer_nurbs.h" -#include "abc_writer_points.h" -#include "abc_writer_transform.h" -#include "intern/abc_util.h" - -#include "DNA_camera_types.h" -#include "DNA_curve_types.h" -#include "DNA_fluid_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meta_types.h" -#include "DNA_modifier_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_space_types.h" /* for FILE_MAX */ - -#include "BLI_string.h" - -#ifdef WIN32 -/* needed for MSCV because of snprintf from BLI_string */ -# include "BLI_winstuff.h" -#endif - -#include "BKE_duplilist.h" -#include "BKE_global.h" -#include "BKE_idprop.h" -#include "BKE_layer.h" -#include "BKE_main.h" -#include "BKE_mball.h" -#include "BKE_modifier.h" -#include "BKE_particle.h" -#include "BKE_scene.h" - -#include "DEG_depsgraph_query.h" - -using Alembic::Abc::OBox3dProperty; -using Alembic::Abc::TimeSamplingPtr; - -/* ************************************************************************** */ - -namespace blender { -namespace io { -namespace alembic { - -ExportSettings::ExportSettings() - : scene(NULL), - view_layer(NULL), - depsgraph(NULL), - logger(), - selected_only(false), - visible_objects_only(false), - renderable_only(false), - frame_start(1), - frame_end(1), - frame_samples_xform(1), - frame_samples_shape(1), - shutter_open(0.0), - shutter_close(1.0), - global_scale(1.0f), - flatten_hierarchy(false), - export_normals(false), - export_uvs(false), - export_vcols(false), - export_face_sets(false), - export_vweigths(false), - export_hair(true), - export_particles(true), - apply_subdiv(false), - use_subdiv_schema(false), - export_child_hairs(true), - pack_uv(false), - triangulate(false), - quad_method(0), - ngon_method(0) -{ -} - -static bool object_is_smoke_sim(Object *ob) -{ - ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluid); - - if (md) { - FluidModifierData *smd = reinterpret_cast<FluidModifierData *>(md); - return (smd->type == MOD_FLUID_TYPE_DOMAIN && smd->domain && - smd->domain->type == FLUID_DOMAIN_TYPE_GAS); - } - - return false; -} - -static bool object_type_is_exportable(Scene *scene, Object *ob) -{ - switch (ob->type) { - case OB_MESH: - if (object_is_smoke_sim(ob)) { - return false; - } - - return true; - case OB_EMPTY: - case OB_CURVE: - case OB_SURF: - case OB_CAMERA: - return true; - case OB_MBALL: - return AbcMBallWriter::isBasisBall(scene, ob); - default: - return false; - } -} - -/** - * Returns whether this object should be exported into the Alembic file. - * - * \param settings: export settings, used for options like 'selected only'. - * \param base: the object's base in question. - * \param is_duplicated: Normally false; true when the object is instanced - * into the scene by a dupli-object (e.g. part of a dupli-group). - * This ignores selection and layer visibility, - * and assumes that the dupli-object itself (e.g. the group-instantiating empty) is exported. - */ -static bool export_object(const ExportSettings *const settings, - const Base *const base, - bool is_duplicated) -{ - if (!is_duplicated) { - View3D *v3d = NULL; - - /* These two tests only make sense when the object isn't being instanced - * into the scene. When it is, its exportability is determined by - * its dupli-object and the DupliObject::no_draw property. */ - if (settings->selected_only && !BASE_SELECTED(v3d, base)) { - return false; - } - // FIXME Sybren: handle these cleanly (maybe just remove code), - // now using active scene layer instead. - if (settings->visible_objects_only && !BASE_VISIBLE(v3d, base)) { - return false; - } - } - - Object *ob_eval = DEG_get_evaluated_object(settings->depsgraph, base->object); - if ((ob_eval->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0) { - /* XXX fix after 2.80: the object was not part of the depsgraph, and thus we cannot get the - * evaluated copy to export. This will be handled more elegantly in the new - * AbstractHierarchyIterator that Sybren is working on. This condition is temporary, and avoids - * a BLI_assert() failure getting the evaluated mesh of this object. */ - return false; - } - - // if (settings->renderable_only && (ob->restrictflag & OB_RESTRICT_RENDER)) { - // return false; - // } - - return true; -} - -/* ************************************************************************** */ - -AbcExporter::AbcExporter(Main *bmain, const char *filename, ExportSettings &settings) - : m_bmain(bmain), - m_settings(settings), - m_filename(filename), - m_trans_sampling_index(0), - m_shape_sampling_index(0), - m_writer(NULL) -{ -} - -AbcExporter::~AbcExporter() -{ - /* Free xforms map */ - m_xforms_type::iterator it_x, e_x; - for (it_x = m_xforms.begin(), e_x = m_xforms.end(); it_x != e_x; ++it_x) { - delete it_x->second; - } - - /* Free shapes vector */ - for (int i = 0, e = m_shapes.size(); i != e; i++) { - delete m_shapes[i]; - } - - delete m_writer; -} - -void AbcExporter::getShutterSamples(unsigned int nr_of_samples, - bool time_relative, - std::vector<double> &samples) -{ - Scene *scene = m_settings.scene; /* for use in the FPS macro */ - samples.clear(); - - unsigned int frame_offset = time_relative ? m_settings.frame_start : 0; - double time_factor = time_relative ? FPS : 1.0; - double shutter_open = m_settings.shutter_open; - double shutter_close = m_settings.shutter_close; - double time_inc = (shutter_close - shutter_open) / nr_of_samples; - - /* sample between shutter open & close */ - for (int sample = 0; sample < nr_of_samples; sample++) { - double sample_time = shutter_open + time_inc * sample; - double time = (frame_offset + sample_time) / time_factor; - - samples.push_back(time); - } -} - -Alembic::Abc::TimeSamplingPtr AbcExporter::createTimeSampling(double step) -{ - std::vector<double> samples; - - if (m_settings.frame_start == m_settings.frame_end) { - return TimeSamplingPtr(new Alembic::Abc::TimeSampling()); - } - - getShutterSamples(step, true, samples); - - /* TODO(Sybren): shouldn't we use the FPS macro here? */ - Alembic::Abc::TimeSamplingType ts(static_cast<uint32_t>(samples.size()), - 1.0 / m_settings.scene->r.frs_sec); - - return TimeSamplingPtr(new Alembic::Abc::TimeSampling(ts, samples)); -} - -void AbcExporter::getFrameSet(unsigned int nr_of_samples, std::set<double> &frames) -{ - frames.clear(); - - std::vector<double> shutter_samples; - - getShutterSamples(nr_of_samples, false, shutter_samples); - - for (double frame = m_settings.frame_start; frame <= m_settings.frame_end; frame += 1.0) { - for (size_t j = 0; j < nr_of_samples; j++) { - frames.insert(frame + shutter_samples[j]); - } - } -} - -void AbcExporter::operator()(short *do_update, float *progress, bool *was_canceled) -{ - std::string abc_scene_name; - - if (m_bmain->name[0] != '\0') { - char scene_file_name[FILE_MAX]; - BLI_strncpy(scene_file_name, m_bmain->name, FILE_MAX); - abc_scene_name = scene_file_name; - } - else { - abc_scene_name = "untitled"; - } - - m_writer = new ArchiveWriter(m_filename, abc_scene_name, m_settings.scene); - - /* Create time samplings for transforms and shapes. */ - - TimeSamplingPtr trans_time = createTimeSampling(m_settings.frame_samples_xform); - - m_trans_sampling_index = m_writer->archive().addTimeSampling(*trans_time); - - TimeSamplingPtr shape_time; - - if ((m_settings.frame_samples_shape == m_settings.frame_samples_xform) || - (m_settings.frame_start == m_settings.frame_end)) { - shape_time = trans_time; - m_shape_sampling_index = m_trans_sampling_index; - } - else { - shape_time = createTimeSampling(m_settings.frame_samples_shape); - m_shape_sampling_index = m_writer->archive().addTimeSampling(*shape_time); - } - - OBox3dProperty archive_bounds_prop = Alembic::AbcGeom::CreateOArchiveBounds( - m_writer->archive(), m_trans_sampling_index); - - createTransformWritersHierarchy(); - createShapeWriters(); - - /* Make a list of frames to export. */ - - std::set<double> xform_frames; - getFrameSet(m_settings.frame_samples_xform, xform_frames); - - std::set<double> shape_frames; - getFrameSet(m_settings.frame_samples_shape, shape_frames); - - /* Merge all frames needed. */ - std::set<double> frames(xform_frames); - frames.insert(shape_frames.begin(), shape_frames.end()); - - /* Export all frames. */ - - std::set<double>::const_iterator begin = frames.begin(); - std::set<double>::const_iterator end = frames.end(); - - const float size = static_cast<float>(frames.size()); - size_t i = 0; - - for (; begin != end; ++begin) { - *progress = (++i / size); - *do_update = 1; - - if (G.is_break) { - *was_canceled = true; - break; - } - - const double frame = *begin; - - /* 'frame' is offset by start frame, so need to cancel the offset. */ - setCurrentFrame(m_bmain, frame); - - if (shape_frames.count(frame) != 0) { - for (int i = 0, e = m_shapes.size(); i != e; i++) { - m_shapes[i]->write(); - } - } - - if (xform_frames.count(frame) == 0) { - continue; - } - - m_xforms_type::iterator xit, xe; - for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) { - xit->second->write(); - } - - /* Save the archive 's bounding box. */ - Imath::Box3d bounds; - - for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) { - Imath::Box3d box = xit->second->bounds(); - bounds.extendBy(box); - } - - archive_bounds_prop.set(bounds); - } -} - -void AbcExporter::createTransformWritersHierarchy() -{ - for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base; - base = base->next) { - Object *ob = base->object; - - if (export_object(&m_settings, base, false)) { - switch (ob->type) { - case OB_LAMP: - case OB_LATTICE: - case OB_SPEAKER: - /* We do not export transforms for objects of these classes. */ - break; - default: - exploreTransform(base, ob, ob->parent, NULL); - } - } - } -} - -void AbcExporter::exploreTransform(Base *base, - Object *object, - Object *parent, - Object *dupliObParent) -{ - /* If an object isn't exported itself, its duplilist shouldn't be - * exported either. */ - if (!export_object(&m_settings, base, dupliObParent != NULL)) { - return; - } - - Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object); - if (object_type_is_exportable(m_settings.scene, ob)) { - createTransformWriter(ob, parent, dupliObParent); - } - - ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob); - - if (lb) { - DupliObject *link = static_cast<DupliObject *>(lb->first); - Object *dupli_ob = NULL; - Object *dupli_parent = NULL; - - for (; link; link = link->next) { - /* This skips things like custom bone shapes. */ - if (m_settings.renderable_only && link->no_draw) { - continue; - } - - if (link->type == OB_DUPLICOLLECTION) { - dupli_ob = link->ob; - dupli_parent = (dupli_ob->parent) ? dupli_ob->parent : ob; - - exploreTransform(base, dupli_ob, dupli_parent, ob); - } - } - - free_object_duplilist(lb); - } -} - -AbcTransformWriter *AbcExporter::createTransformWriter(Object *ob, - Object *parent, - Object *dupliObParent) -{ - /* An object should not be its own parent, or we'll get infinite loops. */ - BLI_assert(ob != parent); - BLI_assert(ob != dupliObParent); - - std::string name; - if (m_settings.flatten_hierarchy) { - name = get_id_name(ob); - } - else { - name = get_object_dag_path_name(ob, dupliObParent); - } - - /* check if we have already created a transform writer for this object */ - AbcTransformWriter *my_writer = getXForm(name); - if (my_writer != NULL) { - return my_writer; - } - - AbcTransformWriter *parent_writer = NULL; - Alembic::Abc::OObject alembic_parent; - - if (m_settings.flatten_hierarchy || parent == NULL) { - /* Parentless objects still have the "top object" as parent - * in Alembic. */ - alembic_parent = m_writer->archive().getTop(); - } - else { - /* Since there are so many different ways to find parents (as evident - * in the number of conditions below), we can't really look up the - * parent by name. We'll just call createTransformWriter(), which will - * return the parent's AbcTransformWriter pointer. */ - if (parent->parent) { - if (parent == dupliObParent) { - parent_writer = createTransformWriter(parent, parent->parent, NULL); - } - else { - parent_writer = createTransformWriter(parent, parent->parent, dupliObParent); - } - } - else if (parent == dupliObParent) { - if (dupliObParent->parent == NULL) { - parent_writer = createTransformWriter(parent, NULL, NULL); - } - else { - parent_writer = createTransformWriter( - parent, dupliObParent->parent, dupliObParent->parent); - } - } - else { - parent_writer = createTransformWriter(parent, dupliObParent, dupliObParent); - } - - BLI_assert(parent_writer); - alembic_parent = parent_writer->alembicXform(); - } - - my_writer = new AbcTransformWriter( - ob, alembic_parent, parent_writer, m_trans_sampling_index, m_settings); - - /* When flattening, the matrix of the dupliobject has to be added. */ - if (m_settings.flatten_hierarchy && dupliObParent) { - my_writer->m_proxy_from = dupliObParent; - } - - m_xforms[name] = my_writer; - return my_writer; -} - -void AbcExporter::createShapeWriters() -{ - for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base; - base = base->next) { - exploreObject(base, base->object, NULL); - } -} - -void AbcExporter::exploreObject(Base *base, Object *object, Object *dupliObParent) -{ - /* If an object isn't exported itself, its duplilist shouldn't be - * exported either. */ - if (!export_object(&m_settings, base, dupliObParent != NULL)) { - return; - } - - Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object); - createShapeWriter(ob, dupliObParent); - - ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob); - - if (lb) { - DupliObject *link = static_cast<DupliObject *>(lb->first); - - for (; link; link = link->next) { - /* This skips things like custom bone shapes. */ - if (m_settings.renderable_only && link->no_draw) { - continue; - } - if (link->type == OB_DUPLICOLLECTION) { - exploreObject(base, link->ob, ob); - } - } - - free_object_duplilist(lb); - } -} - -void AbcExporter::createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform) -{ - if (!m_settings.export_hair && !m_settings.export_particles) { - return; - } - - ParticleSystem *psys = static_cast<ParticleSystem *>(ob->particlesystem.first); - - for (; psys; psys = psys->next) { - if (!psys_check_enabled(ob, psys, G.is_rendering) || !psys->part) { - continue; - } - - if (m_settings.export_hair && psys->part->type == PART_HAIR) { - m_settings.export_child_hairs = true; - m_shapes.push_back(new AbcHairWriter(ob, xform, m_shape_sampling_index, m_settings, psys)); - } - else if (m_settings.export_particles && - (psys->part->type == PART_EMITTER || psys->part->type == PART_FLUID_FLIP || - psys->part->type == PART_FLUID_SPRAY || psys->part->type == PART_FLUID_BUBBLE || - psys->part->type == PART_FLUID_FOAM || psys->part->type == PART_FLUID_TRACER || - psys->part->type == PART_FLUID_SPRAYFOAM || - psys->part->type == PART_FLUID_SPRAYBUBBLE || - psys->part->type == PART_FLUID_FOAMBUBBLE || - psys->part->type == PART_FLUID_SPRAYFOAMBUBBLE)) { - m_shapes.push_back(new AbcPointsWriter(ob, xform, m_shape_sampling_index, m_settings, psys)); - } - } -} - -void AbcExporter::createShapeWriter(Object *ob, Object *dupliObParent) -{ - if (!object_type_is_exportable(m_settings.scene, ob)) { - return; - } - - std::string name; - - if (m_settings.flatten_hierarchy) { - name = get_id_name(ob); - } - else { - name = get_object_dag_path_name(ob, dupliObParent); - } - - AbcTransformWriter *xform = getXForm(name); - - if (!xform) { - ABC_LOG(m_settings.logger) << __func__ << ": xform " << name << " is NULL\n"; - return; - } - - createParticleSystemsWriters(ob, xform); - - switch (ob->type) { - case OB_MESH: { - Mesh *me = static_cast<Mesh *>(ob->data); - - if (!me) { - return; - } - - m_shapes.push_back(new AbcMeshWriter(ob, xform, m_shape_sampling_index, m_settings)); - break; - } - case OB_SURF: { - Curve *cu = static_cast<Curve *>(ob->data); - - if (!cu) { - return; - } - - AbcObjectWriter *writer; - if (m_settings.curves_as_mesh) { - writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings); - } - else { - writer = new AbcNurbsWriter(ob, xform, m_shape_sampling_index, m_settings); - } - m_shapes.push_back(writer); - break; - } - case OB_CURVE: { - Curve *cu = static_cast<Curve *>(ob->data); - - if (!cu) { - return; - } - - AbcObjectWriter *writer; - if (m_settings.curves_as_mesh) { - writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings); - } - else { - writer = new AbcCurveWriter(ob, xform, m_shape_sampling_index, m_settings); - } - m_shapes.push_back(writer); - break; - } - case OB_CAMERA: { - Camera *cam = static_cast<Camera *>(ob->data); - - if (cam->type == CAM_PERSP) { - m_shapes.push_back(new AbcCameraWriter(ob, xform, m_shape_sampling_index, m_settings)); - } - - break; - } - case OB_MBALL: { - MetaBall *mball = static_cast<MetaBall *>(ob->data); - if (!mball) { - return; - } - - m_shapes.push_back( - new AbcMBallWriter(m_bmain, ob, xform, m_shape_sampling_index, m_settings)); - break; - } - } -} - -AbcTransformWriter *AbcExporter::getXForm(const std::string &name) -{ - std::map<std::string, AbcTransformWriter *>::iterator it = m_xforms.find(name); - - if (it == m_xforms.end()) { - return NULL; - } - - return it->second; -} - -void AbcExporter::setCurrentFrame(Main *bmain, double t) -{ - m_settings.scene->r.cfra = static_cast<int>(t); - m_settings.scene->r.subframe = static_cast<float>(t) - m_settings.scene->r.cfra; - BKE_scene_graph_update_for_newframe(m_settings.depsgraph, bmain); -} - -} // namespace alembic -} // namespace io -} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_exporter.h b/source/blender/io/alembic/exporter/abc_exporter.h deleted file mode 100644 index af30f2ceb50..00000000000 --- a/source/blender/io/alembic/exporter/abc_exporter.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -#pragma once - -/** \file - * \ingroup balembic - */ - -#include <Alembic/Abc/All.h> -#include <map> -#include <set> -#include <vector> - -#include "intern/abc_util.h" - -struct Base; -struct Depsgraph; -struct Main; -struct Object; -struct Scene; -struct ViewLayer; - -namespace blender { -namespace io { -namespace alembic { - -class AbcObjectWriter; -class AbcTransformWriter; -class ArchiveWriter; - -struct ExportSettings { - ExportSettings(); - - Scene *scene; - /** Scene layer to export; all its objects will be exported, unless selected_only=true. */ - ViewLayer *view_layer; - Depsgraph *depsgraph; - SimpleLogger logger; - - bool selected_only; - bool visible_objects_only; - bool renderable_only; - - double frame_start, frame_end; - double frame_samples_xform; - double frame_samples_shape; - double shutter_open; - double shutter_close; - float global_scale; - - bool flatten_hierarchy; - - bool export_normals; - bool export_uvs; - bool export_vcols; - bool export_face_sets; - bool export_vweigths; - bool export_hair; - bool export_particles; - - bool apply_subdiv; - bool curves_as_mesh; - bool use_subdiv_schema; - bool export_child_hairs; - bool pack_uv; - bool triangulate; - - int quad_method; - int ngon_method; -}; - -class AbcExporter { - Main *m_bmain; - ExportSettings &m_settings; - - const char *m_filename; - - unsigned int m_trans_sampling_index, m_shape_sampling_index; - - ArchiveWriter *m_writer; - - /* mapping from name to transform writer */ - typedef std::map<std::string, AbcTransformWriter *> m_xforms_type; - m_xforms_type m_xforms; - - std::vector<AbcObjectWriter *> m_shapes; - - public: - AbcExporter(Main *bmain, const char *filename, ExportSettings &settings); - ~AbcExporter(); - - void operator()(short *do_update, float *progress, bool *was_canceled); - - protected: - void getShutterSamples(unsigned int nr_of_samples, - bool time_relative, - std::vector<double> &samples); - void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames); - - private: - Alembic::Abc::TimeSamplingPtr createTimeSampling(double step); - - void createTransformWritersHierarchy(); - AbcTransformWriter *createTransformWriter(Object *ob, Object *parent, Object *dupliObParent); - void exploreTransform(Base *base, Object *object, Object *parent, Object *dupliObParent); - void exploreObject(Base *base, Object *object, Object *dupliObParent); - void createShapeWriters(); - void createShapeWriter(Object *ob, Object *dupliObParent); - void createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform); - - AbcTransformWriter *getXForm(const std::string &name); - - void setCurrentFrame(Main *bmain, double t); -}; - -} // namespace alembic -} // namespace io -} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc new file mode 100644 index 00000000000..90004c0e85b --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc @@ -0,0 +1,261 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +#include "abc_hierarchy_iterator.h" +#include "abc_writer_abstract.h" +#include "abc_writer_camera.h" +#include "abc_writer_curves.h" +#include "abc_writer_hair.h" +#include "abc_writer_mball.h" +#include "abc_writer_mesh.h" +#include "abc_writer_nurbs.h" +#include "abc_writer_points.h" +#include "abc_writer_transform.h" + +#include <memory> +#include <string> + +#include "BLI_assert.h" + +#include "DEG_depsgraph_query.h" + +#include "DNA_ID.h" +#include "DNA_layer_types.h" +#include "DNA_object_types.h" + +namespace blender { +namespace io { +namespace alembic { + +ABCHierarchyIterator::ABCHierarchyIterator(Depsgraph *depsgraph, + ABCArchive *abc_archive, + const AlembicExportParams ¶ms) + : AbstractHierarchyIterator(depsgraph), abc_archive_(abc_archive), params_(params) +{ +} + +void ABCHierarchyIterator::iterate_and_write() +{ + AbstractHierarchyIterator::iterate_and_write(); + update_archive_bounding_box(); +} + +void ABCHierarchyIterator::update_archive_bounding_box() +{ + Imath::Box3d bounds; + update_bounding_box_recursive(bounds, HierarchyContext::root()); + abc_archive_->update_bounding_box(bounds); +} + +void ABCHierarchyIterator::update_bounding_box_recursive(Imath::Box3d &bounds, + const HierarchyContext *context) +{ + if (context != nullptr) { + AbstractHierarchyWriter *abstract_writer = writers_[context->export_path]; + ABCAbstractWriter *abc_writer = static_cast<ABCAbstractWriter *>(abstract_writer); + + if (abc_writer != nullptr) { + bounds.extendBy(abc_writer->bounding_box()); + } + } + + for (HierarchyContext *child_context : graph_children(context)) { + update_bounding_box_recursive(bounds, child_context); + } +} + +bool ABCHierarchyIterator::mark_as_weak_export(const Object *object) const +{ + if (params_.selected_only && (object->base_flag & BASE_SELECTED) == 0) { + return true; + } + /* TODO(Sybren): handle other flags too? */ + return false; +} + +void ABCHierarchyIterator::delete_object_writer(AbstractHierarchyWriter *writer) +{ + delete writer; +} + +std::string ABCHierarchyIterator::make_valid_name(const std::string &name) const +{ + std::string abc_name(name); + std::replace(abc_name.begin(), abc_name.end(), ' ', '_'); + std::replace(abc_name.begin(), abc_name.end(), '.', '_'); + std::replace(abc_name.begin(), abc_name.end(), ':', '_'); + return abc_name; +} + +AbstractHierarchyIterator::ExportGraph::key_type ABCHierarchyIterator:: + determine_graph_index_object(const HierarchyContext *context) +{ + if (params_.flatten_hierarchy) { + return std::make_pair(nullptr, nullptr); + } + + return AbstractHierarchyIterator::determine_graph_index_object(context); +} + +AbstractHierarchyIterator::ExportGraph::key_type ABCHierarchyIterator::determine_graph_index_dupli( + const HierarchyContext *context, const std::set<Object *> &dupli_set) +{ + if (params_.flatten_hierarchy) { + return std::make_pair(nullptr, nullptr); + } + + return AbstractHierarchyIterator::determine_graph_index_dupli(context, dupli_set); +} + +Alembic::Abc::OObject ABCHierarchyIterator::get_alembic_parent( + const HierarchyContext *context) const +{ + Alembic::Abc::OObject parent; + + if (!context->higher_up_export_path.empty()) { + AbstractHierarchyWriter *writer = get_writer(context->higher_up_export_path); + ABCAbstractWriter *abc_writer = static_cast<ABCAbstractWriter *>(writer); + parent = abc_writer->get_alembic_object(); + } + + if (!parent.valid()) { + /* An invalid parent object means "no parent", which should be translated to Alembic's top + * archive object. */ + return abc_archive_->archive->getTop(); + } + + return parent; +} + +ABCWriterConstructorArgs ABCHierarchyIterator::writer_constructor_args( + const HierarchyContext *context) const +{ + ABCWriterConstructorArgs constructor_args; + constructor_args.depsgraph = depsgraph_; + constructor_args.abc_archive = abc_archive_; + constructor_args.abc_parent = get_alembic_parent(context); + constructor_args.abc_name = context->export_name; + constructor_args.abc_path = context->export_path; + constructor_args.hierarchy_iterator = this; + constructor_args.export_params = ¶ms_; + return constructor_args; +} + +AbstractHierarchyWriter *ABCHierarchyIterator::create_transform_writer( + const HierarchyContext *context) +{ + ABCAbstractWriter *transform_writer = new ABCTransformWriter(writer_constructor_args(context)); + transform_writer->create_alembic_objects(context); + return transform_writer; +} + +AbstractHierarchyWriter *ABCHierarchyIterator::create_data_writer(const HierarchyContext *context) +{ + const ABCWriterConstructorArgs writer_args = writer_constructor_args(context); + ABCAbstractWriter *data_writer = nullptr; + + switch (context->object->type) { + case OB_MESH: + data_writer = new ABCMeshWriter(writer_args); + break; + case OB_CAMERA: + data_writer = new ABCCameraWriter(writer_args); + break; + case OB_CURVE: + if (params_.curves_as_mesh) { + data_writer = new ABCCurveMeshWriter(writer_args); + } + else { + data_writer = new ABCCurveWriter(writer_args); + } + break; + case OB_SURF: + if (params_.curves_as_mesh) { + data_writer = new ABCCurveMeshWriter(writer_args); + } + else { + data_writer = new ABCNurbsWriter(writer_args); + } + break; + case OB_MBALL: + data_writer = new ABCMetaballWriter(writer_args); + break; + + case OB_EMPTY: + case OB_LAMP: + case OB_FONT: + case OB_SPEAKER: + case OB_LIGHTPROBE: + case OB_LATTICE: + case OB_ARMATURE: + case OB_GPENCIL: + return nullptr; + case OB_TYPE_MAX: + BLI_assert(!"OB_TYPE_MAX should not be used"); + return nullptr; + } + + if (!data_writer->is_supported(context)) { + delete data_writer; + return nullptr; + } + + data_writer->create_alembic_objects(context); + return data_writer; +} + +AbstractHierarchyWriter *ABCHierarchyIterator::create_hair_writer(const HierarchyContext *context) +{ + if (!params_.export_hair) { + return nullptr; + } + + const ABCWriterConstructorArgs writer_args = writer_constructor_args(context); + ABCAbstractWriter *hair_writer = new ABCHairWriter(writer_args); + + if (!hair_writer->is_supported(context)) { + delete hair_writer; + return nullptr; + } + + hair_writer->create_alembic_objects(context); + return hair_writer; +} + +AbstractHierarchyWriter *ABCHierarchyIterator::create_particle_writer( + const HierarchyContext *context) +{ + if (!params_.export_particles) { + return nullptr; + } + + const ABCWriterConstructorArgs writer_args = writer_constructor_args(context); + std::unique_ptr<ABCPointsWriter> particle_writer(std::make_unique<ABCPointsWriter>(writer_args)); + + if (!particle_writer->is_supported(context)) { + return nullptr; + } + + particle_writer->create_alembic_objects(context); + return particle_writer.release(); +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.h b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.h new file mode 100644 index 00000000000..edcb31806ba --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.h @@ -0,0 +1,90 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ +#pragma once + +#include "ABC_alembic.h" +#include "abc_archive.h" + +#include "IO_abstract_hierarchy_iterator.h" + +#include <string> + +#include <Alembic/Abc/OArchive.h> +#include <Alembic/Abc/OObject.h> + +struct Depsgraph; +struct ID; +struct Object; + +namespace blender { +namespace io { +namespace alembic { + +class ABCHierarchyIterator; + +struct ABCWriterConstructorArgs { + Depsgraph *depsgraph; + ABCArchive *abc_archive; + Alembic::Abc::OObject abc_parent; + std::string abc_name; + std::string abc_path; + const ABCHierarchyIterator *hierarchy_iterator; + const AlembicExportParams *export_params; +}; + +class ABCHierarchyIterator : public AbstractHierarchyIterator { + private: + ABCArchive *abc_archive_; + const AlembicExportParams ¶ms_; + + public: + ABCHierarchyIterator(Depsgraph *depsgraph, + ABCArchive *abc_archive_, + const AlembicExportParams ¶ms); + + virtual void iterate_and_write() override; + virtual std::string make_valid_name(const std::string &name) const override; + + protected: + virtual bool mark_as_weak_export(const Object *object) const override; + + virtual ExportGraph::key_type determine_graph_index_object( + const HierarchyContext *context) override; + virtual AbstractHierarchyIterator::ExportGraph::key_type determine_graph_index_dupli( + const HierarchyContext *context, const std::set<Object *> &dupli_set) override; + + virtual AbstractHierarchyWriter *create_transform_writer( + const HierarchyContext *context) override; + virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) override; + virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) override; + virtual AbstractHierarchyWriter *create_particle_writer( + const HierarchyContext *context) override; + + virtual void delete_object_writer(AbstractHierarchyWriter *writer) override; + + private: + Alembic::Abc::OObject get_alembic_parent(const HierarchyContext *context) const; + ABCWriterConstructorArgs writer_constructor_args(const HierarchyContext *context) const; + void update_archive_bounding_box(); + void update_bounding_box_recursive(Imath::Box3d &bounds, const HierarchyContext *context); +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_subdiv_disabler.cc b/source/blender/io/alembic/exporter/abc_subdiv_disabler.cc new file mode 100644 index 00000000000..7c147076975 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_subdiv_disabler.cc @@ -0,0 +1,107 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ +#include "abc_subdiv_disabler.h" + +#include <stdio.h> + +#include "BLI_listbase.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "DNA_layer_types.h" +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "BKE_modifier.h" + +namespace blender { +namespace io { +namespace alembic { + +SubdivModifierDisabler::SubdivModifierDisabler(Depsgraph *depsgraph) : depsgraph_(depsgraph) +{ +} + +SubdivModifierDisabler::~SubdivModifierDisabler() +{ + for (ModifierData *modifier : disabled_modifiers_) { + modifier->mode &= ~eModifierMode_DisableTemporary; + } +} + +void SubdivModifierDisabler::disable_modifiers() +{ + Scene *scene = DEG_get_input_scene(depsgraph_); + ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph_); + + LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + Object *object = base->object; + + if (object->type != OB_MESH) { + continue; + } + + ModifierData *subdiv = get_subdiv_modifier(scene, object); + if (subdiv == nullptr) { + continue; + } + + /* This disables more modifiers than necessary, as it doesn't take restrictions like + * "export selected objects only" into account. However, with the subsurfs disabled, + * moving to a different frame is also going to be faster, so in the end this is probably + * a good thing to do. */ + subdiv->mode |= eModifierMode_DisableTemporary; + disabled_modifiers_.insert(subdiv); + DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); + } +} + +/* Check if the mesh is a subsurf, ignoring disabled modifiers and + * displace if it's after subsurf. */ +ModifierData *SubdivModifierDisabler::get_subdiv_modifier(Scene *scene, Object *ob) +{ + ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last); + + for (; md; md = md->prev) { + if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Render)) { + continue; + } + + if (md->type == eModifierType_Subsurf) { + SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md); + + if (smd->subdivType == ME_CC_SUBSURF) { + return md; + } + } + + /* mesh is not a subsurf. break */ + if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) { + return nullptr; + } + } + + return nullptr; +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_archive.h b/source/blender/io/alembic/exporter/abc_subdiv_disabler.h index db13dc0ec92..677847f3f63 100644 --- a/source/blender/io/alembic/exporter/abc_writer_archive.h +++ b/source/blender/io/alembic/exporter/abc_subdiv_disabler.h @@ -13,40 +13,41 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * The Original Code is Copyright (C) 2020 Blender Foundation. * All rights reserved. */ #pragma once -/** \file - * \ingroup balembic - */ - -#include <Alembic/Abc/All.h> -#include <Alembic/AbcCoreOgawa/All.h> - -#include <fstream> +#include <set> -struct Main; +struct Depsgraph; +struct ModifierData; +struct Object; struct Scene; namespace blender { namespace io { namespace alembic { -/* Wrappers around input and output archives. The goal is to be able to use - * streams so that unicode paths work on Windows (T49112), and to make sure that - * the stream objects remain valid as long as the archives are open. +/** + * Temporarily all subdivision modifiers on mesh objects. + * The destructor restores all disabled modifiers. + * + * This is used to export unsubdivided meshes to Alembic. It is done in a separate step before the + * exporter starts iterating over all the frames, so that it only has to happen once per export. */ - -class ArchiveWriter { - std::ofstream m_outfile; - Alembic::Abc::OArchive m_archive; +class SubdivModifierDisabler final { + private: + Depsgraph *depsgraph_; + std::set<ModifierData *> disabled_modifiers_; public: - ArchiveWriter(const char *filename, const std::string &abc_scene_name, const Scene *scene); + explicit SubdivModifierDisabler(Depsgraph *depsgraph); + ~SubdivModifierDisabler(); + + void disable_modifiers(); - Alembic::Abc::OArchive &archive(); + static ModifierData *get_subdiv_modifier(Scene *scene, Object *ob); }; } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.cc b/source/blender/io/alembic/exporter/abc_writer_abstract.cc new file mode 100644 index 00000000000..e43b394e27f --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_abstract.cc @@ -0,0 +1,101 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ +#include "abc_writer_abstract.h" +#include "abc_hierarchy_iterator.h" + +#include "BKE_animsys.h" +#include "BKE_key.h" +#include "BKE_object.h" + +#include "DNA_modifier_types.h" + +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + +namespace blender { +namespace io { +namespace alembic { + +using Alembic::Abc::OObject; +using Alembic::Abc::TimeSamplingPtr; + +ABCAbstractWriter::ABCAbstractWriter(const ABCWriterConstructorArgs &args) + : args_(args), + frame_has_been_written_(false), + is_animated_(false), + timesample_index_(args_.abc_archive->time_sampling_index_shapes()) +{ +} + +ABCAbstractWriter::~ABCAbstractWriter() +{ +} + +bool ABCAbstractWriter::is_supported(const HierarchyContext * /*context*/) const +{ + return true; +} + +void ABCAbstractWriter::write(HierarchyContext &context) +{ + if (!frame_has_been_written_) { + is_animated_ = (args_.export_params->frame_start != args_.export_params->frame_end) && + check_is_animated(context); + } + else if (!is_animated_) { + /* A frame has already been written, and without animation one frame is enough. */ + return; + } + + do_write(context); + + frame_has_been_written_ = true; +} + +const Imath::Box3d &ABCAbstractWriter::bounding_box() const +{ + return bounding_box_; +} + +void ABCAbstractWriter::update_bounding_box(Object *object) +{ + BoundBox *bb = BKE_object_boundbox_get(object); + + if (!bb) { + if (object->type != OB_CAMERA) { + CLOG_WARN(&LOG, "Bounding box is null!\n"); + } + bounding_box_.min.x = bounding_box_.min.y = bounding_box_.min.z = 0; + bounding_box_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0; + return; + } + + /* Convert Z-up to Y-up. This also changes which vector goes into which min/max property. */ + bounding_box_.min.x = bb->vec[0][0]; + bounding_box_.min.y = bb->vec[0][2]; + bounding_box_.min.z = -bb->vec[6][1]; + + bounding_box_.max.x = bb->vec[6][0]; + bounding_box_.max.y = bb->vec[6][2]; + bounding_box_.max.z = -bb->vec[0][1]; +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.h b/source/blender/io/alembic/exporter/abc_writer_abstract.h new file mode 100644 index 00000000000..d6fd692775e --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_abstract.h @@ -0,0 +1,77 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ +#pragma once + +#include "IO_abstract_hierarchy_iterator.h" +#include "abc_hierarchy_iterator.h" + +#include <Alembic/Abc/OObject.h> +#include <vector> + +#include "DEG_depsgraph_query.h" +#include "DNA_material_types.h" + +struct Material; +struct Object; + +namespace blender { +namespace io { +namespace alembic { + +class ABCAbstractWriter : public AbstractHierarchyWriter { + protected: + const ABCWriterConstructorArgs args_; + + bool frame_has_been_written_; + bool is_animated_; + uint32_t timesample_index_; + Imath::Box3d bounding_box_; + + public: + explicit ABCAbstractWriter(const ABCWriterConstructorArgs &args); + virtual ~ABCAbstractWriter(); + + virtual void write(HierarchyContext &context) override; + + /* Returns true if the data to be written is actually supported. This would, for example, allow a + * hypothetical camera writer accept a perspective camera but reject an orthogonal one. + * + * Returning false from a transform writer will prevent the object and all its decendants from + * being exported. Returning false from a data writer (object data, hair, or particles) will + * only prevent that data from being written (and thus cause the object to be exported as an + * Empty). */ + virtual bool is_supported(const HierarchyContext *context) const; + + const Imath::Box3d &bounding_box() const; + + /* Called by AlembicHierarchyCreator after checking that the data is supported via + * is_supported(). */ + virtual void create_alembic_objects(const HierarchyContext *context) = 0; + + virtual const Alembic::Abc::OObject get_alembic_object() const = 0; + + protected: + virtual void do_write(HierarchyContext &context) = 0; + + virtual void update_bounding_box(Object *object); +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_archive.cc b/source/blender/io/alembic/exporter/abc_writer_archive.cc deleted file mode 100644 index 741a3697b38..00000000000 --- a/source/blender/io/alembic/exporter/abc_writer_archive.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2016 Kévin Dietrich. - * All rights reserved. - */ - -/** \file - * \ingroup balembic - */ - -#include "abc_writer_archive.h" - -#include "BKE_blender_version.h" - -#include "BLI_path_util.h" -#include "BLI_string.h" - -#include "DNA_scene_types.h" - -#ifdef WIN32 -# include "utfconv.h" -#endif - -#include <fstream> - -using Alembic::Abc::ErrorHandler; -using Alembic::Abc::kWrapExisting; -using Alembic::Abc::OArchive; - -namespace blender { -namespace io { -namespace alembic { - -/* This kinda duplicates CreateArchiveWithInfo, but Alembic does not seem to - * have a version supporting streams. */ -static OArchive create_archive(std::ostream *ostream, - const std::string &scene_name, - double scene_fps) -{ - Alembic::Abc::MetaData abc_metadata; - - abc_metadata.set(Alembic::Abc::kApplicationNameKey, "Blender"); - abc_metadata.set(Alembic::Abc::kUserDescriptionKey, scene_name); - abc_metadata.set("blender_version", std::string("v") + BKE_blender_version_string()); - abc_metadata.set("FramesPerTimeUnit", std::to_string(scene_fps)); - - time_t raw_time; - time(&raw_time); - char buffer[128]; - -#if defined _WIN32 || defined _WIN64 - ctime_s(buffer, 128, &raw_time); -#else - ctime_r(&raw_time, buffer); -#endif - - const std::size_t buffer_len = strlen(buffer); - if (buffer_len > 0 && buffer[buffer_len - 1] == '\n') { - buffer[buffer_len - 1] = '\0'; - } - - abc_metadata.set(Alembic::Abc::kDateWrittenKey, buffer); - - ErrorHandler::Policy policy = ErrorHandler::kThrowPolicy; - Alembic::AbcCoreOgawa::WriteArchive archive_writer; - return OArchive(archive_writer(ostream, abc_metadata), kWrapExisting, policy); -} - -ArchiveWriter::ArchiveWriter(const char *filename, - const std::string &abc_scene_name, - const Scene *scene) -{ - /* Use stream to support unicode character paths on Windows. */ -#ifdef WIN32 - UTF16_ENCODE(filename); - std::wstring wstr(filename_16); - m_outfile.open(wstr.c_str(), std::ios::out | std::ios::binary); - UTF16_UN_ENCODE(filename); -#else - m_outfile.open(filename, std::ios::out | std::ios::binary); -#endif - - m_archive = create_archive(&m_outfile, abc_scene_name, FPS); -} - -OArchive &ArchiveWriter::archive() -{ - return m_archive; -} - -} // namespace alembic -} // namespace io -} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_camera.cc b/source/blender/io/alembic/exporter/abc_writer_camera.cc index ef6d1512a96..7e7277cb4ea 100644 --- a/source/blender/io/alembic/exporter/abc_writer_camera.cc +++ b/source/blender/io/alembic/exporter/abc_writer_camera.cc @@ -19,67 +19,90 @@ */ #include "abc_writer_camera.h" -#include "abc_writer_transform.h" +#include "abc_hierarchy_iterator.h" + +#include "BKE_camera.h" + +#include "BLI_assert.h" #include "DNA_camera_types.h" -#include "DNA_object_types.h" +#include "DNA_scene_types.h" -using Alembic::AbcGeom::OCamera; -using Alembic::AbcGeom::OFloatProperty; +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; namespace blender { namespace io { namespace alembic { -AbcCameraWriter::AbcCameraWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings) - : AbcObjectWriter(ob, time_sampling, settings, parent) +using Alembic::AbcGeom::CameraSample; +using Alembic::AbcGeom::OCamera; +using Alembic::AbcGeom::OFloatProperty; + +ABCCameraWriter::ABCCameraWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args) +{ +} + +bool ABCCameraWriter::is_supported(const HierarchyContext *context) const +{ + Camera *camera = static_cast<Camera *>(context->object->data); + return camera->type == CAM_PERSP; +} + +void ABCCameraWriter::create_alembic_objects(const HierarchyContext * /*context*/) { - OCamera camera(parent->alembicXform(), m_name, m_time_sampling); - m_camera_schema = camera.getSchema(); + CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str()); + abc_camera_ = OCamera(args_.abc_parent, args_.abc_name, timesample_index_); + abc_camera_schema_ = abc_camera_.getSchema(); + + abc_custom_data_container_ = abc_camera_schema_.getUserProperties(); + abc_stereo_distance_ = OFloatProperty( + abc_custom_data_container_, "stereoDistance", timesample_index_); + abc_eye_separation_ = OFloatProperty( + abc_custom_data_container_, "eyeSeparation", timesample_index_); +} - 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); +const Alembic::Abc::OObject ABCCameraWriter::get_alembic_object() const +{ + return abc_camera_; } -void AbcCameraWriter::do_write() +void ABCCameraWriter::do_write(HierarchyContext &context) { - Camera *cam = static_cast<Camera *>(m_object->data); + Camera *cam = static_cast<Camera *>(context.object->data); - m_stereo_distance.set(cam->stereo.convergence_distance); - m_eye_separation.set(cam->stereo.interocular_distance); + abc_stereo_distance_.set(cam->stereo.convergence_distance); + abc_eye_separation_.set(cam->stereo.interocular_distance); const double apperture_x = cam->sensor_x / 10.0; const double apperture_y = cam->sensor_y / 10.0; const double film_aspect = apperture_x / apperture_y; - m_camera_sample.setFocalLength(cam->lens); - m_camera_sample.setHorizontalAperture(apperture_x); - m_camera_sample.setVerticalAperture(apperture_y); - m_camera_sample.setHorizontalFilmOffset(apperture_x * cam->shiftx); - m_camera_sample.setVerticalFilmOffset(apperture_y * cam->shifty * film_aspect); - m_camera_sample.setNearClippingPlane(cam->clip_start); - m_camera_sample.setFarClippingPlane(cam->clip_end); + CameraSample camera_sample; + camera_sample.setFocalLength(cam->lens); + camera_sample.setHorizontalAperture(apperture_x); + camera_sample.setVerticalAperture(apperture_y); + camera_sample.setHorizontalFilmOffset(apperture_x * cam->shiftx); + camera_sample.setVerticalFilmOffset(apperture_y * cam->shifty * film_aspect); + camera_sample.setNearClippingPlane(cam->clip_start); + camera_sample.setFarClippingPlane(cam->clip_end); if (cam->dof.focus_object) { - Imath::V3f v(m_object->loc[0] - cam->dof.focus_object->loc[0], - m_object->loc[1] - cam->dof.focus_object->loc[1], - m_object->loc[2] - cam->dof.focus_object->loc[2]); - m_camera_sample.setFocusDistance(v.length()); + Imath::V3f v(context.object->loc[0] - cam->dof.focus_object->loc[0], + context.object->loc[1] - cam->dof.focus_object->loc[1], + context.object->loc[2] - cam->dof.focus_object->loc[2]); + camera_sample.setFocusDistance(v.length()); } else { - m_camera_sample.setFocusDistance(cam->dof.focus_distance); + camera_sample.setFocusDistance(cam->dof.focus_distance); } /* Blender camera does not have an fstop param, so try to find a custom prop * instead. */ - m_camera_sample.setFStop(cam->dof.aperture_fstop); + camera_sample.setFStop(cam->dof.aperture_fstop); - m_camera_sample.setLensSqueezeRatio(1.0); - m_camera_schema.set(m_camera_sample); + camera_sample.setLensSqueezeRatio(1.0); + abc_camera_schema_.set(camera_sample); } } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_camera.h b/source/blender/io/alembic/exporter/abc_writer_camera.h index befa1e24551..a72cfa2f357 100644 --- a/source/blender/io/alembic/exporter/abc_writer_camera.h +++ b/source/blender/io/alembic/exporter/abc_writer_camera.h @@ -19,29 +19,32 @@ * \ingroup balembic */ -#include "abc_writer_object.h" +#include "abc_writer_abstract.h" -/* ************************************************************************** */ +#include <Alembic/AbcGeom/OCamera.h> namespace blender { namespace io { namespace alembic { -class AbcCameraWriter : public AbcObjectWriter { - Alembic::AbcGeom::OCameraSchema m_camera_schema; - Alembic::AbcGeom::CameraSample m_camera_sample; - Alembic::AbcGeom::OCompoundProperty m_custom_data_container; - Alembic::AbcGeom::OFloatProperty m_stereo_distance; - Alembic::AbcGeom::OFloatProperty m_eye_separation; +class ABCCameraWriter : public ABCAbstractWriter { + private: + Alembic::AbcGeom::OCamera abc_camera_; + Alembic::AbcGeom::OCameraSchema abc_camera_schema_; + + Alembic::AbcGeom::OCompoundProperty abc_custom_data_container_; + Alembic::AbcGeom::OFloatProperty abc_stereo_distance_; + Alembic::AbcGeom::OFloatProperty abc_eye_separation_; public: - AbcCameraWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings); + explicit ABCCameraWriter(const ABCWriterConstructorArgs &args); - private: - virtual void do_write(); + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; + + protected: + virtual bool is_supported(const HierarchyContext *context) const override; + virtual void do_write(HierarchyContext &context) override; }; } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.cc b/source/blender/io/alembic/exporter/abc_writer_curves.cc index 7c2acb9e0c9..f2a46c5e4fe 100644 --- a/source/blender/io/alembic/exporter/abc_writer_curves.cc +++ b/source/blender/io/alembic/exporter/abc_writer_curves.cc @@ -22,9 +22,7 @@ */ #include "abc_writer_curves.h" -#include "abc_writer_transform.h" #include "intern/abc_axis_conversion.h" -#include "intern/abc_reader_curves.h" #include "DNA_curve_types.h" #include "DNA_object_types.h" @@ -33,6 +31,9 @@ #include "BKE_mesh.h" #include "BKE_object.h" +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + using Alembic::AbcGeom::OCompoundProperty; using Alembic::AbcGeom::OCurves; using Alembic::AbcGeom::OCurvesSchema; @@ -44,24 +45,32 @@ namespace blender { namespace io { namespace alembic { -AbcCurveWriter::AbcCurveWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings) - : AbcObjectWriter(ob, time_sampling, settings, parent) +const std::string ABC_CURVE_RESOLUTION_U_PROPNAME("blender:resolution"); + +ABCCurveWriter::ABCCurveWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args) +{ +} + +void ABCCurveWriter::create_alembic_objects(const HierarchyContext *context) { - OCurves curves(parent->alembicXform(), m_name, m_time_sampling); - m_schema = curves.getSchema(); + CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str()); + abc_curve_ = OCurves(args_.abc_parent, args_.abc_name, timesample_index_); + abc_curve_schema_ = abc_curve_.getSchema(); - Curve *cu = static_cast<Curve *>(m_object->data); - OCompoundProperty user_props = m_schema.getUserProperties(); + Curve *cu = static_cast<Curve *>(context->object->data); + OCompoundProperty user_props = abc_curve_schema_.getUserProperties(); OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME); user_prop_resolu.set(cu->resolu); } -void AbcCurveWriter::do_write() +const Alembic::Abc::OObject ABCCurveWriter::get_alembic_object() const +{ + return abc_curve_; +} + +void ABCCurveWriter::do_write(HierarchyContext &context) { - Curve *curve = static_cast<Curve *>(m_object->data); + Curve *curve = static_cast<Curve *>(context.object->data); std::vector<Imath::V3f> verts; std::vector<int32_t> vert_counts; @@ -152,35 +161,31 @@ void AbcCurveWriter::do_write() Alembic::AbcGeom::OFloatGeomParam::Sample width_sample; width_sample.setVals(widths); - m_sample = OCurvesSchema::Sample(verts, - vert_counts, - curve_type, - periodicity, - width_sample, - OV2fGeomParam::Sample(), /* UVs */ - ON3fGeomParam::Sample(), /* normals */ - curve_basis, - weights, - orders, - knots); - - m_sample.setSelfBounds(bounds()); - m_schema.set(m_sample); + OCurvesSchema::Sample sample(verts, + vert_counts, + curve_type, + periodicity, + width_sample, + OV2fGeomParam::Sample(), /* UVs */ + ON3fGeomParam::Sample(), /* normals */ + curve_basis, + weights, + orders, + knots); + + update_bounding_box(context.object); + sample.setSelfBounds(bounding_box_); + abc_curve_schema_.set(sample); } -AbcCurveMeshWriter::AbcCurveMeshWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings) - : AbcGenericMeshWriter(ob, parent, time_sampling, settings) +ABCCurveMeshWriter::ABCCurveMeshWriter(const ABCWriterConstructorArgs &args) + : ABCGenericMeshWriter(args) { } -Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/, - Object *ob_eval, - bool &r_needsfree) +Mesh *ABCCurveMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree) { - Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval); if (mesh_eval != NULL) { /* Mesh_eval only exists when generative modifiers are in use. */ r_needsfree = false; @@ -188,7 +193,7 @@ Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/, } r_needsfree = true; - return BKE_mesh_new_nomain_from_curve(ob_eval); + return BKE_mesh_new_nomain_from_curve(object_eval); } } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.h b/source/blender/io/alembic/exporter/abc_writer_curves.h index d12ebc46a22..12a909761f5 100644 --- a/source/blender/io/alembic/exporter/abc_writer_curves.h +++ b/source/blender/io/alembic/exporter/abc_writer_curves.h @@ -22,36 +22,38 @@ * \ingroup balembic */ +#include "abc_writer_abstract.h" #include "abc_writer_mesh.h" -#include "abc_writer_object.h" + +#include <Alembic/AbcGeom/OCurves.h> namespace blender { namespace io { namespace alembic { -class AbcCurveWriter : public AbcObjectWriter { - Alembic::AbcGeom::OCurvesSchema m_schema; - Alembic::AbcGeom::OCurvesSchema::Sample m_sample; +extern const std::string ABC_CURVE_RESOLUTION_U_PROPNAME; + +class ABCCurveWriter : public ABCAbstractWriter { + private: + Alembic::AbcGeom::OCurves abc_curve_; + Alembic::AbcGeom::OCurvesSchema abc_curve_schema_; public: - AbcCurveWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings); + explicit ABCCurveWriter(const ABCWriterConstructorArgs &args); + + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; protected: - void do_write(); + virtual void do_write(HierarchyContext &context) override; }; -class AbcCurveMeshWriter : public AbcGenericMeshWriter { +class ABCCurveMeshWriter : public ABCGenericMeshWriter { public: - AbcCurveMeshWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings); + ABCCurveMeshWriter(const ABCWriterConstructorArgs &args); protected: - Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree); + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; }; } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.cc b/source/blender/io/alembic/exporter/abc_writer_hair.cc index 304aa7a5ff8..ac4deddd9b4 100644 --- a/source/blender/io/alembic/exporter/abc_writer_hair.cc +++ b/source/blender/io/alembic/exporter/abc_writer_hair.cc @@ -19,7 +19,6 @@ */ #include "abc_writer_hair.h" -#include "abc_writer_transform.h" #include "intern/abc_axis_conversion.h" #include <cstdio> @@ -35,40 +34,46 @@ #include "BKE_mesh_runtime.h" #include "BKE_particle.h" -using Alembic::Abc::P3fArraySamplePtr; +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; +using Alembic::Abc::P3fArraySamplePtr; using Alembic::AbcGeom::OCurves; using Alembic::AbcGeom::OCurvesSchema; using Alembic::AbcGeom::ON3fGeomParam; using Alembic::AbcGeom::OV2fGeomParam; -/* ************************************************************************** */ - namespace blender { namespace io { namespace alembic { -AbcHairWriter::AbcHairWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings, - ParticleSystem *psys) - : AbcObjectWriter(ob, time_sampling, settings, parent), m_uv_warning_shown(false) +ABCHairWriter::ABCHairWriter(const ABCWriterConstructorArgs &args) + : ABCAbstractWriter(args), uv_warning_shown_(false) { - m_psys = psys; +} - std::string psys_name = get_valid_abc_name(psys->name); - OCurves curves(parent->alembicXform(), psys_name, m_time_sampling); - m_schema = curves.getSchema(); +void ABCHairWriter::create_alembic_objects(const HierarchyContext * /*context*/) +{ + CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str()); + abc_curves_ = OCurves(args_.abc_parent, args_.abc_name, timesample_index_); + abc_curves_schema_ = abc_curves_.getSchema(); } -void AbcHairWriter::do_write() +const Alembic::Abc::OObject ABCHairWriter::get_alembic_object() const { - if (!m_psys) { - return; - } - Mesh *mesh = mesh_get_eval_final( - m_settings.depsgraph, m_settings.scene, m_object, &CD_MASK_MESH); + return abc_curves_; +} + +bool ABCHairWriter::check_is_animated(const HierarchyContext & /*context*/) const +{ + /* We assume that hair particles are always animated. */ + return true; +} + +void ABCHairWriter::do_write(HierarchyContext &context) +{ + Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph); + Mesh *mesh = mesh_get_eval_final(args_.depsgraph, scene_eval, context.object, &CD_MASK_MESH); BKE_mesh_tessface_ensure(mesh); std::vector<Imath::V3f> verts; @@ -76,44 +81,45 @@ void AbcHairWriter::do_write() std::vector<Imath::V2f> uv_values; std::vector<Imath::V3f> norm_values; - if (m_psys->pathcache) { - ParticleSettings *part = m_psys->part; - bool export_children = m_settings.export_child_hairs && m_psys->childcache && - part->childtype != 0; + ParticleSystem *psys = context.particle_system; + if (psys->pathcache) { + ParticleSettings *part = psys->part; + bool export_children = psys->childcache && part->childtype != 0; if (!export_children || part->draw & PART_DRAW_PARENT) { - write_hair_sample(mesh, part, verts, norm_values, uv_values, hvertices); + write_hair_sample(context, mesh, verts, norm_values, uv_values, hvertices); } if (export_children) { - write_hair_child_sample(mesh, part, verts, norm_values, uv_values, hvertices); + write_hair_child_sample(context, mesh, verts, norm_values, uv_values, hvertices); } } Alembic::Abc::P3fArraySample iPos(verts); - m_sample = OCurvesSchema::Sample(iPos, hvertices); - m_sample.setBasis(Alembic::AbcGeom::kNoBasis); - m_sample.setType(Alembic::AbcGeom::kLinear); - m_sample.setWrap(Alembic::AbcGeom::kNonPeriodic); + OCurvesSchema::Sample sample(iPos, hvertices); + sample.setBasis(Alembic::AbcGeom::kNoBasis); + sample.setType(Alembic::AbcGeom::kLinear); + sample.setWrap(Alembic::AbcGeom::kNonPeriodic); if (!uv_values.empty()) { OV2fGeomParam::Sample uv_smp; uv_smp.setVals(uv_values); - m_sample.setUVs(uv_smp); + sample.setUVs(uv_smp); } if (!norm_values.empty()) { ON3fGeomParam::Sample norm_smp; norm_smp.setVals(norm_values); - m_sample.setNormals(norm_smp); + sample.setNormals(norm_smp); } - m_sample.setSelfBounds(bounds()); - m_schema.set(m_sample); + update_bounding_box(context.object); + sample.setSelfBounds(bounding_box_); + abc_curves_schema_.set(sample); } -void AbcHairWriter::write_hair_sample(Mesh *mesh, - ParticleSettings *part, +void ABCHairWriter::write_hair_sample(const HierarchyContext &context, + Mesh *mesh, std::vector<Imath::V3f> &verts, std::vector<Imath::V3f> &norm_values, std::vector<Imath::V2f> &uv_values, @@ -121,28 +127,30 @@ void AbcHairWriter::write_hair_sample(Mesh *mesh, { /* Get untransformed vertices, there's a xform under the hair. */ float inv_mat[4][4]; - invert_m4_m4_safe(inv_mat, m_object->obmat); + invert_m4_m4_safe(inv_mat, context.object->obmat); MTFace *mtface = mesh->mtface; MFace *mface = mesh->mface; MVert *mverts = mesh->mvert; - if ((!mtface || !mface) && !m_uv_warning_shown) { + if ((!mtface || !mface) && !uv_warning_shown_) { std::fprintf(stderr, "Warning, no UV set found for underlying geometry of %s.\n", - m_object->id.name + 2); - m_uv_warning_shown = true; + context.object->id.name + 2); + uv_warning_shown_ = true; } - ParticleData *pa = m_psys->particles; + ParticleSystem *psys = context.particle_system; + ParticleSettings *part = psys->part; + ParticleData *pa = psys->particles; int k; - ParticleCacheKey **cache = m_psys->pathcache; + ParticleCacheKey **cache = psys->pathcache; ParticleCacheKey *path; float normal[3]; Imath::V3f tmp_nor; - for (int p = 0; p < m_psys->totpart; p++, pa++) { + for (int p = 0; p < psys->totpart; p++, pa++) { /* underlying info for faces-only emission */ path = cache[p]; @@ -224,8 +232,8 @@ void AbcHairWriter::write_hair_sample(Mesh *mesh, } } -void AbcHairWriter::write_hair_child_sample(Mesh *mesh, - ParticleSettings *part, +void ABCHairWriter::write_hair_child_sample(const HierarchyContext &context, + Mesh *mesh, std::vector<Imath::V3f> &verts, std::vector<Imath::V3f> &norm_values, std::vector<Imath::V2f> &uv_values, @@ -233,26 +241,30 @@ void AbcHairWriter::write_hair_child_sample(Mesh *mesh, { /* Get untransformed vertices, there's a xform under the hair. */ float inv_mat[4][4]; - invert_m4_m4_safe(inv_mat, m_object->obmat); + invert_m4_m4_safe(inv_mat, context.object->obmat); MTFace *mtface = mesh->mtface; MVert *mverts = mesh->mvert; - ParticleCacheKey **cache = m_psys->childcache; + ParticleSystem *psys = context.particle_system; + ParticleSettings *part = psys->part; + ParticleCacheKey **cache = psys->childcache; ParticleCacheKey *path; - ChildParticle *pc = m_psys->child; + ChildParticle *pc = psys->child; - for (int p = 0; p < m_psys->totchild; p++, pc++) { + for (int p = 0; p < psys->totchild; p++, pc++) { path = cache[p]; if (part->from == PART_FROM_FACE && part->childtype != PART_CHILD_PARTICLES && mtface) { const int num = pc->num; if (num < 0) { - ABC_LOG(m_settings.logger) - << "Warning, child particle of hair system " << m_psys->name - << " has unknown face index of geometry of " << (m_object->id.name + 2) - << ", skipping child hair." << std::endl; + CLOG_WARN( + &LOG, + "Child particle of hair system %s has unknown face index of geometry of %s, skipping " + "child hair.", + psys->name, + context.object->id.name + 2); continue; } diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.h b/source/blender/io/alembic/exporter/abc_writer_hair.h index d7831684a12..af1372a08f3 100644 --- a/source/blender/io/alembic/exporter/abc_writer_hair.h +++ b/source/blender/io/alembic/exporter/abc_writer_hair.h @@ -19,9 +19,10 @@ * \ingroup balembic */ -#include "abc_writer_object.h" +#include "abc_writer_abstract.h" +#include <Alembic/AbcGeom/OCurves.h> +#include <vector> -struct Mesh; struct ParticleSettings; struct ParticleSystem; @@ -29,33 +30,33 @@ namespace blender { namespace io { namespace alembic { -class AbcHairWriter : public AbcObjectWriter { - ParticleSystem *m_psys; - - Alembic::AbcGeom::OCurvesSchema m_schema; - Alembic::AbcGeom::OCurvesSchema::Sample m_sample; +class ABCHairWriter : public ABCAbstractWriter { + private: + Alembic::AbcGeom::OCurves abc_curves_; + Alembic::AbcGeom::OCurvesSchema abc_curves_schema_; - bool m_uv_warning_shown; + bool uv_warning_shown_; public: - AbcHairWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings, - ParticleSystem *psys); + explicit ABCHairWriter(const ABCWriterConstructorArgs &args); - private: - virtual void do_write(); + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; - void write_hair_sample(struct Mesh *mesh, - ParticleSettings *part, + protected: + virtual void do_write(HierarchyContext &context) override; + virtual bool check_is_animated(const HierarchyContext &context) const override; + + private: + void write_hair_sample(const HierarchyContext &context, + struct Mesh *mesh, std::vector<Imath::V3f> &verts, std::vector<Imath::V3f> &norm_values, std::vector<Imath::V2f> &uv_values, std::vector<int32_t> &hvertices); - void write_hair_child_sample(struct Mesh *mesh, - ParticleSettings *part, + void write_hair_child_sample(const HierarchyContext &context, + struct Mesh *mesh, std::vector<Imath::V3f> &verts, std::vector<Imath::V3f> &norm_values, std::vector<Imath::V2f> &uv_values, diff --git a/source/blender/io/alembic/exporter/abc_writer_mball.cc b/source/blender/io/alembic/exporter/abc_writer_mball.cc index 8ae3bb64390..167e392eb96 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mball.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mball.cc @@ -19,11 +19,9 @@ */ #include "abc_writer_mball.h" -#include "abc_writer_mesh.h" +#include "abc_hierarchy_iterator.h" -#include "DNA_mesh_types.h" -#include "DNA_meta_types.h" -#include "DNA_object_types.h" +#include "BLI_assert.h" #include "BKE_displist.h" #include "BKE_lib_id.h" @@ -31,68 +29,57 @@ #include "BKE_mesh.h" #include "BKE_object.h" -#include "BLI_utildefines.h" +#include "DNA_mesh_types.h" +#include "DNA_meta_types.h" namespace blender { namespace io { namespace alembic { -AbcMBallWriter::AbcMBallWriter(Main *bmain, - Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings) - : AbcGenericMeshWriter(ob, parent, time_sampling, settings), m_bmain(bmain) +ABCMetaballWriter::ABCMetaballWriter(const ABCWriterConstructorArgs &args) + : ABCGenericMeshWriter(args) { - m_is_animated = isAnimated(); } -AbcMBallWriter::~AbcMBallWriter() +bool ABCMetaballWriter::is_supported(const HierarchyContext *context) const { + Scene *scene = DEG_get_input_scene(args_.depsgraph); + bool supported = is_basis_ball(scene, context->object) && + ABCGenericMeshWriter::is_supported(context); + return supported; } -bool AbcMBallWriter::isAnimated() const +bool ABCMetaballWriter::check_is_animated(const HierarchyContext & /*context*/) const { + /* We assume that metaballs are always animated, as the current object may + * not be animated but another ball in the same group may be. */ return true; } -Mesh *AbcMBallWriter::getEvaluatedMesh(Scene * /*scene_eval*/, Object *ob_eval, bool &r_needsfree) +bool ABCMetaballWriter::export_as_subdivision_surface(Object * /*ob_eval*/) const { - Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); - if (mesh_eval != NULL) { + /* Metaballs should be exported to subdivision surfaces, if the export options allow. */ + return true; +} + +Mesh *ABCMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree) +{ + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval); + if (mesh_eval != nullptr) { /* Mesh_eval only exists when generative modifiers are in use. */ r_needsfree = false; return mesh_eval; } r_needsfree = true; - - /* The approach below is copied from BKE_mesh_new_from_object() */ - Mesh *tmpmesh = BKE_mesh_add(m_bmain, ((ID *)m_object->data)->name + 2); - BLI_assert(tmpmesh != NULL); - - /* BKE_mesh_add gives us a user count we don't need */ - id_us_min(&tmpmesh->id); - - ListBase disp = {NULL, NULL}; - /* TODO(sergey): This is gonna to work for until Depsgraph - * only contains for_render flag. As soon as CoW is - * implemented, this is to be rethought. - */ - BKE_displist_make_mball_forRender(m_settings.depsgraph, m_settings.scene, m_object, &disp); - BKE_mesh_from_metaball(&disp, tmpmesh); - BKE_displist_free(&disp); - - BKE_mesh_texspace_copy_from_object(tmpmesh, m_object); - - return tmpmesh; + return BKE_mesh_new_from_object(args_.depsgraph, object_eval, false); } -void AbcMBallWriter::freeEvaluatedMesh(struct Mesh *mesh) +void ABCMetaballWriter::free_export_mesh(Mesh *mesh) { - BKE_id_free(m_bmain, mesh); + BKE_id_free(nullptr, mesh); } -bool AbcMBallWriter::isBasisBall(Scene *scene, Object *ob) +bool ABCMetaballWriter::is_basis_ball(Scene *scene, Object *ob) const { Object *basis_ob = BKE_mball_basis_find(scene, ob); return ob == basis_ob; diff --git a/source/blender/io/alembic/exporter/abc_writer_mball.h b/source/blender/io/alembic/exporter/abc_writer_mball.h index d632f0bd410..90d8c4d4b15 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mball.h +++ b/source/blender/io/alembic/exporter/abc_writer_mball.h @@ -20,39 +20,24 @@ */ #include "abc_writer_mesh.h" -#include "abc_writer_object.h" - -struct Main; -struct Object; namespace blender { namespace io { namespace alembic { -/* AbcMBallWriter converts the metaballs to meshes at every frame, - * and defers to AbcGenericMeshWriter to perform the writing - * to the Alembic file. Only the basis balls are exported, as this - * results in the entire shape as one mesh. */ -class AbcMBallWriter : public AbcGenericMeshWriter { - Main *m_bmain; - +class ABCMetaballWriter : public ABCGenericMeshWriter { public: - explicit AbcMBallWriter(Main *bmain, - Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings); - - ~AbcMBallWriter(); - - static bool isBasisBall(Scene *scene, Object *ob); + explicit ABCMetaballWriter(const ABCWriterConstructorArgs &args); protected: - Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override; - void freeEvaluatedMesh(struct Mesh *mesh) override; + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; + virtual void free_export_mesh(Mesh *mesh) override; + virtual bool is_supported(const HierarchyContext *context) const override; + virtual bool check_is_animated(const HierarchyContext &context) const override; + virtual bool export_as_subdivision_surface(Object *ob_eval) const override; private: - bool isAnimated() const override; + bool is_basis_ball(Scene *scene, Object *ob) const; }; } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index 512768bcd98..07196f2b81f 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -19,29 +19,37 @@ */ #include "abc_writer_mesh.h" -#include "abc_writer_transform.h" +#include "abc_hierarchy_iterator.h" #include "intern/abc_axis_conversion.h" -#include "DNA_material_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_object_fluidsim_types.h" +#include "BLI_assert.h" +#include "BLI_math_vector.h" -#include "BKE_anim_data.h" -#include "BKE_key.h" +#include "BKE_customdata.h" #include "BKE_lib_id.h" #include "BKE_material.h" #include "BKE_mesh.h" -#include "BKE_mesh_runtime.h" #include "BKE_modifier.h" +#include "BKE_object.h" #include "bmesh.h" #include "bmesh_tools.h" -#include "DEG_depsgraph_query.h" +#include "DEG_depsgraph.h" + +#include "DNA_layer_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_fluidsim_types.h" +#include "DNA_particle_types.h" + +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; using Alembic::Abc::FloatArraySample; using Alembic::Abc::Int32ArraySample; +using Alembic::Abc::OObject; using Alembic::Abc::V2fArraySample; using Alembic::Abc::V3fArraySample; @@ -64,136 +72,77 @@ namespace alembic { /* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */ -static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points) -{ - 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_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points); static void get_topology(struct Mesh *mesh, std::vector<int32_t> &poly_verts, std::vector<int32_t> &loop_counts, - bool &r_has_flat_shaded_poly) -{ - 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); - } - } -} - + bool &r_has_flat_shaded_poly); static void get_creases(struct Mesh *mesh, std::vector<int32_t> &indices, std::vector<int32_t> &lengths, - std::vector<float> &sharpnesses) -{ - const float factor = 1.0f / 255.0f; + std::vector<float> &sharpnesses); +static void get_loop_normals(struct Mesh *mesh, + std::vector<Imath::V3f> &normals, + bool has_flat_shaded_poly); - indices.clear(); - lengths.clear(); - sharpnesses.clear(); +ABCGenericMeshWriter::ABCGenericMeshWriter(const ABCWriterConstructorArgs &args) + : ABCAbstractWriter(args), is_subd_(false) +{ +} - MEdge *edge = mesh->medge; +void ABCGenericMeshWriter::create_alembic_objects(const HierarchyContext *context) +{ + if (!args_.export_params->apply_subdiv && export_as_subdivision_surface(context->object)) { + is_subd_ = args_.export_params->use_subdiv_schema; + } - for (int i = 0, e = mesh->totedge; i < e; i++) { - const float sharpness = static_cast<float>(edge[i].crease) * factor; + if (is_subd_) { + CLOG_INFO(&LOG, 2, "exporting OSubD %s", args_.abc_path.c_str()); + abc_subdiv_ = OSubD(args_.abc_parent, args_.abc_name, timesample_index_); + abc_subdiv_schema_ = abc_subdiv_.getSchema(); + } + else { + CLOG_INFO(&LOG, 2, "exporting OPolyMesh %s", args_.abc_path.c_str()); + abc_poly_mesh_ = OPolyMesh(args_.abc_parent, args_.abc_name, timesample_index_); + abc_poly_mesh_schema_ = abc_poly_mesh_.getSchema(); - if (sharpness != 0.0f) { - indices.push_back(edge[i].v1); - indices.push_back(edge[i].v2); - sharpnesses.push_back(sharpness); - } + OCompoundProperty typeContainer = abc_poly_mesh_.getSchema().getUserProperties(); + OBoolProperty type(typeContainer, "meshtype"); + type.set(subsurf_modifier_ == nullptr); } - lengths.resize(sharpnesses.size(), 2); + Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph); + liquid_sim_modifier_ = get_liquid_sim_modifier(scene_eval, context->object); } -static void get_loop_normals(struct Mesh *mesh, - std::vector<Imath::V3f> &normals, - bool has_flat_shaded_poly) +ABCGenericMeshWriter::~ABCGenericMeshWriter() { - 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]); - } +const Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const +{ + if (is_subd_) { + return abc_subdiv_; } + return abc_poly_mesh_; } -/* *************** Modifiers *************** */ - -/* check if the mesh is a subsurf, ignoring disabled modifiers and - * displace if it's after subsurf. */ -static ModifierData *get_subsurf_modifier(Scene *scene, Object *ob) +bool ABCGenericMeshWriter::export_as_subdivision_surface(Object *ob_eval) const { - ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last); + ModifierData *md = static_cast<ModifierData *>(ob_eval->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; + /* This modifier has been temporarily disabled by SubdivModifierDisabler, + * so this indicates this is to be exported as subdivision surface. */ + if (md->type == eModifierType_Subsurf && (md->mode & eModifierMode_DisableTemporary)) { + return true; } } - return NULL; + return false; } -static ModifierData *get_liquid_sim_modifier(Scene *scene, Object *ob) +ModifierData *ABCGenericMeshWriter::get_liquid_sim_modifier(Scene *scene, Object *ob) { ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluidsim); @@ -205,122 +154,99 @@ static ModifierData *get_liquid_sim_modifier(Scene *scene, Object *ob) } } - return NULL; + return nullptr; } -/* ************************************************************************** */ - -AbcGenericMeshWriter::AbcGenericMeshWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings) - : AbcObjectWriter(ob, time_sampling, settings, parent) +bool ABCGenericMeshWriter::is_supported(const HierarchyContext *context) const { - 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; - } + Object *object = context->object; + bool is_dupli = context->duplicator != nullptr; + int base_flag; - if (!m_settings.apply_subdiv) { - m_subsurf_mod = get_subsurf_modifier(m_settings.scene, m_object); - m_is_subd = (m_subsurf_mod != NULL); + if (is_dupli) { + /* Construct the object's base flags from its dupli-parent, just like is done in + * deg_objects_dupli_iterator_next(). Without this, the visibility check below will fail. Doing + * this here, instead of a more suitable location in AbstractHierarchyIterator, prevents + * copying the Object for every dupli. */ + base_flag = object->base_flag; + object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI; } - m_is_liquid = (get_liquid_sim_modifier(m_settings.scene, m_object) != NULL); + int visibility = BKE_object_visibility( + object, DAG_EVAL_RENDER /* TODO(Sybren): add evaluation mode to export options? */); - while (parent->alembicXform().getChildHeader(m_name)) { - m_name.append("_"); + if (is_dupli) { + object->base_flag = base_flag; } - 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); - } + return (visibility & OB_VISIBLE_SELF) != 0; } -AbcGenericMeshWriter::~AbcGenericMeshWriter() +void ABCGenericMeshWriter::do_write(HierarchyContext &context) { - if (m_subsurf_mod) { - m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary; - } -} + Object *object = context.object; + bool needsfree = false; -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; - } + Mesh *mesh = get_export_mesh(object, needsfree); - /* Test modifiers. */ - ModifierData *md = static_cast<ModifierData *>(m_object->modifiers.first); - while (md) { + if (mesh == nullptr) { + return; + } - if (md->type != eModifierType_Subsurf) { - return true; - } + if (args_.export_params->triangulate) { + const bool tag_only = false; + const int quad_method = args_.export_params->quad_method; + const int ngon_method = args_.export_params->ngon_method; - md = md->next; - } + struct BMeshCreateParams bmcp = {false}; + struct BMeshFromMeshParams bmfmp = {true, false, false, 0}; + BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmcp, &bmfmp); - return false; -} + BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr); -void AbcGenericMeshWriter::setIsAnimated(bool is_animated) -{ - m_is_animated = is_animated; -} + Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); + BM_mesh_free(bm); -void AbcGenericMeshWriter::do_write() -{ - /* We have already stored a sample for this object. */ - if (!m_first_frame && !m_is_animated) { - return; + if (needsfree) { + free_export_mesh(mesh); + } + mesh = triangulated_mesh; + needsfree = true; } - bool needsfree; - struct Mesh *mesh = getFinalMesh(needsfree); + m_custom_data_config.pack_uvs = args_.export_params->packuv; + m_custom_data_config.mpoly = mesh->mpoly; + m_custom_data_config.mloop = mesh->mloop; + m_custom_data_config.totpoly = mesh->totpoly; + m_custom_data_config.totloop = mesh->totloop; + m_custom_data_config.totvert = mesh->totvert; try { - if (m_settings.use_subdiv_schema && m_subdiv_schema.valid()) { - writeSubD(mesh); + if (is_subd_) { + write_subd(context, mesh); } else { - writeMesh(mesh); + write_mesh(context, mesh); } if (needsfree) { - freeEvaluatedMesh(mesh); + free_export_mesh(mesh); } } catch (...) { if (needsfree) { - freeEvaluatedMesh(mesh); + free_export_mesh(mesh); } throw; } } -void AbcGenericMeshWriter::freeEvaluatedMesh(struct Mesh *mesh) +void ABCGenericMeshWriter::free_export_mesh(Mesh *mesh) { - BKE_id_free(NULL, mesh); + BKE_id_free(nullptr, mesh); } -void AbcGenericMeshWriter::writeMesh(struct Mesh *mesh) +void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) { std::vector<Imath::V3f> points, normals; std::vector<int32_t> poly_verts, loop_counts; @@ -330,32 +256,33 @@ void AbcGenericMeshWriter::writeMesh(struct Mesh *mesh) 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); + if (!frame_has_been_written_ && args_.export_params->face_sets) { + write_face_sets(context.object, mesh, abc_poly_mesh_schema_); } - m_mesh_sample = OPolyMeshSchema::Sample( + OPolyMeshSchema::Sample 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); + UVSample uvs_and_indices; - if (!sample.indices.empty() && !sample.uvs.empty()) { + if (!frame_has_been_written_ && args_.export_params->uvs) { + const char *name = get_uv_sample(uvs_and_indices, m_custom_data_config, &mesh->ldata); + + if (!uvs_and_indices.indices.empty() && !uvs_and_indices.uvs.empty()) { OV2fGeomParam::Sample uv_sample; - uv_sample.setVals(V2fArraySample(sample.uvs)); - uv_sample.setIndices(UInt32ArraySample(sample.indices)); + uv_sample.setVals(V2fArraySample(uvs_and_indices.uvs)); + uv_sample.setIndices(UInt32ArraySample(uvs_and_indices.indices)); uv_sample.setScope(kFacevaryingScope); - m_mesh_schema.setUVSourceName(name); - m_mesh_sample.setUVs(uv_sample); + abc_poly_mesh_schema_.setUVSourceName(name); + mesh_sample.setUVs(uv_sample); } write_custom_data( - m_mesh_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); } - if (m_settings.export_normals) { + if (args_.export_params->normals) { get_loop_normals(mesh, normals, has_flat_shaded_poly); ON3fGeomParam::Sample normals_sample; @@ -364,22 +291,23 @@ void AbcGenericMeshWriter::writeMesh(struct Mesh *mesh) normals_sample.setVals(V3fArraySample(normals)); } - m_mesh_sample.setNormals(normals_sample); + mesh_sample.setNormals(normals_sample); } - if (m_is_liquid) { - getVelocities(mesh, velocities); - m_mesh_sample.setVelocities(V3fArraySample(velocities)); + if (liquid_sim_modifier_ != nullptr) { + get_velocities(mesh, velocities); + mesh_sample.setVelocities(V3fArraySample(velocities)); } - m_mesh_sample.setSelfBounds(bounds()); + update_bounding_box(context.object); + mesh_sample.setSelfBounds(bounding_box_); - m_mesh_schema.set(m_mesh_sample); + abc_poly_mesh_schema_.set(mesh_sample); - writeArbGeoParams(mesh); + write_arb_geo_params(mesh); } -void AbcGenericMeshWriter::writeSubD(struct Mesh *mesh) +void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *mesh) { std::vector<float> crease_sharpness; std::vector<Imath::V3f> points; @@ -391,15 +319,15 @@ void AbcGenericMeshWriter::writeSubD(struct Mesh *mesh) 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); + if (!frame_has_been_written_ && args_.export_params->face_sets) { + write_face_sets(context.object, mesh, abc_subdiv_schema_); } - m_subdiv_sample = OSubDSchema::Sample( + OSubDSchema::Sample subdiv_sample = OSubDSchema::Sample( V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); UVSample sample; - if (m_first_frame && m_settings.export_uvs) { + if (!frame_has_been_written_ && args_.export_params->uvs) { const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); if (!sample.indices.empty() && !sample.uvs.empty()) { @@ -408,30 +336,32 @@ void AbcGenericMeshWriter::writeSubD(struct Mesh *mesh) uv_sample.setIndices(UInt32ArraySample(sample.indices)); uv_sample.setScope(kFacevaryingScope); - m_subdiv_schema.setUVSourceName(name); - m_subdiv_sample.setUVs(uv_sample); + abc_subdiv_schema_.setUVSourceName(name); + subdiv_sample.setUVs(uv_sample); } write_custom_data( - m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + abc_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)); + subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices)); + subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths)); + subdiv_sample.setCreaseSharpnesses(FloatArraySample(crease_sharpness)); } - m_subdiv_sample.setSelfBounds(bounds()); - m_subdiv_schema.set(m_subdiv_sample); + update_bounding_box(context.object); + subdiv_sample.setSelfBounds(bounding_box_); + abc_subdiv_schema_.set(subdiv_sample); - writeArbGeoParams(mesh); + write_arb_geo_params(mesh); } -template<typename Schema> void AbcGenericMeshWriter::writeFaceSets(struct Mesh *me, Schema &schema) +template<typename Schema> +void ABCGenericMeshWriter::write_face_sets(Object *object, struct Mesh *mesh, Schema &schema) { std::map<std::string, std::vector<int32_t>> geo_groups; - getGeoGroups(me, geo_groups); + get_geo_groups(object, mesh, geo_groups); std::map<std::string, std::vector<int32_t>>::iterator it; for (it = geo_groups.begin(); it != geo_groups.end(); ++it) { @@ -442,83 +372,35 @@ template<typename Schema> void AbcGenericMeshWriter::writeFaceSets(struct Mesh * } } -Mesh *AbcGenericMeshWriter::getFinalMesh(bool &r_needsfree) +void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me) { - /* 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; + if (liquid_sim_modifier_ != nullptr) { + /* We don't need anything more for liquid meshes. */ + return; } - 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. */ + if (frame_has_been_written_ || !args_.export_params->vcolors) { 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); - } + OCompoundProperty arb_geom_params; + if (is_subd_) { + arb_geom_params = abc_subdiv_.getSchema().getArbGeomParams(); + } + else { + arb_geom_params = abc_poly_mesh_.getSchema().getArbGeomParams(); } + write_custom_data(arb_geom_params, m_custom_data_config, &me->ldata, CD_MLOOPCOL); } -void AbcGenericMeshWriter::getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) +void ABCGenericMeshWriter::get_velocities(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); + FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(liquid_sim_modifier_); FluidsimSettings *fss = fmd->fss; if (fss->meshVelocities) { @@ -534,8 +416,9 @@ void AbcGenericMeshWriter::getVelocities(struct Mesh *mesh, std::vector<Imath::V } } -void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh, - std::map<std::string, std::vector<int32_t>> &geo_groups) +void ABCGenericMeshWriter::get_geo_groups(Object *object, + struct Mesh *mesh, + std::map<std::string, std::vector<int32_t>> &geo_groups) { const int num_poly = mesh->totpoly; MPoly *polygons = mesh->mpoly; @@ -544,13 +427,13 @@ void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh, MPoly ¤t_poly = polygons[i]; short mnr = current_poly.mat_nr; - Material *mat = BKE_object_material_get(m_object, mnr + 1); + Material *mat = BKE_object_material_get(object, mnr + 1); if (!mat) { continue; } - std::string name = get_id_name(&mat->id); + std::string name = args_.hierarchy_iterator->get_id_name(&mat->id); if (geo_groups.find(name) == geo_groups.end()) { std::vector<int32_t> faceArray; @@ -561,9 +444,9 @@ void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh, } if (geo_groups.size() == 0) { - Material *mat = BKE_object_material_get(m_object, 1); + Material *mat = BKE_object_material_get(object, 1); - std::string name = (mat) ? get_id_name(&mat->id) : "default"; + std::string name = (mat) ? args_.hierarchy_iterator->get_id_name(&mat->id) : "default"; std::vector<int32_t> faceArray; @@ -575,23 +458,114 @@ void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh, } } -AbcMeshWriter::AbcMeshWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings) - : AbcGenericMeshWriter(ob, parent, time_sampling, settings) +/* 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 != nullptr || !"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]); + } + } } -AbcMeshWriter::~AbcMeshWriter() +ABCMeshWriter::ABCMeshWriter(const ABCWriterConstructorArgs &args) : ABCGenericMeshWriter(args) { } -Mesh *AbcMeshWriter::getEvaluatedMesh(Scene *scene_eval, - Object *ob_eval, - bool &UNUSED(r_needsfree)) +Mesh *ABCMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/) { - return mesh_get_eval_final(m_settings.depsgraph, scene_eval, ob_eval, &CD_MASK_MESH); + return BKE_object_get_evaluated_mesh(object_eval); } } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.h b/source/blender/io/alembic/exporter/abc_writer_mesh.h index 412c5530b32..3a592255a70 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.h +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.h @@ -19,75 +19,75 @@ * \ingroup balembic */ -#include "abc_writer_object.h" +#include "abc_writer_abstract.h" #include "intern/abc_customdata.h" -struct Mesh; +#include <Alembic/AbcGeom/OPolyMesh.h> +#include <Alembic/AbcGeom/OSubD.h> + struct ModifierData; namespace blender { namespace io { namespace alembic { -/* Writer for Alembic meshes. Does not assume the object is a mesh object. */ -class AbcGenericMeshWriter : public AbcObjectWriter { - protected: - Alembic::AbcGeom::OPolyMeshSchema m_mesh_schema; - Alembic::AbcGeom::OPolyMeshSchema::Sample m_mesh_sample; - - Alembic::AbcGeom::OSubDSchema m_subdiv_schema; - Alembic::AbcGeom::OSubDSchema::Sample m_subdiv_sample; +/* Writer for Alembic geometry. Does not assume the object is a mesh object. */ +class ABCGenericMeshWriter : public ABCAbstractWriter { + private: + /* Either polymesh or subd is used, depending on is_subd_. + * References to the schema must be kept, or Alembic will not properly write. */ + Alembic::AbcGeom::OPolyMesh abc_poly_mesh_; + Alembic::AbcGeom::OPolyMeshSchema abc_poly_mesh_schema_; - Alembic::Abc::OArrayProperty m_mat_indices; + Alembic::AbcGeom::OSubD abc_subdiv_; + Alembic::AbcGeom::OSubDSchema abc_subdiv_schema_; - bool m_is_animated; - ModifierData *m_subsurf_mod; + /* Determines whether a poly mesh or a subdivision surface is exported. + * The value is set by an export option but only true if there is a subsdivision modifier on the + * exported object. */ + bool is_subd_; + ModifierData *subsurf_modifier_; + ModifierData *liquid_sim_modifier_; CDStreamConfig m_custom_data_config; - bool m_is_liquid; - bool m_is_subd; - public: - AbcGenericMeshWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings); + explicit ABCGenericMeshWriter(const ABCWriterConstructorArgs &args); + virtual ~ABCGenericMeshWriter(); - ~AbcGenericMeshWriter(); - void setIsAnimated(bool is_animated); + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const; protected: - virtual void do_write(); - virtual bool isAnimated() const; - virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) = 0; - virtual void freeEvaluatedMesh(struct Mesh *mesh); + virtual bool is_supported(const HierarchyContext *context) const override; + virtual void do_write(HierarchyContext &context) override; - Mesh *getFinalMesh(bool &r_needsfree); + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0; + virtual void free_export_mesh(Mesh *mesh); - void writeMesh(struct Mesh *mesh); - void writeSubD(struct Mesh *mesh); + virtual bool export_as_subdivision_surface(Object *ob_eval) const; - void writeArbGeoParams(struct Mesh *mesh); - void getGeoGroups(struct Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geoGroups); + private: + void write_mesh(HierarchyContext &context, Mesh *mesh); + void write_subd(HierarchyContext &context, Mesh *mesh); + template<typename Schema> void write_face_sets(Object *object, Mesh *mesh, Schema &schema); - /* fluid surfaces support */ - void getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels); + ModifierData *get_liquid_sim_modifier(Scene *scene_eval, Object *ob_eval); - template<typename Schema> void writeFaceSets(struct Mesh *mesh, Schema &schema); + void write_arb_geo_params(Mesh *me); + void get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels); + void get_geo_groups(Object *object, + Mesh *mesh, + std::map<std::string, std::vector<int32_t>> &geo_groups); }; -class AbcMeshWriter : public AbcGenericMeshWriter { +/* Writer for Alembic geometry of Blender Mesh objects. */ +class ABCMeshWriter : public ABCGenericMeshWriter { public: - AbcMeshWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings); - - ~AbcMeshWriter(); + ABCMeshWriter(const ABCWriterConstructorArgs &args); protected: - virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override; + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; }; } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc index fa37c1eac59..1fd382214a6 100644 --- a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc +++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc @@ -19,7 +19,6 @@ */ #include "abc_writer_nurbs.h" -#include "abc_writer_transform.h" #include "intern/abc_axis_conversion.h" #include "DNA_curve_types.h" @@ -29,52 +28,70 @@ #include "BKE_curve.h" +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + +namespace blender { +namespace io { +namespace alembic { + +using Alembic::Abc::OObject; using Alembic::AbcGeom::FloatArraySample; using Alembic::AbcGeom::OBoolProperty; using Alembic::AbcGeom::OCompoundProperty; using Alembic::AbcGeom::ONuPatch; using Alembic::AbcGeom::ONuPatchSchema; -namespace blender { -namespace io { -namespace alembic { +ABCNurbsWriter::ABCNurbsWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args) +{ +} -AbcNurbsWriter::AbcNurbsWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings) - : AbcObjectWriter(ob, time_sampling, settings, parent) +void ABCNurbsWriter::create_alembic_objects(const HierarchyContext *context) { - m_is_animated = isAnimated(); + Curve *curve = static_cast<Curve *>(context->object->data); + size_t num_nurbs = BLI_listbase_count(&curve->nurb); + OObject abc_parent = args_.abc_parent; + const char *abc_parent_path = abc_parent.getFullName().c_str(); - /* if the object is static, use the default static time sampling */ - if (!m_is_animated) { - m_time_sampling = 0; - } + for (size_t i = 0; i < num_nurbs; i++) { + std::stringstream patch_name_stream; + patch_name_stream << args_.abc_name << '_' << i; - Curve *curve = static_cast<Curve *>(m_object->data); - size_t numNurbs = BLI_listbase_count(&curve->nurb); + while (abc_parent.getChildHeader(patch_name_stream.str())) { + patch_name_stream << "_"; + } - for (size_t i = 0; i < numNurbs; i++) { - std::stringstream str; - str << m_name << '_' << i; + std::string patch_name = patch_name_stream.str(); + CLOG_INFO(&LOG, 2, "exporting %s/%s", abc_parent_path, patch_name.c_str()); - while (parent->alembicXform().getChildHeader(str.str())) { - str << "_"; - } + ONuPatch nurbs(abc_parent, patch_name.c_str(), timesample_index_); + abc_nurbs_.push_back(nurbs); + abc_nurbs_schemas_.push_back(nurbs.getSchema()); + } +} - ONuPatch nurbs(parent->alembicXform(), str.str().c_str(), m_time_sampling); - m_nurbs_schema.push_back(nurbs.getSchema()); +const OObject ABCNurbsWriter::get_alembic_object() const +{ + if (abc_nurbs_.empty()) { + return OObject(); } + /* For parenting purposes within the Alembic file, all NURBS patches are equal, so just use the + * first one. */ + return abc_nurbs_[0]; } -bool AbcNurbsWriter::isAnimated() const +bool ABCNurbsWriter::check_is_animated(const HierarchyContext &context) const { - /* check if object has shape keys */ - Curve *cu = static_cast<Curve *>(m_object->data); + /* Check if object has shape keys. */ + Curve *cu = static_cast<Curve *>(context.object->data); return (cu->key != NULL); } +bool ABCNurbsWriter::is_supported(const HierarchyContext *context) const +{ + return ELEM(context->object->type, OB_SURF, OB_CURVE); +} + static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_knots) { if (num_knots <= 1) { @@ -95,22 +112,13 @@ static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_ knots.push_back(2.0f * knots[num_knots] - knots[num_knots - 1]); } -void AbcNurbsWriter::do_write() +void ABCNurbsWriter::do_write(HierarchyContext &context) { - /* we have already stored a sample for this object. */ - if (!m_first_frame && !m_is_animated) { - return; - } - - if (!ELEM(m_object->type, OB_SURF, OB_CURVE)) { - return; - } - - Curve *curve = static_cast<Curve *>(m_object->data); + Curve *curve = static_cast<Curve *>(context.object->data); ListBase *nulb; - if (m_object->runtime.curve_cache->deformed_nurbs.first != NULL) { - nulb = &m_object->runtime.curve_cache->deformed_nurbs; + if (context.object->runtime.curve_cache->deformed_nurbs.first != NULL) { + nulb = &context.object->runtime.curve_cache->deformed_nurbs; } else { nulb = BKE_curve_nurbs_get(curve); @@ -147,7 +155,7 @@ void AbcNurbsWriter::do_write() /* TODO(kevin): to accommodate other software we should duplicate control * points to indicate that a NURBS is cyclic. */ - OCompoundProperty user_props = m_nurbs_schema[count].getUserProperties(); + OCompoundProperty user_props = abc_nurbs_schemas_[count].getUserProperties(); if ((nu->flagu & CU_NURB_ENDPOINT) != 0) { OBoolProperty prop(user_props, "endpoint_u"); @@ -169,7 +177,7 @@ void AbcNurbsWriter::do_write() prop.set(true); } - m_nurbs_schema[count].set(sample); + abc_nurbs_schemas_[count].set(sample); } } diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.h b/source/blender/io/alembic/exporter/abc_writer_nurbs.h index b2a9cf1b786..23af4c40556 100644 --- a/source/blender/io/alembic/exporter/abc_writer_nurbs.h +++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.h @@ -19,26 +19,37 @@ * \ingroup balembic */ -#include "abc_writer_object.h" +#include "abc_writer_abstract.h" +#include "abc_writer_mesh.h" +#include <vector> namespace blender { namespace io { namespace alembic { -class AbcNurbsWriter : public AbcObjectWriter { - std::vector<Alembic::AbcGeom::ONuPatchSchema> m_nurbs_schema; - bool m_is_animated; +class ABCNurbsWriter : public ABCAbstractWriter { + private: + std::vector<Alembic::AbcGeom::ONuPatch> abc_nurbs_; + std::vector<Alembic::AbcGeom::ONuPatchSchema> abc_nurbs_schemas_; public: - AbcNurbsWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings); + explicit ABCNurbsWriter(const ABCWriterConstructorArgs &args); - private: - virtual void do_write(); + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; + + protected: + virtual bool is_supported(const HierarchyContext *context) const override; + virtual void do_write(HierarchyContext &context) override; + virtual bool check_is_animated(const HierarchyContext &context) const override; +}; + +class ABCNurbsMeshWriter : public ABCGenericMeshWriter { + public: + explicit ABCNurbsMeshWriter(const ABCWriterConstructorArgs &args); - bool isAnimated() const; + protected: + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; }; } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_object.cc b/source/blender/io/alembic/exporter/abc_writer_object.cc deleted file mode 100644 index 97cac3a1af9..00000000000 --- a/source/blender/io/alembic/exporter/abc_writer_object.cc +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup balembic - */ - -#include "abc_writer_object.h" - -#include "DNA_object_types.h" - -#include "BKE_object.h" - -namespace blender { -namespace io { -namespace alembic { - -AbcObjectWriter::AbcObjectWriter(Object *ob, - uint32_t time_sampling, - ExportSettings &settings, - AbcObjectWriter *parent) - : m_object(ob), m_settings(settings), m_time_sampling(time_sampling), m_first_frame(true) -{ - /* This class is used as superclass for objects themselves (i.e. transforms) and for object - * data (meshes, curves, cameras, etc.). However, when writing transforms, the m_name field is - * ignored. This is a temporary tweak to get the exporter to write object data with the data - * name instead of the object name in a safe way. */ - if (m_object->data == nullptr) { - m_name = get_id_name(m_object); - } - else { - ID *ob_data = static_cast<ID *>(m_object->data); - m_name = get_id_name(ob_data); - } - - if (parent) { - parent->addChild(this); - } -} - -AbcObjectWriter::~AbcObjectWriter() -{ -} - -void AbcObjectWriter::addChild(AbcObjectWriter *child) -{ - m_children.push_back(child); -} - -Imath::Box3d AbcObjectWriter::bounds() -{ - BoundBox *bb = BKE_object_boundbox_get(this->m_object); - - if (!bb) { - if (this->m_object->type != OB_CAMERA) { - ABC_LOG(m_settings.logger) << "Bounding box is null!\n"; - } - - return Imath::Box3d(); - } - - /* Convert Z-up to Y-up. This also changes which vector goes into which min/max property. */ - this->m_bounds.min.x = bb->vec[0][0]; - this->m_bounds.min.y = bb->vec[0][2]; - this->m_bounds.min.z = -bb->vec[6][1]; - - this->m_bounds.max.x = bb->vec[6][0]; - this->m_bounds.max.y = bb->vec[6][2]; - this->m_bounds.max.z = -bb->vec[0][1]; - - return this->m_bounds; -} - -void AbcObjectWriter::write() -{ - do_write(); - m_first_frame = false; -} - -} // namespace alembic -} // namespace io -} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_object.h b/source/blender/io/alembic/exporter/abc_writer_object.h deleted file mode 100644 index 7f8e8735812..00000000000 --- a/source/blender/io/alembic/exporter/abc_writer_object.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -#pragma once - -/** \file - * \ingroup balembic - */ - -#include <Alembic/Abc/All.h> -#include <Alembic/AbcGeom/All.h> - -#include "abc_exporter.h" - -#include "DNA_ID.h" - -struct Main; -struct Object; - -namespace blender { -namespace io { -namespace alembic { - -class AbcTransformWriter; - -class AbcObjectWriter { - protected: - Object *m_object; - ExportSettings &m_settings; - - uint32_t m_time_sampling; - - Imath::Box3d m_bounds; - std::vector<AbcObjectWriter *> m_children; - - std::vector<std::pair<std::string, IDProperty *>> m_props; - - bool m_first_frame; - std::string m_name; - - public: - AbcObjectWriter(Object *ob, - uint32_t time_sampling, - ExportSettings &settings, - AbcObjectWriter *parent = NULL); - - virtual ~AbcObjectWriter(); - - void addChild(AbcObjectWriter *child); - - virtual Imath::Box3d bounds(); - - void write(); - - private: - virtual void do_write() = 0; -}; - -} // namespace alembic -} // namespace io -} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_points.cc b/source/blender/io/alembic/exporter/abc_writer_points.cc index 4d7f5d11c36..19870e39a90 100644 --- a/source/blender/io/alembic/exporter/abc_writer_points.cc +++ b/source/blender/io/alembic/exporter/abc_writer_points.cc @@ -22,9 +22,6 @@ */ #include "abc_writer_points.h" -#include "abc_writer_mesh.h" -#include "abc_writer_transform.h" -#include "intern/abc_util.h" #include "DNA_object_types.h" #include "DNA_particle_types.h" @@ -36,81 +33,102 @@ #include "DEG_depsgraph_query.h" +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + +namespace blender { +namespace io { +namespace alembic { + using Alembic::AbcGeom::kVertexScope; using Alembic::AbcGeom::OPoints; using Alembic::AbcGeom::OPointsSchema; -/* ************************************************************************** */ +ABCPointsWriter::ABCPointsWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args) +{ +} -namespace blender { -namespace io { -namespace alembic { +void ABCPointsWriter::create_alembic_objects(const HierarchyContext * /*context*/) +{ + CLOG_INFO(&LOG, 2, "exporting OPoints %s", args_.abc_path.c_str()); + abc_points_ = OPoints(args_.abc_parent, args_.abc_name, timesample_index_); + abc_points_schema_ = abc_points_.getSchema(); +} -AbcPointsWriter::AbcPointsWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings, - ParticleSystem *psys) - : AbcObjectWriter(ob, time_sampling, settings, parent) +const Alembic::Abc::OObject ABCPointsWriter::get_alembic_object() const { - m_psys = psys; + return abc_points_; +} - std::string psys_name = get_valid_abc_name(psys->name); - OPoints points(parent->alembicXform(), psys_name, m_time_sampling); - m_schema = points.getSchema(); +bool ABCPointsWriter::is_supported(const HierarchyContext *context) const +{ + return ELEM(context->particle_system->part->type, + PART_EMITTER, + PART_FLUID_FLIP, + PART_FLUID_SPRAY, + PART_FLUID_BUBBLE, + PART_FLUID_FOAM, + PART_FLUID_TRACER, + PART_FLUID_SPRAYFOAM, + PART_FLUID_SPRAYBUBBLE, + PART_FLUID_FOAMBUBBLE, + PART_FLUID_SPRAYFOAMBUBBLE); } -void AbcPointsWriter::do_write() +bool ABCPointsWriter::check_is_animated(const HierarchyContext & /*context*/) const { - if (!m_psys) { - return; - } + /* We assume that particles are always animated. */ + return true; +} + +void ABCPointsWriter::do_write(HierarchyContext &context) +{ + BLI_assert(context.particle_system != nullptr); std::vector<Imath::V3f> points; std::vector<Imath::V3f> velocities; std::vector<float> widths; std::vector<uint64_t> ids; + ParticleSystem *psys = context.particle_system; ParticleKey state; - ParticleSimulationData sim; - sim.depsgraph = m_settings.depsgraph; - sim.scene = m_settings.scene; - sim.ob = m_object; - sim.psys = m_psys; + sim.depsgraph = args_.depsgraph; + sim.scene = DEG_get_evaluated_scene(args_.depsgraph); + sim.ob = context.object; + sim.psys = psys; - m_psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); uint64_t index = 0; - for (int p = 0; p < m_psys->totpart; p++) { + for (int p = 0; p < psys->totpart; p++) { float pos[3], vel[3]; - if (m_psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) { + if (psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) { continue; } - state.time = DEG_get_ctime(m_settings.depsgraph); - + state.time = DEG_get_ctime(args_.depsgraph); if (psys_get_particle_state(&sim, p, &state, 0) == 0) { continue; } /* location */ - mul_v3_m4v3(pos, m_object->imat, state.co); + mul_v3_m4v3(pos, context.object->imat, state.co); /* velocity */ - sub_v3_v3v3(vel, state.co, m_psys->particles[p].prev_state.co); + sub_v3_v3v3(vel, state.co, psys->particles[p].prev_state.co); /* Convert Z-up to Y-up. */ points.push_back(Imath::V3f(pos[0], pos[2], -pos[1])); velocities.push_back(Imath::V3f(vel[0], vel[2], -vel[1])); - widths.push_back(m_psys->particles[p].size); + widths.push_back(psys->particles[p].size); ids.push_back(index++); } - if (m_psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(m_psys->lattice_deform_data); - m_psys->lattice_deform_data = NULL; + if (psys->lattice_deform_data) { + BKE_lattice_deform_data_destroy(psys->lattice_deform_data); + psys->lattice_deform_data = NULL; } Alembic::Abc::P3fArraySample psample(points); @@ -119,10 +137,10 @@ void AbcPointsWriter::do_write() Alembic::Abc::FloatArraySample wsample_array(widths); Alembic::AbcGeom::OFloatGeomParam::Sample wsample(wsample_array, kVertexScope); - m_sample = OPointsSchema::Sample(psample, idsample, vsample, wsample); - m_sample.setSelfBounds(bounds()); - - m_schema.set(m_sample); + OPointsSchema::Sample sample(psample, idsample, vsample, wsample); + update_bounding_box(context.object); + sample.setSelfBounds(bounding_box_); + abc_points_schema_.set(sample); } } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_points.h b/source/blender/io/alembic/exporter/abc_writer_points.h index 7e8a3eaadcd..03800b80acf 100644 --- a/source/blender/io/alembic/exporter/abc_writer_points.h +++ b/source/blender/io/alembic/exporter/abc_writer_points.h @@ -22,30 +22,29 @@ * \ingroup balembic */ -#include "abc_writer_object.h" -#include "intern/abc_customdata.h" +#include "abc_writer_abstract.h" -struct ParticleSystem; - -/* ************************************************************************** */ +#include <Alembic/AbcGeom/OPoints.h> namespace blender { namespace io { namespace alembic { -class AbcPointsWriter : public AbcObjectWriter { - Alembic::AbcGeom::OPointsSchema m_schema; - Alembic::AbcGeom::OPointsSchema::Sample m_sample; - ParticleSystem *m_psys; +class ABCPointsWriter : public ABCAbstractWriter { + Alembic::AbcGeom::OPoints abc_points_; + Alembic::AbcGeom::OPointsSchema abc_points_schema_; public: - AbcPointsWriter(Object *ob, - AbcTransformWriter *parent, - uint32_t time_sampling, - ExportSettings &settings, - ParticleSystem *psys); + explicit ABCPointsWriter(const ABCWriterConstructorArgs &args); + + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; + + virtual bool is_supported(const HierarchyContext *context) const override; - void do_write(); + protected: + virtual bool check_is_animated(const HierarchyContext &context) const override; + virtual void do_write(HierarchyContext &context) override; }; } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.cc b/source/blender/io/alembic/exporter/abc_writer_transform.cc index fa808c1b75b..65d6b7c5b41 100644 --- a/source/blender/io/alembic/exporter/abc_writer_transform.cc +++ b/source/blender/io/alembic/exporter/abc_writer_transform.cc @@ -19,112 +19,95 @@ */ #include "abc_writer_transform.h" +#include "abc_hierarchy_iterator.h" #include "intern/abc_axis_conversion.h" +#include "intern/abc_util.h" -#include <OpenEXR/ImathBoxAlgo.h> +#include "BKE_object.h" -#include "DNA_object_types.h" +#include "BLI_math_matrix.h" +#include "BLI_math_rotation.h" -#include "BLI_math.h" +#include "DNA_layer_types.h" -#include "DEG_depsgraph_query.h" - -using Alembic::AbcGeom::OObject; -using Alembic::AbcGeom::OXform; +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; namespace blender { namespace io { namespace alembic { -AbcTransformWriter::AbcTransformWriter(Object *ob, - const OObject &abc_parent, - AbcTransformWriter *parent, - unsigned int time_sampling, - ExportSettings &settings) - : AbcObjectWriter(ob, time_sampling, settings, parent), m_proxy_from(NULL) -{ - 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(); +using Alembic::Abc::OObject; +using Alembic::AbcGeom::OXform; +using Alembic::AbcGeom::OXformSchema; +using Alembic::AbcGeom::XformSample; - /* Blender objects can't have a parent without inheriting the transform. */ - m_inherits_xform = parent != NULL; +ABCTransformWriter::ABCTransformWriter(const ABCWriterConstructorArgs &args) + : ABCAbstractWriter(args) +{ + timesample_index_ = args_.abc_archive->time_sampling_index_transforms(); } -void AbcTransformWriter::do_write() +void ABCTransformWriter::create_alembic_objects(const HierarchyContext * /*context*/) { - Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object); - - if (m_first_frame) { - m_visibility = Alembic::AbcGeom::CreateVisibilityProperty( - m_xform, m_xform.getSchema().getTimeSampling()); - } - - m_visibility.set(!(ob_eval->restrictflag & OB_RESTRICT_VIEWPORT)); + CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str()); + abc_xform_ = OXform(args_.abc_parent, args_.abc_name, timesample_index_); + abc_xform_schema_ = abc_xform_.getSchema(); +} - if (!m_first_frame && !m_is_animated) { - return; - } +void ABCTransformWriter::do_write(HierarchyContext &context) +{ + float parent_relative_matrix[4][4]; // The object matrix relative to the parent. + mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world); - float yup_mat[4][4]; - create_transform_matrix( - ob_eval, yup_mat, m_inherits_xform ? ABC_MATRIX_LOCAL : ABC_MATRIX_WORLD, m_proxy_from); + // After this, parent_relative_matrix uses Y=up. + copy_m44_axis_swap(parent_relative_matrix, parent_relative_matrix, ABC_YUP_FROM_ZUP); /* If the parent is a camera, undo its to-Maya rotation (see below). */ - bool is_root_object = !m_inherits_xform || ob_eval->parent == nullptr; - if (!is_root_object && ob_eval->parent->type == OB_CAMERA) { + bool is_root_object = context.export_parent == nullptr; + if (!is_root_object && context.export_parent->type == OB_CAMERA) { float rot_mat[4][4]; axis_angle_to_mat4_single(rot_mat, 'X', M_PI_2); - mul_m4_m4m4(yup_mat, rot_mat, yup_mat); + mul_m4_m4m4(parent_relative_matrix, rot_mat, parent_relative_matrix); } /* If the object is a camera, apply an extra rotation to Maya camera orientation. */ - if (ob_eval->type == OB_CAMERA) { + if (context.object->type == OB_CAMERA) { float rot_mat[4][4]; axis_angle_to_mat4_single(rot_mat, 'X', -M_PI_2); - mul_m4_m4m4(yup_mat, yup_mat, rot_mat); + mul_m4_m4m4(parent_relative_matrix, parent_relative_matrix, rot_mat); } if (is_root_object) { /* Only apply scaling to root objects, parenting will propagate it. */ float scale_mat[4][4]; - scale_m4_fl(scale_mat, m_settings.global_scale); - scale_mat[3][3] = m_settings.global_scale; /* also scale translation */ - mul_m4_m4m4(yup_mat, yup_mat, scale_mat); - yup_mat[3][3] /= m_settings.global_scale; /* normalise the homogeneous component */ + scale_m4_fl(scale_mat, args_.export_params->global_scale); + scale_mat[3][3] = args_.export_params->global_scale; /* also scale translation */ + mul_m4_m4m4(parent_relative_matrix, parent_relative_matrix, scale_mat); + parent_relative_matrix[3][3] /= + args_.export_params->global_scale; /* normalise the homogeneous component */ } - m_matrix = convert_matrix_datatype(yup_mat); - m_sample.setMatrix(m_matrix); - - /* Always export as "inherits transform", as this is the only way in which Blender works. The - * above code has already taken care of writing the correct matrix so that this option is not - * necessary. However, certain packages (for example the USD Alembic exporter) are incompatible - * with non-inheriting transforms and will completely ignore the transform if that is used. */ - m_sample.setInheritsXforms(true); - m_schema.set(m_sample); + XformSample xform_sample; + xform_sample.setMatrix(convert_matrix_datatype(parent_relative_matrix)); + xform_sample.setInheritsXforms(true); + abc_xform_schema_.set(xform_sample); } -Imath::Box3d AbcTransformWriter::bounds() +const OObject ABCTransformWriter::get_alembic_object() const { - Imath::Box3d bounds; - - 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); + return abc_xform_; } -bool AbcTransformWriter::hasAnimation(Object * /*ob*/) const +bool ABCTransformWriter::check_is_animated(const HierarchyContext &context) const { - return true; + if (context.duplicator != NULL) { + /* This object is being duplicated, so could be emitted by a particle system and thus + * influenced by forces. TODO(Sybren): Make this more strict. Probably better to get from the + * depsgraph whether this object instance has a time source. */ + return true; + } + return BKE_object_moves_in_time(context.object, context.animation_check_include_parent); } } // namespace alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.h b/source/blender/io/alembic/exporter/abc_writer_transform.h index fc997d77f4c..950bff39c29 100644 --- a/source/blender/io/alembic/exporter/abc_writer_transform.h +++ b/source/blender/io/alembic/exporter/abc_writer_transform.h @@ -19,44 +19,27 @@ * \ingroup balembic */ -#include "abc_writer_object.h" +#include "abc_writer_abstract.h" -#include <Alembic/AbcGeom/All.h> +#include <Alembic/AbcGeom/OXform.h> namespace blender { namespace io { namespace alembic { -class AbcTransformWriter : public AbcObjectWriter { - Alembic::AbcGeom::OXform m_xform; - Alembic::AbcGeom::OXformSchema m_schema; - Alembic::AbcGeom::XformSample m_sample; - Alembic::AbcGeom::OVisibilityProperty m_visibility; - Alembic::Abc::M44d m_matrix; - - bool m_is_animated; - bool m_inherits_xform; - - public: - Object *m_proxy_from; +class ABCTransformWriter : public ABCAbstractWriter { + private: + Alembic::AbcGeom::OXform abc_xform_; + Alembic::AbcGeom::OXformSchema abc_xform_schema_; 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(); + explicit ABCTransformWriter(const ABCWriterConstructorArgs &args); + virtual void create_alembic_objects(const HierarchyContext *context) override; - bool hasAnimation(Object *ob) const; + protected: + virtual void do_write(HierarchyContext &context) override; + virtual bool check_is_animated(const HierarchyContext &context) const override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; }; } // namespace alembic diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index bca84b97749..5932791ecf4 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -70,36 +70,19 @@ #include "WM_api.h" #include "WM_types.h" -using Alembic::Abc::Int32ArraySamplePtr; using Alembic::Abc::ObjectHeader; - -using Alembic::AbcGeom::kWrapExisting; -using Alembic::AbcGeom::MetaData; -using Alembic::AbcGeom::P3fArraySamplePtr; - using Alembic::AbcGeom::ICamera; -using Alembic::AbcGeom::ICompoundProperty; using Alembic::AbcGeom::ICurves; -using Alembic::AbcGeom::ICurvesSchema; using Alembic::AbcGeom::IFaceSet; using Alembic::AbcGeom::ILight; -using Alembic::AbcGeom::IN3fArrayProperty; -using Alembic::AbcGeom::IN3fGeomParam; using Alembic::AbcGeom::INuPatch; using Alembic::AbcGeom::IObject; using Alembic::AbcGeom::IPoints; -using Alembic::AbcGeom::IPointsSchema; using Alembic::AbcGeom::IPolyMesh; -using Alembic::AbcGeom::IPolyMeshSchema; using Alembic::AbcGeom::ISampleSelector; using Alembic::AbcGeom::ISubD; -using Alembic::AbcGeom::IV2fGeomParam; using Alembic::AbcGeom::IXform; -using Alembic::AbcGeom::IXformSchema; -using Alembic::AbcGeom::N3fArraySamplePtr; -using Alembic::AbcGeom::V3fArraySamplePtr; -using Alembic::AbcGeom::XformSample; - +using Alembic::AbcGeom::MetaData; using Alembic::AbcMaterial::IMaterial; using namespace blender::io::alembic; diff --git a/source/blender/io/common/IO_abstract_hierarchy_iterator.h b/source/blender/io/common/IO_abstract_hierarchy_iterator.h index 2f0c1b270f1..5f84fd48b71 100644 --- a/source/blender/io/common/IO_abstract_hierarchy_iterator.h +++ b/source/blender/io/common/IO_abstract_hierarchy_iterator.h @@ -197,8 +197,8 @@ class AbstractHierarchyIterator { /* Iterate over the depsgraph, create writers, and tell the writers to write. * Main entry point for the AbstractHierarchyIterator, must be called for every to-be-exported - * frame. */ - void iterate_and_write(); + * (sub)frame. */ + virtual void iterate_and_write(); /* Release all writers. Call after all frames have been exported. */ void release_writers(); diff --git a/tests/gtests/alembic/CMakeLists.txt b/tests/gtests/alembic/CMakeLists.txt index 6ba1c4465d9..90f1829fc77 100644 --- a/tests/gtests/alembic/CMakeLists.txt +++ b/tests/gtests/alembic/CMakeLists.txt @@ -24,6 +24,8 @@ set(INC ../../../source/blender/blenlib ../../../source/blender/blenkernel ../../../source/blender/io/alembic + ../../../source/blender/io/common + ../../../source/blender/io/usd/intern ../../../source/blender/makesdna ../../../source/blender/depsgraph ${ALEMBIC_INCLUDE_DIRS} diff --git a/tests/gtests/alembic/abc_export_test.cc b/tests/gtests/alembic/abc_export_test.cc index 2926809a1af..cdc98178550 100644 --- a/tests/gtests/alembic/abc_export_test.cc +++ b/tests/gtests/alembic/abc_export_test.cc @@ -1,11 +1,12 @@ #include "testing/testing.h" // Keep first since utildefines defines AT which conflicts with STL -#include "exporter/abc_exporter.h" +#include "exporter/abc_archive.h" #include "intern/abc_util.h" extern "C" { #include "BKE_main.h" +#include "BLI_fileops.h" #include "BLI_math.h" #include "BLI_utildefines.h" #include "DNA_scene_types.h" @@ -13,140 +14,150 @@ extern "C" { #include "DEG_depsgraph.h" -using namespace blender::io::alembic; - -class TestableAbcExporter : public AbcExporter { - public: - TestableAbcExporter(Main *bmain, const char *filename, ExportSettings &settings) - : AbcExporter(bmain, filename, settings) - { - } - - void getShutterSamples(unsigned int nr_of_samples, - bool time_relative, - std::vector<double> &samples) - { - AbcExporter::getShutterSamples(nr_of_samples, time_relative, samples); - } - - void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames) - { - AbcExporter::getFrameSet(nr_of_samples, frames); - } -}; +namespace blender { +namespace io { +namespace alembic { class AlembicExportTest : public testing::Test { protected: - ExportSettings settings; + ABCArchive *abc_archive; + + AlembicExportParams params; Scene scene; Depsgraph *depsgraph; - TestableAbcExporter *exporter; Main *bmain; virtual void SetUp() { - settings.frame_start = 31.0; - settings.frame_end = 223.0; + abc_archive = nullptr; /* Fake a 25 FPS scene with a nonzero base (because that's sometimes forgotten) */ scene.r.frs_sec = 50; scene.r.frs_sec_base = 2; + strcpy(scene.id.name, "SCTestScene"); bmain = BKE_main_new(); /* TODO(sergey): Pass scene layer somehow? */ ViewLayer *view_layer = (ViewLayer *)scene.view_layers.first; - settings.depsgraph = depsgraph = DEG_graph_new(bmain, &scene, view_layer, DAG_EVAL_VIEWPORT); - - settings.scene = &scene; - settings.view_layer = view_layer; - - exporter = NULL; + depsgraph = DEG_graph_new(bmain, &scene, view_layer, DAG_EVAL_RENDER); } virtual void TearDown() { BKE_main_free(bmain); DEG_graph_free(depsgraph); - delete exporter; + deleteArchive(); + } + + // Call after setting up the parameters. + void createArchive() + { + if (abc_archive != nullptr) { + deleteArchive(); + } + abc_archive = new ABCArchive(bmain, &scene, params, "somefile.abc"); } - // Call after setting up the settings. - void createExporter() + void deleteArchive() { - exporter = new TestableAbcExporter(bmain, "somefile.abc", settings); + delete abc_archive; + if (BLI_exists("somefile.abc")) { + BLI_delete("somefile.abc", false, false); + } + abc_archive = nullptr; } }; -TEST_F(AlembicExportTest, TimeSamplesFullShutter) +TEST_F(AlembicExportTest, TimeSamplesFullShutterUniform) { - settings.shutter_open = 0.0; - settings.shutter_close = 1.0; - - createExporter(); - std::vector<double> samples; - - /* test 5 samples per frame */ - exporter->getShutterSamples(5, true, samples); - EXPECT_EQ(5, samples.size()); - EXPECT_NEAR(1.240, samples[0], 1e-5f); - EXPECT_NEAR(1.248, samples[1], 1e-5f); - EXPECT_NEAR(1.256, samples[2], 1e-5f); - EXPECT_NEAR(1.264, samples[3], 1e-5f); - EXPECT_NEAR(1.272, samples[4], 1e-5f); - - /* test same, but using frame number offset instead of time */ - exporter->getShutterSamples(5, false, samples); - EXPECT_EQ(5, samples.size()); - EXPECT_NEAR(0.0, samples[0], 1e-5f); - EXPECT_NEAR(0.2, samples[1], 1e-5f); - EXPECT_NEAR(0.4, samples[2], 1e-5f); - EXPECT_NEAR(0.6, samples[3], 1e-5f); - EXPECT_NEAR(0.8, samples[4], 1e-5f); - - /* use the same setup to test getFrameSet() */ - std::set<double> frames; - exporter->getFrameSet(5, frames); - EXPECT_EQ(965, frames.size()); - EXPECT_EQ(1, frames.count(31.0)); - EXPECT_EQ(1, frames.count(31.2)); - EXPECT_EQ(1, frames.count(31.4)); - EXPECT_EQ(1, frames.count(31.6)); - EXPECT_EQ(1, frames.count(31.8)); + /* Test 5 samples per frame, for 2 frames. */ + params.shutter_open = 0.0; + params.shutter_close = 1.0; + params.frame_start = 31.0; + params.frame_end = 32.0; + params.frame_samples_xform = params.frame_samples_shape = 5; + createArchive(); + std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end()); + EXPECT_EQ(10, frames.size()); + EXPECT_NEAR(31.0, frames[0], 1e-5); + EXPECT_NEAR(31.2, frames[1], 1e-5); + EXPECT_NEAR(31.4, frames[2], 1e-5); + EXPECT_NEAR(31.6, frames[3], 1e-5); + EXPECT_NEAR(31.8, frames[4], 1e-5); + EXPECT_NEAR(32.0, frames[5], 1e-5); + EXPECT_NEAR(32.2, frames[6], 1e-5); + EXPECT_NEAR(32.4, frames[7], 1e-5); + EXPECT_NEAR(32.6, frames[8], 1e-5); + EXPECT_NEAR(32.8, frames[9], 1e-5); + + for (double frame : frames) { + EXPECT_TRUE(abc_archive->is_xform_frame(frame)); + EXPECT_TRUE(abc_archive->is_shape_frame(frame)); + } +} + +TEST_F(AlembicExportTest, TimeSamplesFullShutterDifferent) +{ + /* Test 3 samples per frame for transforms, and 2 per frame for shapes, for 2 frames. */ + params.shutter_open = 0.0; + params.shutter_close = 1.0; + params.frame_start = 31.0; + params.frame_end = 32.0; + params.frame_samples_xform = 3; + params.frame_samples_shape = 2; + createArchive(); + std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end()); + EXPECT_EQ(8, frames.size()); + EXPECT_NEAR(31.0, frames[0], 1e-5); // transform + shape + EXPECT_TRUE(abc_archive->is_xform_frame(frames[0])); + EXPECT_TRUE(abc_archive->is_shape_frame(frames[0])); + EXPECT_NEAR(31.33333, frames[1], 1e-5); // transform + EXPECT_TRUE(abc_archive->is_xform_frame(frames[1])); + EXPECT_FALSE(abc_archive->is_shape_frame(frames[1])); + EXPECT_NEAR(31.5, frames[2], 1e-5); // shape + EXPECT_FALSE(abc_archive->is_xform_frame(frames[2])); + EXPECT_TRUE(abc_archive->is_shape_frame(frames[2])); + EXPECT_NEAR(31.66666, frames[3], 1e-5); // transform + EXPECT_TRUE(abc_archive->is_xform_frame(frames[3])); + EXPECT_FALSE(abc_archive->is_shape_frame(frames[3])); + EXPECT_NEAR(32.0, frames[4], 1e-5); // transform + shape + EXPECT_TRUE(abc_archive->is_xform_frame(frames[4])); + EXPECT_TRUE(abc_archive->is_shape_frame(frames[4])); + EXPECT_NEAR(32.33333, frames[5], 1e-5); // transform + EXPECT_TRUE(abc_archive->is_xform_frame(frames[5])); + EXPECT_FALSE(abc_archive->is_shape_frame(frames[5])); + EXPECT_NEAR(32.5, frames[6], 1e-5); // shape + EXPECT_FALSE(abc_archive->is_xform_frame(frames[6])); + EXPECT_TRUE(abc_archive->is_shape_frame(frames[6])); + EXPECT_NEAR(32.66666, frames[7], 1e-5); // transform + EXPECT_TRUE(abc_archive->is_xform_frame(frames[7])); + EXPECT_FALSE(abc_archive->is_shape_frame(frames[7])); } TEST_F(AlembicExportTest, TimeSamples180degShutter) { - settings.shutter_open = -0.25; - settings.shutter_close = 0.25; - - createExporter(); - std::vector<double> samples; - - /* test 5 samples per frame */ - exporter->getShutterSamples(5, true, samples); - EXPECT_EQ(5, samples.size()); - EXPECT_NEAR(1.230, samples[0], 1e-5f); - EXPECT_NEAR(1.234, samples[1], 1e-5f); - EXPECT_NEAR(1.238, samples[2], 1e-5f); - EXPECT_NEAR(1.242, samples[3], 1e-5f); - EXPECT_NEAR(1.246, samples[4], 1e-5f); - - /* test same, but using frame number offset instead of time */ - exporter->getShutterSamples(5, false, samples); - EXPECT_EQ(5, samples.size()); - EXPECT_NEAR(-0.25, samples[0], 1e-5f); - EXPECT_NEAR(-0.15, samples[1], 1e-5f); - EXPECT_NEAR(-0.05, samples[2], 1e-5f); - EXPECT_NEAR(0.05, samples[3], 1e-5f); - EXPECT_NEAR(0.15, samples[4], 1e-5f); - - /* Use the same setup to test getFrameSet(). - * Here only a few numbers are tested, due to rounding issues. */ - std::set<double> frames; - exporter->getFrameSet(5, frames); - EXPECT_EQ(965, frames.size()); - EXPECT_EQ(1, frames.count(30.75)); - EXPECT_EQ(1, frames.count(30.95)); - EXPECT_EQ(1, frames.count(31.15)); + /* Test 5 samples per frame, for 2 frames. */ + params.shutter_open = -0.25; + params.shutter_close = 0.25; + params.frame_start = 31.0; + params.frame_end = 32.0; + params.frame_samples_xform = params.frame_samples_shape = 5; + createArchive(); + std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end()); + EXPECT_EQ(10, frames.size()); + EXPECT_NEAR(31 - 0.25, frames[0], 1e-5); + EXPECT_NEAR(31 - 0.15, frames[1], 1e-5); + EXPECT_NEAR(31 - 0.05, frames[2], 1e-5); + EXPECT_NEAR(31 + 0.05, frames[3], 1e-5); + EXPECT_NEAR(31 + 0.15, frames[4], 1e-5); + EXPECT_NEAR(32 - 0.25, frames[5], 1e-5); + EXPECT_NEAR(32 - 0.15, frames[6], 1e-5); + EXPECT_NEAR(32 - 0.05, frames[7], 1e-5); + EXPECT_NEAR(32 + 0.05, frames[8], 1e-5); + EXPECT_NEAR(32 + 0.15, frames[9], 1e-5); } + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/tests/gtests/alembic/abc_matrix_test.cc b/tests/gtests/alembic/abc_matrix_test.cc index 60dfd4e30cf..fe0635ea7ab 100644 --- a/tests/gtests/alembic/abc_matrix_test.cc +++ b/tests/gtests/alembic/abc_matrix_test.cc @@ -8,7 +8,9 @@ extern "C" { #include "BLI_utildefines.h" } -using namespace blender::io::alembic; +namespace blender { +namespace io { +namespace alembic { TEST(abc_matrix, CreateRotationMatrixY_YfromZ) { @@ -286,3 +288,7 @@ TEST(abc_matrix, CopyM44AxisSwapWithScale_gimbal_ZfromY) EXPECT_M4_NEAR(expect, result, 1e-5f); } + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/tests/python/alembic_tests.py b/tests/python/alembic_tests.py index 705bb98a060..5f8113729c3 100644 --- a/tests/python/alembic_tests.py +++ b/tests/python/alembic_tests.py @@ -214,7 +214,7 @@ class DupliGroupExportTest(AbstractAlembicTest): self.run_blender('dupligroup-scene.blend', script) # Now check the resulting Alembic file. - xform = self.abcprop(abc, '/Real_Cube/Linked_Suzanne/Cylinder/Suzanne/.xform') + xform = self.abcprop(abc, '/Real_Cube/Linked_Suzanne/Cylinder-0/Suzanne-1/.xform') self.assertEqual(1, xform['.inherits']) self.assertAlmostEqualFloatArray( xform['.vals'], @@ -232,7 +232,7 @@ class DupliGroupExportTest(AbstractAlembicTest): self.run_blender('dupligroup-scene.blend', script) # Now check the resulting Alembic file. - xform = self.abcprop(abc, '/Suzanne/.xform') + xform = self.abcprop(abc, '/Suzanne-1/.xform') self.assertEqual(1, xform['.inherits']) self.assertAlmostEqualFloatArray( |