diff options
Diffstat (limited to 'source/blender/io/alembic/exporter')
25 files changed, 3474 insertions, 0 deletions
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..5fbf74f0705 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_archive.cc @@ -0,0 +1,265 @@ +/* + * 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> + +#ifdef WIN32 +# include "BLI_path_util.h" +# include "BLI_string.h" + +# include "utfconv.h" +#endif + +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 + char filename_cstr[FILE_MAX]; + BLI_strncpy(filename_cstr, filename.c_str(), FILE_MAX); + + UTF16_ENCODE(filename_cstr); + std::wstring wstr(filename_cstr_16); + abc_ostream->open(wstr.c_str(), std::ios::out | std::ios::binary); + UTF16_UN_ENCODE(filename_cstr); +#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 new file mode 100644 index 00000000000..fbc5b2d5c02 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_export_capi.cc @@ -0,0 +1,220 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +#include "ABC_alembic.h" +#include "abc_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" + +#include "BKE_blender_version.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_scene.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + +#include <algorithm> + +struct ExportJobData { + Main *bmain; + Depsgraph *depsgraph; + wmWindowManager *wm; + + 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; + + G.is_rendering = true; + WM_set_locked_interface(data->wm, true); + G.is_break = false; + + *progress = 0.0f; + *do_update = true; + + 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); + + // 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); + + // Create the Alembic archive. + ABCArchive abc_archive(data->bmain, scene, data->params, std::string(data->filename)); + + ABCHierarchyIterator iter(data->depsgraph, &abc_archive, data->params); + + if (export_animation) { + CLOG_INFO(&LOG, 2, "Exporting animation"); + + // Writing the animated frames is not 100% of the work, but it's our best guess. + const float progress_per_frame = 1.0f / std::max(size_t(1), abc_archive.total_frame_count()); + ABCArchive::Frames::const_iterator frame_it = abc_archive.frames_begin(); + const ABCArchive::Frames::const_iterator frames_end = abc_archive.frames_end(); + + for (; frame_it != frames_end; frame_it++) { + double frame = *frame_it; + + if (G.is_break || (stop != nullptr && *stop)) { + break; + } + + // Update the scene for the next frame to render. + scene->r.cfra = static_cast<int>(frame); + scene->r.subframe = frame - scene->r.cfra; + BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain); + + CLOG_INFO(&LOG, 2, "Exporting frame %.2f", frame); + ExportSubset export_subset = abc_archive.export_subset_for_frame(frame); + iter.set_export_subset(export_subset); + iter.iterate_and_write(); + + *progress += progress_per_frame; + *do_update = true; + } + } + else { + // If we're not animating, a single iteration over all objects is enough. + iter.iterate_and_write(); + } + + 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->depsgraph); + + if (data->was_canceled && BLI_exists(data->filename)) { + BLI_delete(data->filename, false, false); + } + + G.is_rendering = false; + WM_set_locked_interface(data->wm, false); +} + +} // namespace alembic +} // namespace io +} // namespace blender + +bool ABC_export(Scene *scene, + bContext *C, + const char *filepath, + 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->bmain = CTX_data_main(C); + job->wm = CTX_wm_manager(C); + job->export_ok = false; + 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( + 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, + blender::io::alembic::export_startjob, + NULL, + NULL, + blender::io::alembic::export_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + /* Fake a job context, so that we don't need NULL pointer checks while exporting. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + blender::io::alembic::export_startjob(job, &stop, &do_update, &progress); + blender::io::alembic::export_endjob(job); + export_ok = job->export_ok; + + MEM_freeN(job); + } + + return export_ok; +} 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_subdiv_disabler.h b/source/blender/io/alembic/exporter/abc_subdiv_disabler.h new file mode 100644 index 00000000000..677847f3f63 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_subdiv_disabler.h @@ -0,0 +1,55 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ +#pragma once + +#include <set> + +struct Depsgraph; +struct ModifierData; +struct Object; +struct Scene; + +namespace blender { +namespace io { +namespace alembic { + +/** + * 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 SubdivModifierDisabler final { + private: + Depsgraph *depsgraph_; + std::set<ModifierData *> disabled_modifiers_; + + public: + explicit SubdivModifierDisabler(Depsgraph *depsgraph); + ~SubdivModifierDisabler(); + + void disable_modifiers(); + + static ModifierData *get_subdiv_modifier(Scene *scene, Object *ob); +}; + +} // namespace alembic +} // namespace io +} // namespace blender 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..a83373a567a --- /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 descendants 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_camera.cc b/source/blender/io/alembic/exporter/abc_writer_camera.cc new file mode 100644 index 00000000000..7e7277cb4ea --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_camera.cc @@ -0,0 +1,110 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_camera.h" +#include "abc_hierarchy_iterator.h" + +#include "BKE_camera.h" + +#include "BLI_assert.h" + +#include "DNA_camera_types.h" +#include "DNA_scene_types.h" + +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + +namespace blender { +namespace io { +namespace alembic { + +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*/) +{ + CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str()); + abc_camera_ = OCamera(args_.abc_parent, args_.abc_name, timesample_index_); + abc_camera_schema_ = abc_camera_.getSchema(); + + abc_custom_data_container_ = abc_camera_schema_.getUserProperties(); + abc_stereo_distance_ = OFloatProperty( + abc_custom_data_container_, "stereoDistance", timesample_index_); + abc_eye_separation_ = OFloatProperty( + abc_custom_data_container_, "eyeSeparation", timesample_index_); +} + +const Alembic::Abc::OObject ABCCameraWriter::get_alembic_object() const +{ + return abc_camera_; +} + +void ABCCameraWriter::do_write(HierarchyContext &context) +{ + Camera *cam = static_cast<Camera *>(context.object->data); + + abc_stereo_distance_.set(cam->stereo.convergence_distance); + abc_eye_separation_.set(cam->stereo.interocular_distance); + + const double apperture_x = cam->sensor_x / 10.0; + const double apperture_y = cam->sensor_y / 10.0; + const double film_aspect = apperture_x / apperture_y; + + 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(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 { + camera_sample.setFocusDistance(cam->dof.focus_distance); + } + + /* Blender camera does not have an fstop param, so try to find a custom prop + * instead. */ + camera_sample.setFStop(cam->dof.aperture_fstop); + + camera_sample.setLensSqueezeRatio(1.0); + abc_camera_schema_.set(camera_sample); +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_camera.h b/source/blender/io/alembic/exporter/abc_writer_camera.h new file mode 100644 index 00000000000..a72cfa2f357 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_camera.h @@ -0,0 +1,52 @@ +/* + * 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 "abc_writer_abstract.h" + +#include <Alembic/AbcGeom/OCamera.h> + +namespace blender { +namespace io { +namespace alembic { + +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: + explicit ABCCameraWriter(const ABCWriterConstructorArgs &args); + + 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 +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.cc b/source/blender/io/alembic/exporter/abc_writer_curves.cc new file mode 100644 index 00000000000..f2a46c5e4fe --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_curves.cc @@ -0,0 +1,201 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_curves.h" +#include "intern/abc_axis_conversion.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BKE_curve.h" +#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; +using Alembic::AbcGeom::OInt16Property; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OV2fGeomParam; + +namespace blender { +namespace io { +namespace alembic { + +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) +{ + CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str()); + abc_curve_ = OCurves(args_.abc_parent, args_.abc_name, timesample_index_); + abc_curve_schema_ = abc_curve_.getSchema(); + + Curve *cu = static_cast<Curve *>(context->object->data); + OCompoundProperty user_props = abc_curve_schema_.getUserProperties(); + OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME); + user_prop_resolu.set(cu->resolu); +} + +const Alembic::Abc::OObject ABCCurveWriter::get_alembic_object() const +{ + return abc_curve_; +} + +void ABCCurveWriter::do_write(HierarchyContext &context) +{ + Curve *curve = static_cast<Curve *>(context.object->data); + + std::vector<Imath::V3f> verts; + std::vector<int32_t> vert_counts; + std::vector<float> widths; + std::vector<float> weights; + std::vector<float> knots; + std::vector<uint8_t> orders; + Imath::V3f temp_vert; + + Alembic::AbcGeom::BasisType curve_basis; + Alembic::AbcGeom::CurveType curve_type; + Alembic::AbcGeom::CurvePeriodicity periodicity; + + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (; nurbs; nurbs = nurbs->next) { + if (nurbs->bp) { + curve_basis = Alembic::AbcGeom::kNoBasis; + curve_type = Alembic::AbcGeom::kVariableOrder; + + const int totpoint = nurbs->pntsu * nurbs->pntsv; + + const BPoint *point = nurbs->bp; + + for (int i = 0; i < totpoint; i++, point++) { + copy_yup_from_zup(temp_vert.getValue(), point->vec); + verts.push_back(temp_vert); + weights.push_back(point->vec[3]); + widths.push_back(point->radius); + } + } + else if (nurbs->bezt) { + curve_basis = Alembic::AbcGeom::kBezierBasis; + curve_type = Alembic::AbcGeom::kCubic; + + const int totpoint = nurbs->pntsu; + + const BezTriple *bezier = nurbs->bezt; + + /* TODO(kevin): store info about handles, Alembic doesn't have this. */ + for (int i = 0; i < totpoint; i++, bezier++) { + copy_yup_from_zup(temp_vert.getValue(), bezier->vec[1]); + verts.push_back(temp_vert); + widths.push_back(bezier->radius); + } + } + + if ((nurbs->flagu & CU_NURB_ENDPOINT) != 0) { + periodicity = Alembic::AbcGeom::kNonPeriodic; + } + else if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) { + periodicity = Alembic::AbcGeom::kPeriodic; + + /* Duplicate the start points to indicate that the curve is actually + * cyclic since other software need those. + */ + + for (int i = 0; i < nurbs->orderu; i++) { + verts.push_back(verts[i]); + } + } + + if (nurbs->knotsu != NULL) { + const size_t num_knots = KNOTSU(nurbs); + + /* Add an extra knot at the beginning and end of the array since most apps + * require/expect them. */ + knots.resize(num_knots + 2); + + for (int i = 0; i < num_knots; i++) { + knots[i + 1] = nurbs->knotsu[i]; + } + + if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) { + knots[0] = nurbs->knotsu[0]; + knots[num_knots - 1] = nurbs->knotsu[num_knots - 1]; + } + else { + knots[0] = (2.0f * nurbs->knotsu[0] - nurbs->knotsu[1]); + knots[num_knots - 1] = (2.0f * nurbs->knotsu[num_knots - 1] - + nurbs->knotsu[num_knots - 2]); + } + } + + orders.push_back(nurbs->orderu); + vert_counts.push_back(verts.size()); + } + + Alembic::AbcGeom::OFloatGeomParam::Sample width_sample; + width_sample.setVals(widths); + + 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(const ABCWriterConstructorArgs &args) + : ABCGenericMeshWriter(args) +{ +} + +Mesh *ABCCurveMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree) +{ + 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; + return mesh_eval; + } + + r_needsfree = true; + return BKE_mesh_new_nomain_from_curve(object_eval); +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.h b/source/blender/io/alembic/exporter/abc_writer_curves.h new file mode 100644 index 00000000000..12a909761f5 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_curves.h @@ -0,0 +1,61 @@ +/* + * 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. + */ +#pragma once + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_abstract.h" +#include "abc_writer_mesh.h" + +#include <Alembic/AbcGeom/OCurves.h> + +namespace blender { +namespace io { +namespace alembic { + +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: + 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: + virtual void do_write(HierarchyContext &context) override; +}; + +class ABCCurveMeshWriter : public ABCGenericMeshWriter { + public: + ABCCurveMeshWriter(const ABCWriterConstructorArgs &args); + + protected: + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.cc b/source/blender/io/alembic/exporter/abc_writer_hair.cc new file mode 100644 index 00000000000..ac4deddd9b4 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_hair.cc @@ -0,0 +1,311 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_hair.h" +#include "intern/abc_axis_conversion.h" + +#include <cstdio> + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "BLI_math_geom.h" + +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_particle.h" + +#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(const ABCWriterConstructorArgs &args) + : ABCAbstractWriter(args), uv_warning_shown_(false) +{ +} + +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(); +} + +const Alembic::Abc::OObject ABCHairWriter::get_alembic_object() const +{ + return abc_curves_; +} + +bool ABCHairWriter::check_is_animated(const HierarchyContext & /*context*/) const +{ + /* We assume that hair particles are always animated. */ + return true; +} + +void ABCHairWriter::do_write(HierarchyContext &context) +{ + Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph); + Mesh *mesh = mesh_get_eval_final(args_.depsgraph, scene_eval, context.object, &CD_MASK_MESH); + BKE_mesh_tessface_ensure(mesh); + + std::vector<Imath::V3f> verts; + std::vector<int32_t> hvertices; + std::vector<Imath::V2f> uv_values; + std::vector<Imath::V3f> norm_values; + + 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(context, mesh, verts, norm_values, uv_values, hvertices); + } + + if (export_children) { + write_hair_child_sample(context, mesh, verts, norm_values, uv_values, hvertices); + } + } + + Alembic::Abc::P3fArraySample iPos(verts); + 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); + sample.setUVs(uv_smp); + } + + if (!norm_values.empty()) { + ON3fGeomParam::Sample norm_smp; + norm_smp.setVals(norm_values); + sample.setNormals(norm_smp); + } + + update_bounding_box(context.object); + sample.setSelfBounds(bounding_box_); + abc_curves_schema_.set(sample); +} + +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, + std::vector<int32_t> &hvertices) +{ + /* Get untransformed vertices, there's a xform under the hair. */ + float inv_mat[4][4]; + invert_m4_m4_safe(inv_mat, context.object->obmat); + + MTFace *mtface = mesh->mtface; + MFace *mface = mesh->mface; + MVert *mverts = mesh->mvert; + + if ((!mtface || !mface) && !uv_warning_shown_) { + std::fprintf(stderr, + "Warning, no UV set found for underlying geometry of %s.\n", + context.object->id.name + 2); + uv_warning_shown_ = true; + } + + ParticleSystem *psys = context.particle_system; + ParticleSettings *part = psys->part; + ParticleData *pa = psys->particles; + int k; + + ParticleCacheKey **cache = psys->pathcache; + ParticleCacheKey *path; + float normal[3]; + Imath::V3f tmp_nor; + + for (int p = 0; p < psys->totpart; p++, pa++) { + /* underlying info for faces-only emission */ + path = cache[p]; + + /* Write UV and normal vectors */ + if (part->from == PART_FROM_FACE && mtface) { + const int num = pa->num_dmcache >= 0 ? pa->num_dmcache : pa->num; + + if (num < mesh->totface) { + /* TODO(Sybren): check whether the NULL check here and if(mface) are actually required */ + MFace *face = mface == NULL ? NULL : &mface[num]; + MTFace *tface = mtface + num; + + if (mface) { + float r_uv[2], mapfw[4], vec[3]; + + psys_interpolate_uvs(tface, face->v4, pa->fuv, r_uv); + uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1])); + + psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, normal, NULL, NULL, NULL); + + copy_yup_from_zup(tmp_nor.getValue(), normal); + norm_values.push_back(tmp_nor); + } + } + else { + std::fprintf(stderr, "Particle to faces overflow (%d/%d)\n", num, mesh->totface); + } + } + else if (part->from == PART_FROM_VERT && mtface) { + /* vertex id */ + const int num = (pa->num_dmcache >= 0) ? pa->num_dmcache : pa->num; + + /* iterate over all faces to find a corresponding underlying UV */ + for (int n = 0; n < mesh->totface; n++) { + MFace *face = &mface[n]; + MTFace *tface = mtface + n; + unsigned int vtx[4]; + vtx[0] = face->v1; + vtx[1] = face->v2; + vtx[2] = face->v3; + vtx[3] = face->v4; + bool found = false; + + for (int o = 0; o < 4; o++) { + if (o > 2 && vtx[o] == 0) { + break; + } + + if (vtx[o] == num) { + uv_values.push_back(Imath::V2f(tface->uv[o][0], tface->uv[o][1])); + + MVert *mv = mverts + vtx[o]; + + normal_short_to_float_v3(normal, mv->no); + copy_yup_from_zup(tmp_nor.getValue(), normal); + norm_values.push_back(tmp_nor); + found = true; + break; + } + } + + if (found) { + break; + } + } + } + + int steps = path->segments + 1; + hvertices.push_back(steps); + + for (k = 0; k < steps; k++, path++) { + float vert[3]; + copy_v3_v3(vert, path->co); + mul_m4_v3(inv_mat, vert); + + /* Convert Z-up to Y-up. */ + verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1])); + } + } +} + +void ABCHairWriter::write_hair_child_sample(const HierarchyContext &context, + Mesh *mesh, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices) +{ + /* Get untransformed vertices, there's a xform under the hair. */ + float inv_mat[4][4]; + invert_m4_m4_safe(inv_mat, context.object->obmat); + + MTFace *mtface = mesh->mtface; + MVert *mverts = mesh->mvert; + + ParticleSystem *psys = context.particle_system; + ParticleSettings *part = psys->part; + ParticleCacheKey **cache = psys->childcache; + ParticleCacheKey *path; + + ChildParticle *pc = psys->child; + + 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) { + 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; + } + + MFace *face = &mesh->mface[num]; + MTFace *tface = mtface + num; + + float r_uv[2], tmpnor[3], mapfw[4], vec[3]; + + psys_interpolate_uvs(tface, face->v4, pc->fuv, r_uv); + uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1])); + + psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, tmpnor, NULL, NULL, NULL); + + /* Convert Z-up to Y-up. */ + norm_values.push_back(Imath::V3f(tmpnor[0], tmpnor[2], -tmpnor[1])); + } + else { + if (uv_values.size()) { + uv_values.push_back(uv_values[pc->parent]); + } + if (norm_values.size()) { + norm_values.push_back(norm_values[pc->parent]); + } + } + + int steps = path->segments + 1; + hvertices.push_back(steps); + + for (int k = 0; k < steps; k++) { + float vert[3]; + copy_v3_v3(vert, path->co); + mul_m4_v3(inv_mat, vert); + + /* Convert Z-up to Y-up. */ + verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1])); + + path++; + } + } +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.h b/source/blender/io/alembic/exporter/abc_writer_hair.h new file mode 100644 index 00000000000..af1372a08f3 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_hair.h @@ -0,0 +1,68 @@ +/* + * 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 "abc_writer_abstract.h" +#include <Alembic/AbcGeom/OCurves.h> +#include <vector> + +struct ParticleSettings; +struct ParticleSystem; + +namespace blender { +namespace io { +namespace alembic { + +class ABCHairWriter : public ABCAbstractWriter { + private: + Alembic::AbcGeom::OCurves abc_curves_; + Alembic::AbcGeom::OCurvesSchema abc_curves_schema_; + + bool uv_warning_shown_; + + public: + explicit ABCHairWriter(const ABCWriterConstructorArgs &args); + + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; + + protected: + virtual void do_write(HierarchyContext &context) override; + virtual bool check_is_animated(const HierarchyContext &context) const override; + + private: + 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(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); +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_mball.cc b/source/blender/io/alembic/exporter/abc_writer_mball.cc new file mode 100644 index 00000000000..167e392eb96 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_mball.cc @@ -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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_mball.h" +#include "abc_hierarchy_iterator.h" + +#include "BLI_assert.h" + +#include "BKE_displist.h" +#include "BKE_lib_id.h" +#include "BKE_mball.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "DNA_mesh_types.h" +#include "DNA_meta_types.h" + +namespace blender { +namespace io { +namespace alembic { + +ABCMetaballWriter::ABCMetaballWriter(const ABCWriterConstructorArgs &args) + : ABCGenericMeshWriter(args) +{ +} + +bool ABCMetaballWriter::is_supported(const HierarchyContext *context) const +{ + Scene *scene = DEG_get_input_scene(args_.depsgraph); + bool supported = is_basis_ball(scene, context->object) && + ABCGenericMeshWriter::is_supported(context); + return supported; +} + +bool ABCMetaballWriter::check_is_animated(const HierarchyContext & /*context*/) const +{ + /* We assume that metaballs are always animated, as the current object may + * not be animated but another ball in the same group may be. */ + return true; +} + +bool ABCMetaballWriter::export_as_subdivision_surface(Object * /*ob_eval*/) const +{ + /* 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; + return BKE_mesh_new_from_object(args_.depsgraph, object_eval, false); +} + +void ABCMetaballWriter::free_export_mesh(Mesh *mesh) +{ + BKE_id_free(nullptr, mesh); +} + +bool ABCMetaballWriter::is_basis_ball(Scene *scene, Object *ob) const +{ + Object *basis_ob = BKE_mball_basis_find(scene, ob); + return ob == basis_ob; +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_mball.h b/source/blender/io/alembic/exporter/abc_writer_mball.h new file mode 100644 index 00000000000..90d8c4d4b15 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_mball.h @@ -0,0 +1,45 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#pragma once + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_mesh.h" + +namespace blender { +namespace io { +namespace alembic { + +class ABCMetaballWriter : public ABCGenericMeshWriter { + public: + explicit ABCMetaballWriter(const ABCWriterConstructorArgs &args); + + protected: + 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 is_basis_ball(Scene *scene, Object *ob) const; +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc new file mode 100644 index 00000000000..07196f2b81f --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -0,0 +1,573 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_mesh.h" +#include "abc_hierarchy_iterator.h" +#include "intern/abc_axis_conversion.h" + +#include "BLI_assert.h" +#include "BLI_math_vector.h" + +#include "BKE_customdata.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "bmesh.h" +#include "bmesh_tools.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; + +using Alembic::AbcGeom::kFacevaryingScope; +using Alembic::AbcGeom::OBoolProperty; +using Alembic::AbcGeom::OCompoundProperty; +using Alembic::AbcGeom::OFaceSet; +using Alembic::AbcGeom::OFaceSetSchema; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OPolyMesh; +using Alembic::AbcGeom::OPolyMeshSchema; +using Alembic::AbcGeom::OSubD; +using Alembic::AbcGeom::OSubDSchema; +using Alembic::AbcGeom::OV2fGeomParam; +using Alembic::AbcGeom::UInt32ArraySample; + +namespace blender { +namespace io { +namespace alembic { + +/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */ + +static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points); +static void get_topology(struct Mesh *mesh, + std::vector<int32_t> &poly_verts, + std::vector<int32_t> &loop_counts, + bool &r_has_flat_shaded_poly); +static void get_creases(struct Mesh *mesh, + std::vector<int32_t> &indices, + std::vector<int32_t> &lengths, + std::vector<float> &sharpnesses); +static void get_loop_normals(struct Mesh *mesh, + std::vector<Imath::V3f> &normals, + bool has_flat_shaded_poly); + +ABCGenericMeshWriter::ABCGenericMeshWriter(const ABCWriterConstructorArgs &args) + : ABCAbstractWriter(args), is_subd_(false) +{ +} + +void ABCGenericMeshWriter::create_alembic_objects(const HierarchyContext *context) +{ + if (!args_.export_params->apply_subdiv && export_as_subdivision_surface(context->object)) { + is_subd_ = args_.export_params->use_subdiv_schema; + } + + if (is_subd_) { + CLOG_INFO(&LOG, 2, "exporting OSubD %s", args_.abc_path.c_str()); + abc_subdiv_ = OSubD(args_.abc_parent, args_.abc_name, timesample_index_); + abc_subdiv_schema_ = abc_subdiv_.getSchema(); + } + else { + CLOG_INFO(&LOG, 2, "exporting OPolyMesh %s", args_.abc_path.c_str()); + abc_poly_mesh_ = OPolyMesh(args_.abc_parent, args_.abc_name, timesample_index_); + abc_poly_mesh_schema_ = abc_poly_mesh_.getSchema(); + + OCompoundProperty typeContainer = abc_poly_mesh_.getSchema().getUserProperties(); + OBoolProperty type(typeContainer, "meshtype"); + type.set(subsurf_modifier_ == nullptr); + } + + Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph); + liquid_sim_modifier_ = get_liquid_sim_modifier(scene_eval, context->object); +} + +ABCGenericMeshWriter::~ABCGenericMeshWriter() +{ +} + +const Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const +{ + if (is_subd_) { + return abc_subdiv_; + } + return abc_poly_mesh_; +} + +bool ABCGenericMeshWriter::export_as_subdivision_surface(Object *ob_eval) const +{ + ModifierData *md = static_cast<ModifierData *>(ob_eval->modifiers.last); + + for (; md; md = md->prev) { + /* This modifier has been temporarily disabled by SubdivModifierDisabler, + * so this indicates this is to be exported as subdivision surface. */ + if (md->type == eModifierType_Subsurf && (md->mode & eModifierMode_DisableTemporary)) { + return true; + } + } + + return false; +} + +ModifierData *ABCGenericMeshWriter::get_liquid_sim_modifier(Scene *scene, Object *ob) +{ + ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluidsim); + + if (md && (BKE_modifier_is_enabled(scene, md, eModifierMode_Render))) { + FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); + + if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) { + return md; + } + } + + return nullptr; +} + +bool ABCGenericMeshWriter::is_supported(const HierarchyContext *context) const +{ + Object *object = context->object; + bool is_dupli = context->duplicator != nullptr; + int base_flag; + + if (is_dupli) { + /* Construct the object's base flags from its dupli-parent, just like is done in + * deg_objects_dupli_iterator_next(). Without this, the visibility check below will fail. Doing + * this here, instead of a more suitable location in AbstractHierarchyIterator, prevents + * copying the Object for every dupli. */ + base_flag = object->base_flag; + object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI; + } + + int visibility = BKE_object_visibility( + object, DAG_EVAL_RENDER /* TODO(Sybren): add evaluation mode to export options? */); + + if (is_dupli) { + object->base_flag = base_flag; + } + + return (visibility & OB_VISIBLE_SELF) != 0; +} + +void ABCGenericMeshWriter::do_write(HierarchyContext &context) +{ + Object *object = context.object; + bool needsfree = false; + + Mesh *mesh = get_export_mesh(object, needsfree); + + if (mesh == nullptr) { + return; + } + + if (args_.export_params->triangulate) { + const bool tag_only = false; + const int quad_method = args_.export_params->quad_method; + const int ngon_method = args_.export_params->ngon_method; + + struct BMeshCreateParams bmcp = {false}; + struct BMeshFromMeshParams bmfmp = {true, false, false, 0}; + BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmcp, &bmfmp); + + BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr); + + Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); + BM_mesh_free(bm); + + if (needsfree) { + free_export_mesh(mesh); + } + mesh = triangulated_mesh; + needsfree = true; + } + + m_custom_data_config.pack_uvs = args_.export_params->packuv; + m_custom_data_config.mpoly = mesh->mpoly; + m_custom_data_config.mloop = mesh->mloop; + m_custom_data_config.totpoly = mesh->totpoly; + m_custom_data_config.totloop = mesh->totloop; + m_custom_data_config.totvert = mesh->totvert; + + try { + if (is_subd_) { + write_subd(context, mesh); + } + else { + write_mesh(context, mesh); + } + + if (needsfree) { + free_export_mesh(mesh); + } + } + catch (...) { + if (needsfree) { + free_export_mesh(mesh); + } + throw; + } +} + +void ABCGenericMeshWriter::free_export_mesh(Mesh *mesh) +{ + BKE_id_free(nullptr, mesh); +} + +void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) +{ + std::vector<Imath::V3f> points, normals; + std::vector<int32_t> poly_verts, loop_counts; + std::vector<Imath::V3f> velocities; + bool has_flat_shaded_poly = false; + + get_vertices(mesh, points); + get_topology(mesh, poly_verts, loop_counts, has_flat_shaded_poly); + + if (!frame_has_been_written_ && args_.export_params->face_sets) { + write_face_sets(context.object, mesh, abc_poly_mesh_schema_); + } + + OPolyMeshSchema::Sample mesh_sample = OPolyMeshSchema::Sample( + V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); + + UVSample uvs_and_indices; + + if (!frame_has_been_written_ && args_.export_params->uvs) { + const char *name = get_uv_sample(uvs_and_indices, m_custom_data_config, &mesh->ldata); + + if (!uvs_and_indices.indices.empty() && !uvs_and_indices.uvs.empty()) { + OV2fGeomParam::Sample uv_sample; + uv_sample.setVals(V2fArraySample(uvs_and_indices.uvs)); + uv_sample.setIndices(UInt32ArraySample(uvs_and_indices.indices)); + uv_sample.setScope(kFacevaryingScope); + + abc_poly_mesh_schema_.setUVSourceName(name); + mesh_sample.setUVs(uv_sample); + } + + write_custom_data( + abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + } + + if (args_.export_params->normals) { + get_loop_normals(mesh, normals, has_flat_shaded_poly); + + ON3fGeomParam::Sample normals_sample; + if (!normals.empty()) { + normals_sample.setScope(kFacevaryingScope); + normals_sample.setVals(V3fArraySample(normals)); + } + + mesh_sample.setNormals(normals_sample); + } + + if (liquid_sim_modifier_ != nullptr) { + get_velocities(mesh, velocities); + mesh_sample.setVelocities(V3fArraySample(velocities)); + } + + update_bounding_box(context.object); + mesh_sample.setSelfBounds(bounding_box_); + + abc_poly_mesh_schema_.set(mesh_sample); + + write_arb_geo_params(mesh); +} + +void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *mesh) +{ + std::vector<float> crease_sharpness; + std::vector<Imath::V3f> points; + std::vector<int32_t> poly_verts, loop_counts; + std::vector<int32_t> crease_indices, crease_lengths; + bool has_flat_poly = false; + + get_vertices(mesh, points); + get_topology(mesh, poly_verts, loop_counts, has_flat_poly); + get_creases(mesh, crease_indices, crease_lengths, crease_sharpness); + + if (!frame_has_been_written_ && args_.export_params->face_sets) { + write_face_sets(context.object, mesh, abc_subdiv_schema_); + } + + OSubDSchema::Sample subdiv_sample = OSubDSchema::Sample( + V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); + + UVSample sample; + if (!frame_has_been_written_ && args_.export_params->uvs) { + const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); + + if (!sample.indices.empty() && !sample.uvs.empty()) { + OV2fGeomParam::Sample uv_sample; + uv_sample.setVals(V2fArraySample(sample.uvs)); + uv_sample.setIndices(UInt32ArraySample(sample.indices)); + uv_sample.setScope(kFacevaryingScope); + + abc_subdiv_schema_.setUVSourceName(name); + subdiv_sample.setUVs(uv_sample); + } + + write_custom_data( + abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + } + + if (!crease_indices.empty()) { + subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices)); + subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths)); + subdiv_sample.setCreaseSharpnesses(FloatArraySample(crease_sharpness)); + } + + update_bounding_box(context.object); + subdiv_sample.setSelfBounds(bounding_box_); + abc_subdiv_schema_.set(subdiv_sample); + + write_arb_geo_params(mesh); +} + +template<typename Schema> +void ABCGenericMeshWriter::write_face_sets(Object *object, struct Mesh *mesh, Schema &schema) +{ + std::map<std::string, std::vector<int32_t>> geo_groups; + get_geo_groups(object, mesh, geo_groups); + + std::map<std::string, std::vector<int32_t>>::iterator it; + for (it = geo_groups.begin(); it != geo_groups.end(); ++it) { + OFaceSet face_set = schema.createFaceSet(it->first); + OFaceSetSchema::Sample samp; + samp.setFaces(Int32ArraySample(it->second)); + face_set.getSchema().set(samp); + } +} + +void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me) +{ + if (liquid_sim_modifier_ != nullptr) { + /* We don't need anything more for liquid meshes. */ + return; + } + + if (frame_has_been_written_ || !args_.export_params->vcolors) { + return; + } + + OCompoundProperty arb_geom_params; + if (is_subd_) { + arb_geom_params = abc_subdiv_.getSchema().getArbGeomParams(); + } + else { + arb_geom_params = abc_poly_mesh_.getSchema().getArbGeomParams(); + } + write_custom_data(arb_geom_params, m_custom_data_config, &me->ldata, CD_MLOOPCOL); +} + +void ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) +{ + const int totverts = mesh->totvert; + + vels.clear(); + vels.resize(totverts); + + FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(liquid_sim_modifier_); + FluidsimSettings *fss = fmd->fss; + + if (fss->meshVelocities) { + float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities); + + for (int i = 0; i < totverts; i++) { + copy_yup_from_zup(vels[i].getValue(), mesh_vels); + mesh_vels += 3; + } + } + else { + std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f)); + } +} + +void ABCGenericMeshWriter::get_geo_groups(Object *object, + struct Mesh *mesh, + std::map<std::string, std::vector<int32_t>> &geo_groups) +{ + const int num_poly = mesh->totpoly; + MPoly *polygons = mesh->mpoly; + + for (int i = 0; i < num_poly; i++) { + MPoly ¤t_poly = polygons[i]; + short mnr = current_poly.mat_nr; + + Material *mat = BKE_object_material_get(object, mnr + 1); + + if (!mat) { + continue; + } + + std::string name = args_.hierarchy_iterator->get_id_name(&mat->id); + + if (geo_groups.find(name) == geo_groups.end()) { + std::vector<int32_t> faceArray; + geo_groups[name] = faceArray; + } + + geo_groups[name].push_back(i); + } + + if (geo_groups.size() == 0) { + Material *mat = BKE_object_material_get(object, 1); + + std::string name = (mat) ? args_.hierarchy_iterator->get_id_name(&mat->id) : "default"; + + std::vector<int32_t> faceArray; + + for (int i = 0, e = mesh->totface; i < e; i++) { + faceArray.push_back(i); + } + + geo_groups[name] = faceArray; + } +} + +/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */ + +static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points) +{ + points.clear(); + 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(const ABCWriterConstructorArgs &args) : ABCGenericMeshWriter(args) +{ +} + +Mesh *ABCMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/) +{ + return BKE_object_get_evaluated_mesh(object_eval); +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.h b/source/blender/io/alembic/exporter/abc_writer_mesh.h new file mode 100644 index 00000000000..bf4d4e9b9d8 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.h @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#pragma once + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_abstract.h" +#include "intern/abc_customdata.h" + +#include <Alembic/AbcGeom/OPolyMesh.h> +#include <Alembic/AbcGeom/OSubD.h> + +struct ModifierData; + +namespace blender { +namespace io { +namespace alembic { + +/* Writer for Alembic geometry. Does not assume the object is a mesh object. */ +class ABCGenericMeshWriter : public ABCAbstractWriter { + private: + /* Either polymesh or subd is used, depending on is_subd_. + * References to the schema must be kept, or Alembic will not properly write. */ + Alembic::AbcGeom::OPolyMesh abc_poly_mesh_; + Alembic::AbcGeom::OPolyMeshSchema abc_poly_mesh_schema_; + + Alembic::AbcGeom::OSubD abc_subdiv_; + Alembic::AbcGeom::OSubDSchema abc_subdiv_schema_; + + /* 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 subdivision modifier on the + * exported object. */ + bool is_subd_; + ModifierData *subsurf_modifier_; + ModifierData *liquid_sim_modifier_; + + CDStreamConfig m_custom_data_config; + + public: + explicit ABCGenericMeshWriter(const ABCWriterConstructorArgs &args); + virtual ~ABCGenericMeshWriter(); + + virtual void create_alembic_objects(const HierarchyContext *context) override; + virtual const Alembic::Abc::OObject get_alembic_object() const; + + protected: + virtual bool is_supported(const HierarchyContext *context) const override; + virtual void do_write(HierarchyContext &context) override; + + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0; + virtual void free_export_mesh(Mesh *mesh); + + virtual bool export_as_subdivision_surface(Object *ob_eval) const; + + 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); + + ModifierData *get_liquid_sim_modifier(Scene *scene_eval, Object *ob_eval); + + 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); +}; + +/* Writer for Alembic geometry of Blender Mesh objects. */ +class ABCMeshWriter : public ABCGenericMeshWriter { + public: + ABCMeshWriter(const ABCWriterConstructorArgs &args); + + protected: + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc new file mode 100644 index 00000000000..1fd382214a6 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc @@ -0,0 +1,186 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_nurbs.h" +#include "intern/abc_axis_conversion.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" + +#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; + +ABCNurbsWriter::ABCNurbsWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args) +{ +} + +void ABCNurbsWriter::create_alembic_objects(const HierarchyContext *context) +{ + Curve *curve = static_cast<Curve *>(context->object->data); + size_t num_nurbs = BLI_listbase_count(&curve->nurb); + OObject abc_parent = args_.abc_parent; + const char *abc_parent_path = abc_parent.getFullName().c_str(); + + for (size_t i = 0; i < num_nurbs; i++) { + std::stringstream patch_name_stream; + patch_name_stream << args_.abc_name << '_' << i; + + while (abc_parent.getChildHeader(patch_name_stream.str())) { + patch_name_stream << "_"; + } + + std::string patch_name = patch_name_stream.str(); + CLOG_INFO(&LOG, 2, "exporting %s/%s", abc_parent_path, patch_name.c_str()); + + ONuPatch nurbs(abc_parent, patch_name.c_str(), timesample_index_); + abc_nurbs_.push_back(nurbs); + abc_nurbs_schemas_.push_back(nurbs.getSchema()); + } +} + +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::check_is_animated(const HierarchyContext &context) const +{ + /* Check if object has shape keys. */ + Curve *cu = static_cast<Curve *>(context.object->data); + return (cu->key != NULL); +} + +bool ABCNurbsWriter::is_supported(const HierarchyContext *context) const +{ + return ELEM(context->object->type, OB_SURF, OB_CURVE); +} + +static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_knots) +{ + if (num_knots <= 1) { + return; + } + + /* Add an extra knot at the beginning and end of the array since most apps + * require/expect them. */ + knots.reserve(num_knots + 2); + + knots.push_back(0.0f); + + for (int i = 0; i < num_knots; i++) { + knots.push_back(nu_knots[i]); + } + + knots[0] = 2.0f * knots[1] - knots[2]; + knots.push_back(2.0f * knots[num_knots] - knots[num_knots - 1]); +} + +void ABCNurbsWriter::do_write(HierarchyContext &context) +{ + Curve *curve = static_cast<Curve *>(context.object->data); + ListBase *nulb; + + 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); + } + + size_t count = 0; + for (Nurb *nu = static_cast<Nurb *>(nulb->first); nu; nu = nu->next, count++) { + std::vector<float> knotsU; + get_knots(knotsU, KNOTSU(nu), nu->knotsu); + + std::vector<float> knotsV; + get_knots(knotsV, KNOTSV(nu), nu->knotsv); + + const int size = nu->pntsu * nu->pntsv; + std::vector<Imath::V3f> positions(size); + std::vector<float> weights(size); + + const BPoint *bp = nu->bp; + + for (int i = 0; i < size; i++, bp++) { + copy_yup_from_zup(positions[i].getValue(), bp->vec); + weights[i] = bp->vec[3]; + } + + ONuPatchSchema::Sample sample; + sample.setUOrder(nu->orderu + 1); + sample.setVOrder(nu->orderv + 1); + sample.setPositions(positions); + sample.setPositionWeights(weights); + sample.setUKnot(FloatArraySample(knotsU)); + sample.setVKnot(FloatArraySample(knotsV)); + sample.setNu(nu->pntsu); + sample.setNv(nu->pntsv); + + /* TODO(kevin): to accommodate other software we should duplicate control + * points to indicate that a NURBS is cyclic. */ + OCompoundProperty user_props = abc_nurbs_schemas_[count].getUserProperties(); + + if ((nu->flagu & CU_NURB_ENDPOINT) != 0) { + OBoolProperty prop(user_props, "endpoint_u"); + prop.set(true); + } + + if ((nu->flagv & CU_NURB_ENDPOINT) != 0) { + OBoolProperty prop(user_props, "endpoint_v"); + prop.set(true); + } + + if ((nu->flagu & CU_NURB_CYCLIC) != 0) { + OBoolProperty prop(user_props, "cyclic_u"); + prop.set(true); + } + + if ((nu->flagv & CU_NURB_CYCLIC) != 0) { + OBoolProperty prop(user_props, "cyclic_v"); + prop.set(true); + } + + abc_nurbs_schemas_[count].set(sample); + } +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.h b/source/blender/io/alembic/exporter/abc_writer_nurbs.h new file mode 100644 index 00000000000..23af4c40556 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.h @@ -0,0 +1,57 @@ +/* + * 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 "abc_writer_abstract.h" +#include "abc_writer_mesh.h" +#include <vector> + +namespace blender { +namespace io { +namespace alembic { + +class ABCNurbsWriter : public ABCAbstractWriter { + private: + std::vector<Alembic::AbcGeom::ONuPatch> abc_nurbs_; + std::vector<Alembic::AbcGeom::ONuPatchSchema> abc_nurbs_schemas_; + + public: + explicit ABCNurbsWriter(const ABCWriterConstructorArgs &args); + + 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); + + protected: + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; +}; + +} // 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 new file mode 100644 index 00000000000..19870e39a90 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_points.cc @@ -0,0 +1,148 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_points.h" + +#include "DNA_object_types.h" +#include "DNA_particle_types.h" + +#include "BKE_lattice.h" +#include "BKE_particle.h" + +#include "BLI_math.h" + +#include "DEG_depsgraph_query.h" + +#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) +{ +} + +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(); +} + +const Alembic::Abc::OObject ABCPointsWriter::get_alembic_object() const +{ + return abc_points_; +} + +bool ABCPointsWriter::is_supported(const HierarchyContext *context) const +{ + return ELEM(context->particle_system->part->type, + PART_EMITTER, + PART_FLUID_FLIP, + PART_FLUID_SPRAY, + PART_FLUID_BUBBLE, + PART_FLUID_FOAM, + PART_FLUID_TRACER, + PART_FLUID_SPRAYFOAM, + PART_FLUID_SPRAYBUBBLE, + PART_FLUID_FOAMBUBBLE, + PART_FLUID_SPRAYFOAMBUBBLE); +} + +bool ABCPointsWriter::check_is_animated(const HierarchyContext & /*context*/) const +{ + /* We assume that particles are always animated. */ + return true; +} + +void ABCPointsWriter::do_write(HierarchyContext &context) +{ + BLI_assert(context.particle_system != nullptr); + + std::vector<Imath::V3f> points; + std::vector<Imath::V3f> velocities; + std::vector<float> widths; + std::vector<uint64_t> ids; + + ParticleSystem *psys = context.particle_system; + ParticleKey state; + ParticleSimulationData sim; + sim.depsgraph = args_.depsgraph; + sim.scene = DEG_get_evaluated_scene(args_.depsgraph); + sim.ob = context.object; + sim.psys = psys; + + psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + + uint64_t index = 0; + for (int p = 0; p < psys->totpart; p++) { + float pos[3], vel[3]; + + if (psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) { + continue; + } + + state.time = DEG_get_ctime(args_.depsgraph); + if (psys_get_particle_state(&sim, p, &state, 0) == 0) { + continue; + } + + /* location */ + mul_v3_m4v3(pos, context.object->imat, state.co); + + /* velocity */ + 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(psys->particles[p].size); + ids.push_back(index++); + } + + if (psys->lattice_deform_data) { + BKE_lattice_deform_data_destroy(psys->lattice_deform_data); + psys->lattice_deform_data = NULL; + } + + Alembic::Abc::P3fArraySample psample(points); + Alembic::Abc::UInt64ArraySample idsample(ids); + Alembic::Abc::V3fArraySample vsample(velocities); + Alembic::Abc::FloatArraySample wsample_array(widths); + Alembic::AbcGeom::OFloatGeomParam::Sample wsample(wsample_array, kVertexScope); + + OPointsSchema::Sample sample(psample, idsample, vsample, wsample); + update_bounding_box(context.object); + sample.setSelfBounds(bounding_box_); + abc_points_schema_.set(sample); +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_points.h b/source/blender/io/alembic/exporter/abc_writer_points.h new file mode 100644 index 00000000000..03800b80acf --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_points.h @@ -0,0 +1,52 @@ +/* + * 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. + */ +#pragma once + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_abstract.h" + +#include <Alembic/AbcGeom/OPoints.h> + +namespace blender { +namespace io { +namespace alembic { + +class ABCPointsWriter : public ABCAbstractWriter { + Alembic::AbcGeom::OPoints abc_points_; + Alembic::AbcGeom::OPointsSchema abc_points_schema_; + + public: + 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; + + protected: + virtual bool check_is_animated(const HierarchyContext &context) const override; + virtual void do_write(HierarchyContext &context) override; +}; + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.cc b/source/blender/io/alembic/exporter/abc_writer_transform.cc new file mode 100644 index 00000000000..65d6b7c5b41 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_transform.cc @@ -0,0 +1,115 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_transform.h" +#include "abc_hierarchy_iterator.h" +#include "intern/abc_axis_conversion.h" +#include "intern/abc_util.h" + +#include "BKE_object.h" + +#include "BLI_math_matrix.h" +#include "BLI_math_rotation.h" + +#include "DNA_layer_types.h" + +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.alembic"}; + +namespace blender { +namespace io { +namespace alembic { + +using Alembic::Abc::OObject; +using Alembic::AbcGeom::OXform; +using Alembic::AbcGeom::OXformSchema; +using Alembic::AbcGeom::XformSample; + +ABCTransformWriter::ABCTransformWriter(const ABCWriterConstructorArgs &args) + : ABCAbstractWriter(args) +{ + timesample_index_ = args_.abc_archive->time_sampling_index_transforms(); +} + +void ABCTransformWriter::create_alembic_objects(const HierarchyContext * /*context*/) +{ + 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(); +} + +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); + + // 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 = 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(parent_relative_matrix, rot_mat, parent_relative_matrix); + } + + /* If the object is a camera, apply an extra rotation to Maya camera orientation. */ + 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(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, 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 */ + } + + XformSample xform_sample; + xform_sample.setMatrix(convert_matrix_datatype(parent_relative_matrix)); + xform_sample.setInheritsXforms(true); + abc_xform_schema_.set(xform_sample); +} + +const OObject ABCTransformWriter::get_alembic_object() const +{ + return abc_xform_; +} + +bool ABCTransformWriter::check_is_animated(const HierarchyContext &context) const +{ + if (context.duplicator != NULL) { + /* This object is being duplicated, so could be emitted by a particle system and thus + * influenced by forces. TODO(Sybren): Make this more strict. Probably better to get from the + * depsgraph whether this object instance has a time source. */ + return true; + } + return BKE_object_moves_in_time(context.object, context.animation_check_include_parent); +} + +} // namespace alembic +} // namespace io +} // namespace blender diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.h b/source/blender/io/alembic/exporter/abc_writer_transform.h new file mode 100644 index 00000000000..950bff39c29 --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_writer_transform.h @@ -0,0 +1,47 @@ +/* + * 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 "abc_writer_abstract.h" + +#include <Alembic/AbcGeom/OXform.h> + +namespace blender { +namespace io { +namespace alembic { + +class ABCTransformWriter : public ABCAbstractWriter { + private: + Alembic::AbcGeom::OXform abc_xform_; + Alembic::AbcGeom::OXformSchema abc_xform_schema_; + + public: + explicit ABCTransformWriter(const ABCWriterConstructorArgs &args); + virtual void create_alembic_objects(const HierarchyContext *context) override; + + protected: + virtual void do_write(HierarchyContext &context) override; + virtual bool check_is_animated(const HierarchyContext &context) const override; + virtual const Alembic::Abc::OObject get_alembic_object() const override; +}; + +} // namespace alembic +} // namespace io +} // namespace blender |