/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 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 #include #ifdef WIN32 # include "BLI_path_util.h" # include "BLI_string.h" # include "utfconv.h" #endif namespace blender::io::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->filepath); 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 &r_samples) { double frame_offset = time_relative ? params.frame_start : 0.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 samples; if (params.frame_start == params.frame_end) { return TimeSamplingPtr(new TimeSampling()); // NOLINT: modernize-make-shared } get_shutter_samples(scene_fps, params, nr_of_samples, true, samples); TimeSamplingType ts(uint32_t(samples.size()), 1.0 / scene_fps); return TimeSamplingPtr(new TimeSampling(ts, samples)); // NOLINT: modernize-make-shared } static void get_frames(double scene_fps, const AlembicExportParams ¶ms, uint nr_of_samples, std::set &r_frames) { /* Get one set of shutter samples, then add those around each frame to export. */ std::vector 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 blender::io::alembic