diff options
author | Sybren A. Stüvel <sybren@blender.org> | 2020-03-06 18:19:35 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@blender.org> | 2020-03-06 18:19:45 +0300 |
commit | eb522af4fec58876ac1b0a73ad9bcdae2d82d33f (patch) | |
tree | 485c6a1fb23b5be256757375e2157378d3a5c61b /source/blender/io/alembic/intern | |
parent | ff60dd8b18ed00902e5bdfd36882072db7af8735 (diff) |
Cleanup: move Alembic, AVI, Collada, and USD to `source/blender/io`
This moves the `alembic`, `avi`, `collada`, and `usd` modules into a common
`io` directory.
This also cleans up some `#include "../../{somedir}/{somefile}.h"` by
adding `../../io/{somedir}` to `CMakeLists.txt` and then just using
`#include "{somefile}.h"`.
No functional changes.
Diffstat (limited to 'source/blender/io/alembic/intern')
43 files changed, 8366 insertions, 0 deletions
diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc new file mode 100644 index 00000000000..c5f60ac3e29 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -0,0 +1,484 @@ +/* + * 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_customdata.h" + +#include <Alembic/AbcGeom/All.h> +#include <algorithm> +#include <unordered_map> + +extern "C" { +#include "DNA_customdata_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_math_base.h" +#include "BLI_utildefines.h" + +#include "BKE_customdata.h" +} + +/* NOTE: for now only UVs and Vertex Colors are supported for streaming. + * Although Alembic only allows for a single UV layer per {I|O}Schema, and does + * not have a vertex color concept, there is a convention between DCCs to write + * such data in a way that lets other DCC know what they are for. See comments + * in the write code for the conventions. */ + +using Alembic::AbcGeom::kFacevaryingScope; +using Alembic::AbcGeom::kVertexScope; + +using Alembic::Abc::C4fArraySample; +using Alembic::Abc::UInt32ArraySample; +using Alembic::Abc::V2fArraySample; + +using Alembic::AbcGeom::OC4fGeomParam; +using Alembic::AbcGeom::OV2fGeomParam; + +static void get_uvs(const CDStreamConfig &config, + std::vector<Imath::V2f> &uvs, + std::vector<uint32_t> &uvidx, + void *cd_data) +{ + MLoopUV *mloopuv_array = static_cast<MLoopUV *>(cd_data); + + if (!mloopuv_array) { + return; + } + + const int num_poly = config.totpoly; + MPoly *polygons = config.mpoly; + MLoop *mloop = config.mloop; + + if (!config.pack_uvs) { + int cnt = 0; + uvidx.resize(config.totloop); + uvs.resize(config.totloop); + + /* Iterate in reverse order to match exported polygons. */ + for (int i = 0; i < num_poly; i++) { + MPoly ¤t_poly = polygons[i]; + MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop; + + for (int j = 0; j < current_poly.totloop; j++, cnt++) { + loopuv--; + + uvidx[cnt] = cnt; + uvs[cnt][0] = loopuv->uv[0]; + uvs[cnt][1] = loopuv->uv[1]; + } + } + } + else { + /* Mapping for indexed UVs, deduplicating UV coordinates at vertices. */ + std::vector<std::vector<uint32_t>> idx_map(config.totvert); + int idx_count = 0; + + for (int i = 0; i < num_poly; i++) { + MPoly ¤t_poly = polygons[i]; + MLoop *looppoly = mloop + current_poly.loopstart + current_poly.totloop; + MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop; + + for (int j = 0; j < current_poly.totloop; j++) { + looppoly--; + loopuv--; + + Imath::V2f uv(loopuv->uv[0], loopuv->uv[1]); + bool found_same = false; + + /* Find UV already in uvs array. */ + for (uint32_t uv_idx : idx_map[looppoly->v]) { + if (uvs[uv_idx] == uv) { + found_same = true; + uvidx.push_back(uv_idx); + break; + } + } + + /* UV doesn't exists for this vertex, add it. */ + if (!found_same) { + uint32_t uv_idx = idx_count++; + idx_map[looppoly->v].push_back(uv_idx); + uvidx.push_back(uv_idx); + uvs.push_back(uv); + } + } + } + } +} + +const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data) +{ + const int active_uvlayer = CustomData_get_active_layer(data, CD_MLOOPUV); + + if (active_uvlayer < 0) { + return ""; + } + + void *cd_data = CustomData_get_layer_n(data, CD_MLOOPUV, active_uvlayer); + + get_uvs(config, sample.uvs, sample.indices, cd_data); + + return CustomData_get_layer_name(data, CD_MLOOPUV, active_uvlayer); +} + +/* Convention to write UVs: + * - V2fGeomParam on the arbGeomParam + * - set scope as face varying + * - (optional due to its behavior) tag as UV using Alembic::AbcGeom::SetIsUV + */ +static void write_uv(const OCompoundProperty &prop, + const CDStreamConfig &config, + void *data, + const char *name) +{ + std::vector<uint32_t> indices; + std::vector<Imath::V2f> uvs; + + get_uvs(config, uvs, indices, data); + + if (indices.empty() || uvs.empty()) { + return; + } + + OV2fGeomParam param(prop, name, true, kFacevaryingScope, 1); + + OV2fGeomParam::Sample sample(V2fArraySample(&uvs.front(), uvs.size()), + UInt32ArraySample(&indices.front(), indices.size()), + kFacevaryingScope); + + param.set(sample); +} + +/* Convention to write Vertex Colors: + * - C3fGeomParam/C4fGeomParam on the arbGeomParam + * - set scope as vertex varying + */ +static void write_mcol(const OCompoundProperty &prop, + const CDStreamConfig &config, + void *data, + const char *name) +{ + const float cscale = 1.0f / 255.0f; + MPoly *polys = config.mpoly; + MLoop *mloops = config.mloop; + MCol *cfaces = static_cast<MCol *>(data); + + std::vector<Imath::C4f> buffer; + std::vector<uint32_t> indices; + + buffer.reserve(config.totvert); + indices.reserve(config.totvert); + + Imath::C4f col; + + for (int i = 0; i < config.totpoly; i++) { + MPoly *p = &polys[i]; + MCol *cface = &cfaces[p->loopstart + p->totloop]; + MLoop *mloop = &mloops[p->loopstart + p->totloop]; + + for (int j = 0; j < p->totloop; j++) { + cface--; + mloop--; + + col[0] = cface->a * cscale; + col[1] = cface->r * cscale; + col[2] = cface->g * cscale; + col[3] = cface->b * cscale; + + buffer.push_back(col); + indices.push_back(buffer.size() - 1); + } + } + + OC4fGeomParam param(prop, name, true, kFacevaryingScope, 1); + + OC4fGeomParam::Sample sample(C4fArraySample(&buffer.front(), buffer.size()), + UInt32ArraySample(&indices.front(), indices.size()), + kVertexScope); + + param.set(sample); +} + +void write_custom_data(const OCompoundProperty &prop, + const CDStreamConfig &config, + CustomData *data, + int data_type) +{ + CustomDataType cd_data_type = static_cast<CustomDataType>(data_type); + + if (!CustomData_has_layer(data, cd_data_type)) { + return; + } + + const int active_layer = CustomData_get_active_layer(data, cd_data_type); + const int tot_layers = CustomData_number_of_layers(data, cd_data_type); + + for (int i = 0; i < tot_layers; i++) { + void *cd_data = CustomData_get_layer_n(data, cd_data_type, i); + const char *name = CustomData_get_layer_name(data, cd_data_type, i); + + if (cd_data_type == CD_MLOOPUV) { + /* Already exported. */ + if (i == active_layer) { + continue; + } + + write_uv(prop, config, cd_data, name); + } + else if (cd_data_type == CD_MLOOPCOL) { + write_mcol(prop, config, cd_data, name); + } + } +} + +/* ************************************************************************** */ + +using Alembic::Abc::C3fArraySamplePtr; +using Alembic::Abc::C4fArraySamplePtr; +using Alembic::Abc::PropertyHeader; + +using Alembic::AbcGeom::IC3fGeomParam; +using Alembic::AbcGeom::IC4fGeomParam; +using Alembic::AbcGeom::IV2fGeomParam; + +static void read_uvs(const CDStreamConfig &config, + void *data, + const Alembic::AbcGeom::V2fArraySamplePtr &uvs, + const Alembic::AbcGeom::UInt32ArraySamplePtr &indices) +{ + MPoly *mpolys = config.mpoly; + MLoopUV *mloopuvs = static_cast<MLoopUV *>(data); + + unsigned int uv_index, loop_index, rev_loop_index; + + for (int i = 0; i < config.totpoly; i++) { + MPoly &poly = mpolys[i]; + unsigned int rev_loop_offset = poly.loopstart + poly.totloop - 1; + + for (int f = 0; f < poly.totloop; f++) { + loop_index = poly.loopstart + f; + rev_loop_index = rev_loop_offset - f; + uv_index = (*indices)[loop_index]; + const Imath::V2f &uv = (*uvs)[uv_index]; + + MLoopUV &loopuv = mloopuvs[rev_loop_index]; + loopuv.uv[0] = uv[0]; + loopuv.uv[1] = uv[1]; + } + } +} + +static size_t mcols_out_of_bounds_check(const size_t color_index, + const size_t array_size, + const std::string &iobject_full_name, + const PropertyHeader &prop_header, + bool &r_is_out_of_bounds, + bool &r_bounds_warning_given) +{ + if (color_index < array_size) { + return color_index; + } + + if (!r_bounds_warning_given) { + std::cerr << "Alembic: color index out of bounds " + "reading face colors for object " + << iobject_full_name << ", property " << prop_header.getName() << std::endl; + r_bounds_warning_given = true; + } + r_is_out_of_bounds = true; + return 0; +} + +static void read_custom_data_mcols(const std::string &iobject_full_name, + const ICompoundProperty &arbGeomParams, + const PropertyHeader &prop_header, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr(); + C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr(); + Alembic::Abc::UInt32ArraySamplePtr indices; + bool use_c3f_ptr; + bool is_facevarying; + + /* Find the correct interpretation of the data */ + if (IC3fGeomParam::matches(prop_header)) { + IC3fGeomParam color_param(arbGeomParams, prop_header.getName()); + IC3fGeomParam::Sample sample; + BLI_assert(!strcmp("rgb", color_param.getInterpretation())); + + color_param.getIndexed(sample, iss); + is_facevarying = sample.getScope() == kFacevaryingScope && + config.totloop == sample.getIndices()->size(); + + c3f_ptr = sample.getVals(); + indices = sample.getIndices(); + use_c3f_ptr = true; + } + else if (IC4fGeomParam::matches(prop_header)) { + IC4fGeomParam color_param(arbGeomParams, prop_header.getName()); + IC4fGeomParam::Sample sample; + BLI_assert(!strcmp("rgba", color_param.getInterpretation())); + + color_param.getIndexed(sample, iss); + is_facevarying = sample.getScope() == kFacevaryingScope && + config.totloop == sample.getIndices()->size(); + + c4f_ptr = sample.getVals(); + indices = sample.getIndices(); + use_c3f_ptr = false; + } + else { + /* this won't happen due to the checks in read_custom_data() */ + return; + } + BLI_assert(c3f_ptr || c4f_ptr); + + /* Read the vertex colors */ + void *cd_data = config.add_customdata_cb( + config.mesh, prop_header.getName().c_str(), CD_MLOOPCOL); + MCol *cfaces = static_cast<MCol *>(cd_data); + MPoly *mpolys = config.mpoly; + MLoop *mloops = config.mloop; + + size_t face_index = 0; + size_t color_index; + bool bounds_warning_given = false; + + /* The colors can go through two layers of indexing. Often the 'indices' + * array doesn't do anything (i.e. indices[n] = n), but when it does, it's + * important. Blender 2.79 writes indices incorrectly (see T53745), which + * is why we have to check for indices->size() > 0 */ + bool use_dual_indexing = is_facevarying && indices->size() > 0; + + for (int i = 0; i < config.totpoly; i++) { + MPoly *poly = &mpolys[i]; + MCol *cface = &cfaces[poly->loopstart + poly->totloop]; + MLoop *mloop = &mloops[poly->loopstart + poly->totloop]; + + for (int j = 0; j < poly->totloop; j++, face_index++) { + cface--; + mloop--; + + color_index = is_facevarying ? face_index : mloop->v; + if (use_dual_indexing) { + color_index = (*indices)[color_index]; + } + if (use_c3f_ptr) { + bool is_mcols_out_of_bounds = false; + color_index = mcols_out_of_bounds_check(color_index, + c3f_ptr->size(), + iobject_full_name, + prop_header, + is_mcols_out_of_bounds, + bounds_warning_given); + if (is_mcols_out_of_bounds) { + continue; + } + const Imath::C3f &color = (*c3f_ptr)[color_index]; + cface->a = unit_float_to_uchar_clamp(color[0]); + cface->r = unit_float_to_uchar_clamp(color[1]); + cface->g = unit_float_to_uchar_clamp(color[2]); + cface->b = 255; + } + else { + bool is_mcols_out_of_bounds = false; + color_index = mcols_out_of_bounds_check(color_index, + c4f_ptr->size(), + iobject_full_name, + prop_header, + is_mcols_out_of_bounds, + bounds_warning_given); + if (is_mcols_out_of_bounds) { + continue; + } + const Imath::C4f &color = (*c4f_ptr)[color_index]; + cface->a = unit_float_to_uchar_clamp(color[0]); + cface->r = unit_float_to_uchar_clamp(color[1]); + cface->g = unit_float_to_uchar_clamp(color[2]); + cface->b = unit_float_to_uchar_clamp(color[3]); + } + } + } +} + +static void read_custom_data_uvs(const ICompoundProperty &prop, + const PropertyHeader &prop_header, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + IV2fGeomParam uv_param(prop, prop_header.getName()); + + if (!uv_param.isIndexed()) { + return; + } + + IV2fGeomParam::Sample sample; + uv_param.getIndexed(sample, iss); + + if (uv_param.getScope() != kFacevaryingScope) { + return; + } + + void *cd_data = config.add_customdata_cb(config.mesh, prop_header.getName().c_str(), CD_MLOOPUV); + + read_uvs(config, cd_data, sample.getVals(), sample.getIndices()); +} + +void read_custom_data(const std::string &iobject_full_name, + const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + if (!prop.valid()) { + return; + } + + int num_uvs = 0; + int num_colors = 0; + + const size_t num_props = prop.getNumProperties(); + + for (size_t i = 0; i < num_props; i++) { + const Alembic::Abc::PropertyHeader &prop_header = prop.getPropertyHeader(i); + + /* Read UVs according to convention. */ + if (IV2fGeomParam::matches(prop_header) && Alembic::AbcGeom::isUV(prop_header)) { + if (++num_uvs > MAX_MTFACE) { + continue; + } + + read_custom_data_uvs(prop, prop_header, config, iss); + continue; + } + + /* Read vertex colors according to convention. */ + if (IC3fGeomParam::matches(prop_header) || IC4fGeomParam::matches(prop_header)) { + if (++num_colors > MAX_MCOL) { + continue; + } + + read_custom_data_mcols(iobject_full_name, prop, prop_header, config, iss); + continue; + } + } +} diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h new file mode 100644 index 00000000000..6107e230627 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -0,0 +1,104 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_CUSTOMDATA_H__ +#define __ABC_CUSTOMDATA_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +struct CustomData; +struct MLoop; +struct MLoopUV; +struct MPoly; +struct MVert; +struct Mesh; + +using Alembic::Abc::ICompoundProperty; +using Alembic::Abc::OCompoundProperty; + +struct UVSample { + std::vector<Imath::V2f> uvs; + std::vector<uint32_t> indices; +}; + +struct CDStreamConfig { + MLoop *mloop; + int totloop; + + MPoly *mpoly; + int totpoly; + + MVert *mvert; + int totvert; + + MLoopUV *mloopuv; + + CustomData *loopdata; + + bool pack_uvs; + + /* TODO(kevin): might need a better way to handle adding and/or updating + * custom data such that it updates the custom data holder and its pointers properly. */ + Mesh *mesh; + void *(*add_customdata_cb)(Mesh *mesh, const char *name, int data_type); + + float weight; + float time; + Alembic::AbcGeom::index_t index; + Alembic::AbcGeom::index_t ceil_index; + + CDStreamConfig() + : mloop(NULL), + totloop(0), + mpoly(NULL), + totpoly(0), + totvert(0), + pack_uvs(false), + mesh(NULL), + add_customdata_cb(NULL), + weight(0.0f), + time(0.0f), + index(0), + ceil_index(0) + { + } +}; + +/* Get the UVs for the main UV property on a OSchema. + * Returns the name of the UV layer. + * + * For now the active layer is used, maybe needs a better way to choose this. */ +const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data); + +void write_custom_data(const OCompoundProperty &prop, + const CDStreamConfig &config, + CustomData *data, + int data_type); + +void read_custom_data(const std::string &iobject_full_name, + const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss); + +#endif /* __ABC_CUSTOMDATA_H__ */ diff --git a/source/blender/io/alembic/intern/abc_exporter.cc b/source/blender/io/alembic/intern/abc_exporter.cc new file mode 100644 index 00000000000..a58b0a29e5e --- /dev/null +++ b/source/blender/io/alembic/intern/abc_exporter.cc @@ -0,0 +1,677 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_exporter.h" + +#include <cmath> + +#include "abc_writer_archive.h" +#include "abc_writer_camera.h" +#include "abc_writer_curves.h" +#include "abc_writer_hair.h" +#include "abc_writer_mball.h" +#include "abc_writer_mesh.h" +#include "abc_writer_nurbs.h" +#include "abc_writer_points.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_camera_types.h" +#include "DNA_curve_types.h" +#include "DNA_meta_types.h" +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" /* for FILE_MAX */ +#include "DNA_fluid_types.h" + +#include "BLI_string.h" + +#ifdef WIN32 +/* needed for MSCV because of snprintf from BLI_string */ +# include "BLI_winstuff.h" +#endif + +#include "BKE_anim.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_layer.h" +#include "BKE_main.h" +#include "BKE_mball.h" +#include "BKE_modifier.h" +#include "BKE_particle.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::Abc::OBox3dProperty; +using Alembic::Abc::TimeSamplingPtr; + +/* ************************************************************************** */ + +ExportSettings::ExportSettings() + : scene(NULL), + view_layer(NULL), + depsgraph(NULL), + logger(), + selected_only(false), + visible_objects_only(false), + renderable_only(false), + frame_start(1), + frame_end(1), + frame_samples_xform(1), + frame_samples_shape(1), + shutter_open(0.0), + shutter_close(1.0), + global_scale(1.0f), + flatten_hierarchy(false), + export_normals(false), + export_uvs(false), + export_vcols(false), + export_face_sets(false), + export_vweigths(false), + export_hair(true), + export_particles(true), + apply_subdiv(false), + use_subdiv_schema(false), + export_child_hairs(true), + export_ogawa(true), + pack_uv(false), + triangulate(false), + quad_method(0), + ngon_method(0) +{ +} + +static bool object_is_smoke_sim(Object *ob) +{ + ModifierData *md = modifiers_findByType(ob, eModifierType_Fluid); + + if (md) { + FluidModifierData *smd = reinterpret_cast<FluidModifierData *>(md); + return (smd->type == MOD_FLUID_TYPE_DOMAIN && smd->domain && + smd->domain->type == FLUID_DOMAIN_TYPE_GAS); + } + + return false; +} + +static bool object_type_is_exportable(Scene *scene, Object *ob) +{ + switch (ob->type) { + case OB_MESH: + if (object_is_smoke_sim(ob)) { + return false; + } + + return true; + case OB_EMPTY: + case OB_CURVE: + case OB_SURF: + case OB_CAMERA: + return true; + case OB_MBALL: + return AbcMBallWriter::isBasisBall(scene, ob); + default: + return false; + } +} + +/** + * Returns whether this object should be exported into the Alembic file. + * + * \param settings: export settings, used for options like 'selected only'. + * \param ob: the object's base in question. + * \param is_duplicated: Normally false; true when the object is instanced + * into the scene by a dupli-object (e.g. part of a dupligroup). + * This ignores selection and layer visibility, + * and assumes that the dupli-object itself (e.g. the group-instantiating empty) is exported. + */ +static bool export_object(const ExportSettings *const settings, + const Base *const base, + bool is_duplicated) +{ + if (!is_duplicated) { + View3D *v3d = NULL; + + /* These two tests only make sense when the object isn't being instanced + * into the scene. When it is, its exportability is determined by + * its dupli-object and the DupliObject::no_draw property. */ + if (settings->selected_only && !BASE_SELECTED(v3d, base)) { + return false; + } + // FIXME Sybren: handle these cleanly (maybe just remove code), + // now using active scene layer instead. + if (settings->visible_objects_only && !BASE_VISIBLE(v3d, base)) { + return false; + } + } + + Object *ob_eval = DEG_get_evaluated_object(settings->depsgraph, base->object); + if ((ob_eval->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0) { + /* XXX fix after 2.80: the object was not part of the depsgraph, and thus we cannot get the + * evaluated copy to export. This will be handled more elegantly in the new + * AbstractHierarchyIterator that Sybren is working on. This condition is temporary, and avoids + * a BLI_assert() failure getting the evaluated mesh of this object. */ + return false; + } + + // if (settings->renderable_only && (ob->restrictflag & OB_RESTRICT_RENDER)) { + // return false; + // } + + return true; +} + +/* ************************************************************************** */ + +AbcExporter::AbcExporter(Main *bmain, const char *filename, ExportSettings &settings) + : m_bmain(bmain), + m_settings(settings), + m_filename(filename), + m_trans_sampling_index(0), + m_shape_sampling_index(0), + m_writer(NULL) +{ +} + +AbcExporter::~AbcExporter() +{ + /* Free xforms map */ + m_xforms_type::iterator it_x, e_x; + for (it_x = m_xforms.begin(), e_x = m_xforms.end(); it_x != e_x; ++it_x) { + delete it_x->second; + } + + /* Free shapes vector */ + for (int i = 0, e = m_shapes.size(); i != e; i++) { + delete m_shapes[i]; + } + + delete m_writer; +} + +void AbcExporter::getShutterSamples(unsigned int nr_of_samples, + bool time_relative, + std::vector<double> &samples) +{ + Scene *scene = m_settings.scene; /* for use in the FPS macro */ + samples.clear(); + + unsigned int frame_offset = time_relative ? m_settings.frame_start : 0; + double time_factor = time_relative ? FPS : 1.0; + double shutter_open = m_settings.shutter_open; + double shutter_close = m_settings.shutter_close; + double time_inc = (shutter_close - shutter_open) / nr_of_samples; + + /* sample between shutter open & close */ + for (int sample = 0; sample < nr_of_samples; sample++) { + double sample_time = shutter_open + time_inc * sample; + double time = (frame_offset + sample_time) / time_factor; + + samples.push_back(time); + } +} + +Alembic::Abc::TimeSamplingPtr AbcExporter::createTimeSampling(double step) +{ + std::vector<double> samples; + + if (m_settings.frame_start == m_settings.frame_end) { + return TimeSamplingPtr(new Alembic::Abc::TimeSampling()); + } + + getShutterSamples(step, true, samples); + + /* TODO(Sybren): shouldn't we use the FPS macro here? */ + Alembic::Abc::TimeSamplingType ts(static_cast<uint32_t>(samples.size()), + 1.0 / m_settings.scene->r.frs_sec); + + return TimeSamplingPtr(new Alembic::Abc::TimeSampling(ts, samples)); +} + +void AbcExporter::getFrameSet(unsigned int nr_of_samples, std::set<double> &frames) +{ + frames.clear(); + + std::vector<double> shutter_samples; + + getShutterSamples(nr_of_samples, false, shutter_samples); + + for (double frame = m_settings.frame_start; frame <= m_settings.frame_end; frame += 1.0) { + for (size_t j = 0; j < nr_of_samples; j++) { + frames.insert(frame + shutter_samples[j]); + } + } +} + +void AbcExporter::operator()(short *do_update, float *progress, bool *was_canceled) +{ + std::string abc_scene_name; + + if (m_bmain->name[0] != '\0') { + char scene_file_name[FILE_MAX]; + BLI_strncpy(scene_file_name, m_bmain->name, FILE_MAX); + abc_scene_name = scene_file_name; + } + else { + abc_scene_name = "untitled"; + } + + m_writer = new ArchiveWriter( + m_filename, abc_scene_name, m_settings.scene, m_settings.export_ogawa); + + /* Create time samplings for transforms and shapes. */ + + TimeSamplingPtr trans_time = createTimeSampling(m_settings.frame_samples_xform); + + m_trans_sampling_index = m_writer->archive().addTimeSampling(*trans_time); + + TimeSamplingPtr shape_time; + + if ((m_settings.frame_samples_shape == m_settings.frame_samples_xform) || + (m_settings.frame_start == m_settings.frame_end)) { + shape_time = trans_time; + m_shape_sampling_index = m_trans_sampling_index; + } + else { + shape_time = createTimeSampling(m_settings.frame_samples_shape); + m_shape_sampling_index = m_writer->archive().addTimeSampling(*shape_time); + } + + OBox3dProperty archive_bounds_prop = Alembic::AbcGeom::CreateOArchiveBounds( + m_writer->archive(), m_trans_sampling_index); + + createTransformWritersHierarchy(); + createShapeWriters(); + + /* Make a list of frames to export. */ + + std::set<double> xform_frames; + getFrameSet(m_settings.frame_samples_xform, xform_frames); + + std::set<double> shape_frames; + getFrameSet(m_settings.frame_samples_shape, shape_frames); + + /* Merge all frames needed. */ + std::set<double> frames(xform_frames); + frames.insert(shape_frames.begin(), shape_frames.end()); + + /* Export all frames. */ + + std::set<double>::const_iterator begin = frames.begin(); + std::set<double>::const_iterator end = frames.end(); + + const float size = static_cast<float>(frames.size()); + size_t i = 0; + + for (; begin != end; ++begin) { + *progress = (++i / size); + *do_update = 1; + + if (G.is_break) { + *was_canceled = true; + break; + } + + const double frame = *begin; + + /* 'frame' is offset by start frame, so need to cancel the offset. */ + setCurrentFrame(m_bmain, frame); + + if (shape_frames.count(frame) != 0) { + for (int i = 0, e = m_shapes.size(); i != e; i++) { + m_shapes[i]->write(); + } + } + + if (xform_frames.count(frame) == 0) { + continue; + } + + m_xforms_type::iterator xit, xe; + for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) { + xit->second->write(); + } + + /* Save the archive 's bounding box. */ + Imath::Box3d bounds; + + for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) { + Imath::Box3d box = xit->second->bounds(); + bounds.extendBy(box); + } + + archive_bounds_prop.set(bounds); + } +} + +void AbcExporter::createTransformWritersHierarchy() +{ + for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base; + base = base->next) { + Object *ob = base->object; + + if (export_object(&m_settings, base, false)) { + switch (ob->type) { + case OB_LAMP: + case OB_LATTICE: + case OB_SPEAKER: + /* We do not export transforms for objects of these classes. */ + break; + default: + exploreTransform(base, ob, ob->parent, NULL); + } + } + } +} + +void AbcExporter::exploreTransform(Base *base, + Object *object, + Object *parent, + Object *dupliObParent) +{ + /* If an object isn't exported itself, its duplilist shouldn't be + * exported either. */ + if (!export_object(&m_settings, base, dupliObParent != NULL)) { + return; + } + + Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object); + if (object_type_is_exportable(m_settings.scene, ob)) { + createTransformWriter(ob, parent, dupliObParent); + } + + ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob); + + if (lb) { + DupliObject *link = static_cast<DupliObject *>(lb->first); + Object *dupli_ob = NULL; + Object *dupli_parent = NULL; + + for (; link; link = link->next) { + /* This skips things like custom bone shapes. */ + if (m_settings.renderable_only && link->no_draw) { + continue; + } + + if (link->type == OB_DUPLICOLLECTION) { + dupli_ob = link->ob; + dupli_parent = (dupli_ob->parent) ? dupli_ob->parent : ob; + + exploreTransform(base, dupli_ob, dupli_parent, ob); + } + } + + free_object_duplilist(lb); + } +} + +AbcTransformWriter *AbcExporter::createTransformWriter(Object *ob, + Object *parent, + Object *dupliObParent) +{ + /* An object should not be its own parent, or we'll get infinite loops. */ + BLI_assert(ob != parent); + BLI_assert(ob != dupliObParent); + + std::string name; + if (m_settings.flatten_hierarchy) { + name = get_id_name(ob); + } + else { + name = get_object_dag_path_name(ob, dupliObParent); + } + + /* check if we have already created a transform writer for this object */ + AbcTransformWriter *my_writer = getXForm(name); + if (my_writer != NULL) { + return my_writer; + } + + AbcTransformWriter *parent_writer = NULL; + Alembic::Abc::OObject alembic_parent; + + if (m_settings.flatten_hierarchy || parent == NULL) { + /* Parentless objects still have the "top object" as parent + * in Alembic. */ + alembic_parent = m_writer->archive().getTop(); + } + else { + /* Since there are so many different ways to find parents (as evident + * in the number of conditions below), we can't really look up the + * parent by name. We'll just call createTransformWriter(), which will + * return the parent's AbcTransformWriter pointer. */ + if (parent->parent) { + if (parent == dupliObParent) { + parent_writer = createTransformWriter(parent, parent->parent, NULL); + } + else { + parent_writer = createTransformWriter(parent, parent->parent, dupliObParent); + } + } + else if (parent == dupliObParent) { + if (dupliObParent->parent == NULL) { + parent_writer = createTransformWriter(parent, NULL, NULL); + } + else { + parent_writer = createTransformWriter( + parent, dupliObParent->parent, dupliObParent->parent); + } + } + else { + parent_writer = createTransformWriter(parent, dupliObParent, dupliObParent); + } + + BLI_assert(parent_writer); + alembic_parent = parent_writer->alembicXform(); + } + + my_writer = new AbcTransformWriter( + ob, alembic_parent, parent_writer, m_trans_sampling_index, m_settings); + + /* When flattening, the matrix of the dupliobject has to be added. */ + if (m_settings.flatten_hierarchy && dupliObParent) { + my_writer->m_proxy_from = dupliObParent; + } + + m_xforms[name] = my_writer; + return my_writer; +} + +void AbcExporter::createShapeWriters() +{ + for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base; + base = base->next) { + exploreObject(base, base->object, NULL); + } +} + +void AbcExporter::exploreObject(Base *base, Object *object, Object *dupliObParent) +{ + /* If an object isn't exported itself, its duplilist shouldn't be + * exported either. */ + if (!export_object(&m_settings, base, dupliObParent != NULL)) { + return; + } + + Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object); + createShapeWriter(ob, dupliObParent); + + ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob); + + if (lb) { + DupliObject *link = static_cast<DupliObject *>(lb->first); + + for (; link; link = link->next) { + /* This skips things like custom bone shapes. */ + if (m_settings.renderable_only && link->no_draw) { + continue; + } + if (link->type == OB_DUPLICOLLECTION) { + exploreObject(base, link->ob, ob); + } + } + + free_object_duplilist(lb); + } +} + +void AbcExporter::createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform) +{ + if (!m_settings.export_hair && !m_settings.export_particles) { + return; + } + + ParticleSystem *psys = static_cast<ParticleSystem *>(ob->particlesystem.first); + + for (; psys; psys = psys->next) { + if (!psys_check_enabled(ob, psys, G.is_rendering) || !psys->part) { + continue; + } + + if (m_settings.export_hair && psys->part->type == PART_HAIR) { + m_settings.export_child_hairs = true; + m_shapes.push_back(new AbcHairWriter(ob, xform, m_shape_sampling_index, m_settings, psys)); + } + else if (m_settings.export_particles && + (psys->part->type == PART_EMITTER || psys->part->type == PART_FLUID_FLIP || + psys->part->type == PART_FLUID_SPRAY || psys->part->type == PART_FLUID_BUBBLE || + psys->part->type == PART_FLUID_FOAM || psys->part->type == PART_FLUID_TRACER || + psys->part->type == PART_FLUID_SPRAYFOAM || + psys->part->type == PART_FLUID_SPRAYBUBBLE || + psys->part->type == PART_FLUID_FOAMBUBBLE || + psys->part->type == PART_FLUID_SPRAYFOAMBUBBLE)) { + m_shapes.push_back(new AbcPointsWriter(ob, xform, m_shape_sampling_index, m_settings, psys)); + } + } +} + +void AbcExporter::createShapeWriter(Object *ob, Object *dupliObParent) +{ + if (!object_type_is_exportable(m_settings.scene, ob)) { + return; + } + + std::string name; + + if (m_settings.flatten_hierarchy) { + name = get_id_name(ob); + } + else { + name = get_object_dag_path_name(ob, dupliObParent); + } + + AbcTransformWriter *xform = getXForm(name); + + if (!xform) { + ABC_LOG(m_settings.logger) << __func__ << ": xform " << name << " is NULL\n"; + return; + } + + createParticleSystemsWriters(ob, xform); + + switch (ob->type) { + case OB_MESH: { + Mesh *me = static_cast<Mesh *>(ob->data); + + if (!me) { + return; + } + + m_shapes.push_back(new AbcMeshWriter(ob, xform, m_shape_sampling_index, m_settings)); + break; + } + case OB_SURF: { + Curve *cu = static_cast<Curve *>(ob->data); + + if (!cu) { + return; + } + + AbcObjectWriter *writer; + if (m_settings.curves_as_mesh) { + writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings); + } + else { + writer = new AbcNurbsWriter(ob, xform, m_shape_sampling_index, m_settings); + } + m_shapes.push_back(writer); + break; + } + case OB_CURVE: { + Curve *cu = static_cast<Curve *>(ob->data); + + if (!cu) { + return; + } + + AbcObjectWriter *writer; + if (m_settings.curves_as_mesh) { + writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings); + } + else { + writer = new AbcCurveWriter(ob, xform, m_shape_sampling_index, m_settings); + } + m_shapes.push_back(writer); + break; + } + case OB_CAMERA: { + Camera *cam = static_cast<Camera *>(ob->data); + + if (cam->type == CAM_PERSP) { + m_shapes.push_back(new AbcCameraWriter(ob, xform, m_shape_sampling_index, m_settings)); + } + + break; + } + case OB_MBALL: { + MetaBall *mball = static_cast<MetaBall *>(ob->data); + if (!mball) { + return; + } + + m_shapes.push_back( + new AbcMBallWriter(m_bmain, ob, xform, m_shape_sampling_index, m_settings)); + break; + } + } +} + +AbcTransformWriter *AbcExporter::getXForm(const std::string &name) +{ + std::map<std::string, AbcTransformWriter *>::iterator it = m_xforms.find(name); + + if (it == m_xforms.end()) { + return NULL; + } + + return it->second; +} + +void AbcExporter::setCurrentFrame(Main *bmain, double t) +{ + m_settings.scene->r.cfra = static_cast<int>(t); + m_settings.scene->r.subframe = static_cast<float>(t) - m_settings.scene->r.cfra; + BKE_scene_graph_update_for_newframe(m_settings.depsgraph, bmain); +} diff --git a/source/blender/io/alembic/intern/abc_exporter.h b/source/blender/io/alembic/intern/abc_exporter.h new file mode 100644 index 00000000000..398004d2ec5 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_exporter.h @@ -0,0 +1,128 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_EXPORTER_H__ +#define __ABC_EXPORTER_H__ + +#include <Alembic/Abc/All.h> +#include <map> +#include <set> +#include <vector> + +#include "abc_util.h" + +class AbcObjectWriter; +class AbcTransformWriter; +class ArchiveWriter; + +struct Base; +struct Depsgraph; +struct Main; +struct Object; +struct Scene; +struct ViewLayer; + +struct ExportSettings { + ExportSettings(); + + Scene *scene; + /** Scene layer to export; all its objects will be exported, unless selected_only=true. */ + ViewLayer *view_layer; + Depsgraph *depsgraph; + SimpleLogger logger; + + bool selected_only; + bool visible_objects_only; + bool renderable_only; + + double frame_start, frame_end; + double frame_samples_xform; + double frame_samples_shape; + double shutter_open; + double shutter_close; + float global_scale; + + bool flatten_hierarchy; + + bool export_normals; + bool export_uvs; + bool export_vcols; + bool export_face_sets; + bool export_vweigths; + bool export_hair; + bool export_particles; + + bool apply_subdiv; + bool curves_as_mesh; + bool use_subdiv_schema; + bool export_child_hairs; + bool export_ogawa; + bool pack_uv; + bool triangulate; + + int quad_method; + int ngon_method; +}; + +class AbcExporter { + Main *m_bmain; + ExportSettings &m_settings; + + const char *m_filename; + + unsigned int m_trans_sampling_index, m_shape_sampling_index; + + ArchiveWriter *m_writer; + + /* mapping from name to transform writer */ + typedef std::map<std::string, AbcTransformWriter *> m_xforms_type; + m_xforms_type m_xforms; + + std::vector<AbcObjectWriter *> m_shapes; + + public: + AbcExporter(Main *bmain, const char *filename, ExportSettings &settings); + ~AbcExporter(); + + void operator()(short *do_update, float *progress, bool *was_canceled); + + protected: + void getShutterSamples(unsigned int nr_of_samples, + bool time_relative, + std::vector<double> &samples); + void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames); + + private: + Alembic::Abc::TimeSamplingPtr createTimeSampling(double step); + + void createTransformWritersHierarchy(); + AbcTransformWriter *createTransformWriter(Object *ob, Object *parent, Object *dupliObParent); + void exploreTransform(Base *base, Object *object, Object *parent, Object *dupliObParent); + void exploreObject(Base *base, Object *object, Object *dupliObParent); + void createShapeWriters(); + void createShapeWriter(Object *ob, Object *dupliObParent); + void createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform); + + AbcTransformWriter *getXForm(const std::string &name); + + void setCurrentFrame(Main *bmain, double t); +}; + +#endif /* __ABC_EXPORTER_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_archive.cc b/source/blender/io/alembic/intern/abc_reader_archive.cc new file mode 100644 index 00000000000..6ad44553701 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_archive.cc @@ -0,0 +1,140 @@ +/* + * 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_reader_archive.h" + +extern "C" { +#include "BKE_main.h" + +#include "BLI_path_util.h" +#include "BLI_string.h" +} + +#ifdef WIN32 +# include "utfconv.h" +#endif + +#include <fstream> + +using Alembic::Abc::ErrorHandler; +using Alembic::Abc::Exception; +using Alembic::Abc::IArchive; +using Alembic::Abc::kWrapExisting; + +static IArchive open_archive(const std::string &filename, + const std::vector<std::istream *> &input_streams, + bool &is_hdf5) +{ + is_hdf5 = false; + + try { + Alembic::AbcCoreOgawa::ReadArchive archive_reader(input_streams); + + return IArchive(archive_reader(filename), kWrapExisting, ErrorHandler::kThrowPolicy); + } + catch (const Exception &e) { + std::cerr << e.what() << '\n'; + +#ifdef WITH_ALEMBIC_HDF5 + try { + is_hdf5 = true; + Alembic::AbcCoreAbstract::ReadArraySampleCachePtr cache_ptr; + + return IArchive(Alembic::AbcCoreHDF5::ReadArchive(), + filename.c_str(), + ErrorHandler::kThrowPolicy, + cache_ptr); + } + catch (const Exception &) { + std::cerr << e.what() << '\n'; + return IArchive(); + } +#else + /* Inspect the file to see whether it's really a HDF5 file. */ + char header[4]; /* char(0x89) + "HDF" */ + std::ifstream the_file(filename.c_str(), std::ios::in | std::ios::binary); + if (!the_file) { + std::cerr << "Unable to open " << filename << std::endl; + } + else if (!the_file.read(header, sizeof(header))) { + std::cerr << "Unable to read from " << filename << std::endl; + } + else if (strncmp(header + 1, "HDF", 3)) { + std::cerr << filename << " has an unknown file format, unable to read." << std::endl; + } + else { + is_hdf5 = true; + std::cerr << filename << " is in the obsolete HDF5 format, unable to read." << std::endl; + } + + if (the_file.is_open()) { + the_file.close(); + } + + return IArchive(); +#endif + } + + return IArchive(); +} + +ArchiveReader::ArchiveReader(struct Main *bmain, const char *filename) +{ + char abs_filename[FILE_MAX]; + BLI_strncpy(abs_filename, filename, FILE_MAX); + BLI_path_abs(abs_filename, BKE_main_blendfile_path(bmain)); + +#ifdef WIN32 + UTF16_ENCODE(abs_filename); + std::wstring wstr(abs_filename_16); + m_infile.open(wstr.c_str(), std::ios::in | std::ios::binary); + UTF16_UN_ENCODE(abs_filename); +#else + m_infile.open(abs_filename, std::ios::in | std::ios::binary); +#endif + + m_streams.push_back(&m_infile); + + m_archive = open_archive(abs_filename, m_streams, m_is_hdf5); + + /* We can't open an HDF5 file from a stream, so close it. */ + if (m_is_hdf5) { + m_infile.close(); + m_streams.clear(); + } +} + +bool ArchiveReader::is_hdf5() const +{ + return m_is_hdf5; +} + +bool ArchiveReader::valid() const +{ + return m_archive.valid(); +} + +Alembic::Abc::IObject ArchiveReader::getTop() +{ + return m_archive.getTop(); +} diff --git a/source/blender/io/alembic/intern/abc_reader_archive.h b/source/blender/io/alembic/intern/abc_reader_archive.h new file mode 100644 index 00000000000..bdb53bd0b8c --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_archive.h @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_ARCHIVE_H__ +#define __ABC_READER_ARCHIVE_H__ + +#include <Alembic/Abc/All.h> + +#ifdef WITH_ALEMBIC_HDF5 +# include <Alembic/AbcCoreHDF5/All.h> +#endif + +#include <Alembic/AbcCoreOgawa/All.h> + +#include <fstream> + +struct Main; +struct Scene; + +/* Wrappers around input and output archives. The goal is to be able to use + * streams so that unicode paths work on Windows (T49112), and to make sure that + * the stream objects remain valid as long as the archives are open. + */ + +class ArchiveReader { + Alembic::Abc::IArchive m_archive; + std::ifstream m_infile; + std::vector<std::istream *> m_streams; + bool m_is_hdf5; + + public: + ArchiveReader(struct Main *bmain, const char *filename); + + bool valid() const; + + /** + * Returns true when either Blender is compiled with HDF5 support and + * the archive was successfully opened (valid() will also return true), + * or when Blender was built without HDF5 support but a HDF5 file was + * detected (valid() will return false). + */ + bool is_hdf5() const; + + Alembic::Abc::IObject getTop(); +}; + +#endif /* __ABC_READER_ARCHIVE_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_camera.cc b/source/blender/io/alembic/intern/abc_reader_camera.cc new file mode 100644 index 00000000000..ab506f32cbe --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_camera.cc @@ -0,0 +1,113 @@ +/* + * 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_reader_camera.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_camera_types.h" +#include "DNA_object_types.h" + +#include "BKE_camera.h" +#include "BKE_object.h" + +#include "BLI_math.h" +} + +using Alembic::AbcGeom::CameraSample; +using Alembic::AbcGeom::ICamera; +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::IFloatProperty; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::kWrapExisting; + +AbcCameraReader::AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + ICamera abc_cam(m_iobject, kWrapExisting); + m_schema = abc_cam.getSchema(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcCameraReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcCameraReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::ICamera::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to Camera when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_CAMERA) { + *err_str = "Object type mismatch, Alembic object path points to Camera."; + return false; + } + + return true; +} + +void AbcCameraReader::readObjectData(Main *bmain, const ISampleSelector &sample_sel) +{ + Camera *bcam = static_cast<Camera *>(BKE_camera_add(bmain, m_data_name.c_str())); + + CameraSample cam_sample; + m_schema.get(cam_sample, sample_sel); + + ICompoundProperty customDataContainer = m_schema.getUserProperties(); + + if (customDataContainer.valid() && customDataContainer.getPropertyHeader("stereoDistance") && + customDataContainer.getPropertyHeader("eyeSeparation")) { + IFloatProperty convergence_plane(customDataContainer, "stereoDistance"); + IFloatProperty eye_separation(customDataContainer, "eyeSeparation"); + + bcam->stereo.interocular_distance = eye_separation.getValue(sample_sel); + bcam->stereo.convergence_distance = convergence_plane.getValue(sample_sel); + } + + const float lens = static_cast<float>(cam_sample.getFocalLength()); + const float apperture_x = static_cast<float>(cam_sample.getHorizontalAperture()); + const float apperture_y = static_cast<float>(cam_sample.getVerticalAperture()); + const float h_film_offset = static_cast<float>(cam_sample.getHorizontalFilmOffset()); + const float v_film_offset = static_cast<float>(cam_sample.getVerticalFilmOffset()); + const float film_aspect = apperture_x / apperture_y; + + bcam->lens = lens; + bcam->sensor_x = apperture_x * 10; + bcam->sensor_y = apperture_y * 10; + bcam->shiftx = h_film_offset / apperture_x; + bcam->shifty = v_film_offset / apperture_y / film_aspect; + bcam->clip_start = max_ff(0.1f, static_cast<float>(cam_sample.getNearClippingPlane())); + bcam->clip_end = static_cast<float>(cam_sample.getFarClippingPlane()); + bcam->dof.focus_distance = static_cast<float>(cam_sample.getFocusDistance()); + bcam->dof.aperture_fstop = static_cast<float>(cam_sample.getFStop()); + + m_object = BKE_object_add_only_object(bmain, OB_CAMERA, m_object_name.c_str()); + m_object->data = bcam; +} diff --git a/source/blender/io/alembic/intern/abc_reader_camera.h b/source/blender/io/alembic/intern/abc_reader_camera.h new file mode 100644 index 00000000000..1d9763b0454 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_camera.h @@ -0,0 +1,40 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_CAMERA_H__ +#define __ABC_READER_CAMERA_H__ + +#include "abc_reader_object.h" + +class AbcCameraReader : public AbcObjectReader { + Alembic::AbcGeom::ICameraSchema m_schema; + + public: + AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); +}; + +#endif /* __ABC_READER_CAMERA_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc new file mode 100644 index 00000000000..1be164c7c94 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -0,0 +1,354 @@ +/* + * 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_reader_curves.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +#include <cstdio> + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +} + +using Alembic::Abc::FloatArraySamplePtr; +using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::P3fArraySamplePtr; +using Alembic::Abc::PropertyHeader; +using Alembic::Abc::UcharArraySamplePtr; + +using Alembic::AbcGeom::CurvePeriodicity; +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::ICurves; +using Alembic::AbcGeom::ICurvesSchema; +using Alembic::AbcGeom::IFloatGeomParam; +using Alembic::AbcGeom::IInt16Property; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::kWrapExisting; + +AbcCurveReader::AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + ICurves abc_curves(object, kWrapExisting); + m_curves_schema = abc_curves.getSchema(); + + get_min_max_time(m_iobject, m_curves_schema, m_min_time, m_max_time); +} + +bool AbcCurveReader::valid() const +{ + return m_curves_schema.valid(); +} + +bool AbcCurveReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::ICurves::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to Curves when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_CURVE) { + *err_str = "Object type mismatch, Alembic object path points to Curves."; + return false; + } + + return true; +} + +void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE); + + cu->flag |= CU_DEFORM_FILL | CU_3D; + cu->actvert = CU_ACT_NONE; + cu->resolu = 1; + + ICompoundProperty user_props = m_curves_schema.getUserProperties(); + if (user_props) { + const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME); + if (header != NULL && header->isScalar() && IInt16Property::matches(*header)) { + IInt16Property resolu(user_props, header->getName()); + cu->resolu = resolu.getValue(sample_sel); + } + } + + m_object = BKE_object_add_only_object(bmain, OB_CURVE, m_object_name.c_str()); + m_object->data = cu; + + read_curve_sample(cu, m_curves_schema, sample_sel); + + if (has_animations(m_curves_schema, m_settings)) { + addCacheModifier(); + } +} + +void AbcCurveReader::read_curve_sample(Curve *cu, + const ICurvesSchema &schema, + const ISampleSelector &sample_sel) +{ + ICurvesSchema::Sample smp; + try { + smp = schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading curve sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return; + } + + const Int32ArraySamplePtr num_vertices = smp.getCurvesNumVertices(); + const P3fArraySamplePtr positions = smp.getPositions(); + const FloatArraySamplePtr weights = smp.getPositionWeights(); + const FloatArraySamplePtr knots = smp.getKnots(); + const CurvePeriodicity periodicity = smp.getWrap(); + const UcharArraySamplePtr orders = smp.getOrders(); + + const IFloatGeomParam widths_param = schema.getWidthsParam(); + FloatArraySamplePtr radiuses; + + if (widths_param.valid()) { + IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel); + radiuses = wsample.getVals(); + } + + int knot_offset = 0; + + size_t idx = 0; + for (size_t i = 0; i < num_vertices->size(); i++) { + const int num_verts = (*num_vertices)[i]; + + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), "abc_getnurb")); + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + nu->pntsu = num_verts; + nu->pntsv = 1; + nu->flag |= CU_SMOOTH; + + switch (smp.getType()) { + case Alembic::AbcGeom::kCubic: + nu->orderu = 4; + break; + case Alembic::AbcGeom::kVariableOrder: + if (orders && orders->size() > i) { + nu->orderu = static_cast<short>((*orders)[i]); + break; + } + ATTR_FALLTHROUGH; + case Alembic::AbcGeom::kLinear: + default: + nu->orderu = 2; + } + + if (periodicity == Alembic::AbcGeom::kNonPeriodic) { + nu->flagu |= CU_NURB_ENDPOINT; + } + else if (periodicity == Alembic::AbcGeom::kPeriodic) { + nu->flagu |= CU_NURB_CYCLIC; + + /* Check the number of points which overlap, we don't have + * overlapping points in Blender, but other software do use them to + * indicate that a curve is actually cyclic. Usually the number of + * overlapping points is equal to the order/degree of the curve. + */ + + const int start = idx; + const int end = idx + num_verts; + int overlap = 0; + + for (int j = start, k = end - nu->orderu; j < nu->orderu; j++, k++) { + const Imath::V3f &p1 = (*positions)[j]; + const Imath::V3f &p2 = (*positions)[k]; + + if (p1 != p2) { + break; + } + + overlap++; + } + + /* TODO: Special case, need to figure out how it coincides with knots. */ + if (overlap == 0 && num_verts > 2 && (*positions)[start] == (*positions)[end - 1]) { + overlap = 1; + } + + /* There is no real cycles. */ + if (overlap == 0) { + nu->flagu &= ~CU_NURB_CYCLIC; + nu->flagu |= CU_NURB_ENDPOINT; + } + + nu->pntsu -= overlap; + } + + const bool do_weights = (weights != NULL) && (weights->size() > 1); + float weight = 1.0f; + + const bool do_radius = (radiuses != NULL) && (radiuses->size() > 1); + float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : 1.0f; + + nu->type = CU_NURBS; + + nu->bp = static_cast<BPoint *>(MEM_callocN(sizeof(BPoint) * nu->pntsu, "abc_getnurb")); + BPoint *bp = nu->bp; + + for (int j = 0; j < nu->pntsu; j++, bp++, idx++) { + const Imath::V3f &pos = (*positions)[idx]; + + if (do_radius) { + radius = (*radiuses)[idx]; + } + + if (do_weights) { + weight = (*weights)[idx]; + } + + copy_zup_from_yup(bp->vec, pos.getValue()); + bp->vec[3] = weight; + bp->f1 = SELECT; + bp->radius = radius; + bp->weight = 1.0f; + } + + if (knots && knots->size() != 0) { + nu->knotsu = static_cast<float *>( + MEM_callocN(KNOTSU(nu) * sizeof(float), "abc_setsplineknotsu")); + + /* TODO: second check is temporary, for until the check for cycles is rock solid. */ + if (periodicity == Alembic::AbcGeom::kPeriodic && (KNOTSU(nu) == knots->size() - 2)) { + /* Skip first and last knots. */ + for (size_t i = 1; i < knots->size() - 1; i++) { + nu->knotsu[i - 1] = (*knots)[knot_offset + i]; + } + } + else { + /* TODO: figure out how to use the knots array from other + * software in this case. */ + BKE_nurb_knot_calc_u(nu); + } + + knot_offset += knots->size(); + } + else { + BKE_nurb_knot_calc_u(nu); + } + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } +} + +/* NOTE: Alembic only stores data about control points, but the Mesh + * passed from the cache modifier contains the displist, which has more data + * than the control points, so to avoid corrupting the displist we modify the + * object directly and create a new Mesh from that. Also we might need to + * create new or delete existing NURBS in the curve. + */ +Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int /*read_flag*/, + const char **err_str) +{ + ICurvesSchema::Sample sample; + + try { + sample = m_curves_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + *err_str = "Error reading curve sample; more detail on the console"; + printf("Alembic: error reading curve sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_curves_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Int32ArraySamplePtr num_vertices = sample.getCurvesNumVertices(); + + int vertex_idx = 0; + int curve_idx; + Curve *curve = static_cast<Curve *>(m_object->data); + + const int curve_count = BLI_listbase_count(&curve->nurb); + bool same_topology = curve_count == num_vertices->size(); + + if (same_topology) { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int num_in_alembic = (*num_vertices)[curve_idx]; + const int num_in_blender = nurbs->pntsu; + + if (num_in_alembic != num_in_blender) { + same_topology = false; + break; + } + } + } + + if (!same_topology) { + BKE_nurbList_free(&curve->nurb); + read_curve_sample(curve, m_curves_schema, sample_sel); + } + else { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int totpoint = (*num_vertices)[curve_idx]; + + if (nurbs->bp) { + BPoint *point = nurbs->bp; + + for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { + const Imath::V3f &pos = (*positions)[vertex_idx]; + copy_zup_from_yup(point->vec, pos.getValue()); + } + } + else if (nurbs->bezt) { + BezTriple *bezier = nurbs->bezt; + + for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { + const Imath::V3f &pos = (*positions)[vertex_idx]; + copy_zup_from_yup(bezier->vec[1], pos.getValue()); + } + } + } + } + + return BKE_mesh_new_nomain_from_curve(m_object); +} diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h new file mode 100644 index 00000000000..1e4f28edc51 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_CURVES_H__ +#define __ABC_READER_CURVES_H__ + +#include "abc_reader_object.h" +#include "abc_reader_mesh.h" + +struct Curve; + +#define ABC_CURVE_RESOLUTION_U_PROPNAME "blender:resolution" + +class AbcCurveReader : public AbcObjectReader { + Alembic::AbcGeom::ICurvesSchema m_curves_schema; + + public: + AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); + + void read_curve_sample(Curve *cu, + const Alembic::AbcGeom::ICurvesSchema &schema, + const Alembic::Abc::ISampleSelector &sample_selector); +}; + +#endif /* __ABC_READER_CURVES_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc new file mode 100644 index 00000000000..a4e412695c3 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -0,0 +1,889 @@ +/* + * 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_reader_mesh.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +#include <algorithm> + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BLI_math_geom.h" + +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +} + +using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::P3fArraySamplePtr; + +using Alembic::AbcGeom::IFaceSet; +using Alembic::AbcGeom::IFaceSetSchema; +using Alembic::AbcGeom::IN3fGeomParam; +using Alembic::AbcGeom::IObject; +using Alembic::AbcGeom::IPolyMesh; +using Alembic::AbcGeom::IPolyMeshSchema; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::ISubD; +using Alembic::AbcGeom::ISubDSchema; +using Alembic::AbcGeom::IV2fGeomParam; +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::N3fArraySample; +using Alembic::AbcGeom::N3fArraySamplePtr; +using Alembic::AbcGeom::UInt32ArraySamplePtr; +using Alembic::AbcGeom::V2fArraySamplePtr; + +/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */ + +/* Some helpers for mesh generation */ +namespace utils { + +static void build_mat_map(const Main *bmain, std::map<std::string, Material *> &mat_map) +{ + Material *material = static_cast<Material *>(bmain->materials.first); + + for (; material; material = static_cast<Material *>(material->id.next)) { + mat_map[material->id.name + 2] = material; + } +} + +static void assign_materials(Main *bmain, + Object *ob, + const std::map<std::string, int> &mat_index_map) +{ + bool can_assign = true; + std::map<std::string, int>::const_iterator it = mat_index_map.begin(); + + int matcount = 0; + for (; it != mat_index_map.end(); ++it, matcount++) { + if (!BKE_object_material_slot_add(bmain, ob)) { + can_assign = false; + break; + } + } + + /* TODO(kevin): use global map? */ + std::map<std::string, Material *> mat_map; + build_mat_map(bmain, mat_map); + + std::map<std::string, Material *>::iterator mat_iter; + + if (can_assign) { + it = mat_index_map.begin(); + + for (; it != mat_index_map.end(); ++it) { + std::string mat_name = it->first; + mat_iter = mat_map.find(mat_name.c_str()); + + Material *assigned_mat; + + if (mat_iter == mat_map.end()) { + assigned_mat = BKE_material_add(bmain, mat_name.c_str()); + mat_map[mat_name] = assigned_mat; + } + else { + assigned_mat = mat_iter->second; + } + + BKE_object_material_assign(bmain, ob, assigned_mat, it->second, BKE_MAT_ASSIGN_OBDATA); + } + } +} + +} /* namespace utils */ + +struct AbcMeshData { + Int32ArraySamplePtr face_indices; + Int32ArraySamplePtr face_counts; + + P3fArraySamplePtr positions; + P3fArraySamplePtr ceil_positions; + + V2fArraySamplePtr uvs; + UInt32ArraySamplePtr uvs_indices; +}; + +static void read_mverts_interp(MVert *mverts, + const P3fArraySamplePtr &positions, + const P3fArraySamplePtr &ceil_positions, + const float weight) +{ + float tmp[3]; + for (int i = 0; i < positions->size(); i++) { + MVert &mvert = mverts[i]; + const Imath::V3f &floor_pos = (*positions)[i]; + const Imath::V3f &ceil_pos = (*ceil_positions)[i]; + + interp_v3_v3v3(tmp, floor_pos.getValue(), ceil_pos.getValue(), weight); + copy_zup_from_yup(mvert.co, tmp); + + mvert.bweight = 0; + } +} + +static void read_mverts(CDStreamConfig &config, const AbcMeshData &mesh_data) +{ + MVert *mverts = config.mvert; + const P3fArraySamplePtr &positions = mesh_data.positions; + + if (config.weight != 0.0f && mesh_data.ceil_positions != NULL && + mesh_data.ceil_positions->size() == positions->size()) { + read_mverts_interp(mverts, positions, mesh_data.ceil_positions, config.weight); + return; + } + + read_mverts(mverts, positions, nullptr); +} + +void read_mverts(MVert *mverts, const P3fArraySamplePtr positions, const N3fArraySamplePtr normals) +{ + for (int i = 0; i < positions->size(); i++) { + MVert &mvert = mverts[i]; + Imath::V3f pos_in = (*positions)[i]; + + copy_zup_from_yup(mvert.co, pos_in.getValue()); + + mvert.bweight = 0; + + if (normals) { + Imath::V3f nor_in = (*normals)[i]; + + short no[3]; + normal_float_to_short_v3(no, nor_in.getValue()); + + copy_zup_from_yup(mvert.no, no); + } + } +} + +static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data) +{ + MPoly *mpolys = config.mpoly; + MLoop *mloops = config.mloop; + MLoopUV *mloopuvs = config.mloopuv; + + const Int32ArraySamplePtr &face_indices = mesh_data.face_indices; + const Int32ArraySamplePtr &face_counts = mesh_data.face_counts; + const V2fArraySamplePtr &uvs = mesh_data.uvs; + const size_t uvs_size = uvs == nullptr ? 0 : uvs->size(); + + const UInt32ArraySamplePtr &uvs_indices = mesh_data.uvs_indices; + + const bool do_uvs = (mloopuvs && uvs && uvs_indices) && + (uvs_indices->size() == face_indices->size()); + unsigned int loop_index = 0; + unsigned int rev_loop_index = 0; + unsigned int uv_index = 0; + + for (int i = 0; i < face_counts->size(); i++) { + const int face_size = (*face_counts)[i]; + + MPoly &poly = mpolys[i]; + poly.loopstart = loop_index; + poly.totloop = face_size; + + /* Polygons are always assumed to be smooth-shaded. If the Alembic mesh should be flat-shaded, + * this is encoded in custom loop normals. See T71246. */ + poly.flag |= ME_SMOOTH; + + /* NOTE: Alembic data is stored in the reverse order. */ + rev_loop_index = loop_index + (face_size - 1); + + for (int f = 0; f < face_size; f++, loop_index++, rev_loop_index--) { + MLoop &loop = mloops[rev_loop_index]; + loop.v = (*face_indices)[loop_index]; + + if (do_uvs) { + MLoopUV &loopuv = mloopuvs[rev_loop_index]; + + uv_index = (*uvs_indices)[loop_index]; + + /* Some Alembic files are broken (or at least export UVs in a way we don't expect). */ + if (uv_index >= uvs_size) { + continue; + } + + loopuv.uv[0] = (*uvs)[uv_index][0]; + loopuv.uv[1] = (*uvs)[uv_index][1]; + } + } + } + + BKE_mesh_calc_edges(config.mesh, false, false); +} + +static void process_no_normals(CDStreamConfig &config) +{ + /* Absense of normals in the Alembic mesh is interpreted as 'smooth'. */ + BKE_mesh_calc_normals(config.mesh); +} + +static void process_loop_normals(CDStreamConfig &config, const N3fArraySamplePtr loop_normals_ptr) +{ + size_t loop_count = loop_normals_ptr->size(); + + if (loop_count == 0) { + process_no_normals(config); + return; + } + + float(*lnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(loop_count, sizeof(float[3]), "ABC::FaceNormals")); + + Mesh *mesh = config.mesh; + MPoly *mpoly = mesh->mpoly; + const N3fArraySample &loop_normals = *loop_normals_ptr; + int abc_index = 0; + for (int i = 0, e = mesh->totpoly; i < e; i++, mpoly++) { + /* As usual, ABC orders the loops in reverse. */ + for (int j = mpoly->totloop - 1; j >= 0; j--, abc_index++) { + int blender_index = mpoly->loopstart + j; + copy_zup_from_yup(lnors[blender_index], loop_normals[abc_index].getValue()); + } + } + + mesh->flag |= ME_AUTOSMOOTH; + BKE_mesh_set_custom_normals(mesh, lnors); + + MEM_freeN(lnors); +} + +static void process_vertex_normals(CDStreamConfig &config, + const N3fArraySamplePtr vertex_normals_ptr) +{ + size_t normals_count = vertex_normals_ptr->size(); + if (normals_count == 0) { + process_no_normals(config); + return; + } + + float(*vnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(normals_count, sizeof(float[3]), "ABC::VertexNormals")); + + const N3fArraySample &vertex_normals = *vertex_normals_ptr; + for (int index = 0; index < normals_count; index++) { + copy_zup_from_yup(vnors[index], vertex_normals[index].getValue()); + } + + config.mesh->flag |= ME_AUTOSMOOTH; + BKE_mesh_set_custom_normals_from_vertices(config.mesh, vnors); + MEM_freeN(vnors); +} + +static void process_normals(CDStreamConfig &config, + const IN3fGeomParam &normals, + const ISampleSelector &selector) +{ + if (!normals.valid()) { + process_no_normals(config); + return; + } + + IN3fGeomParam::Sample normsamp = normals.getExpandedValue(selector); + Alembic::AbcGeom::GeometryScope scope = normals.getScope(); + + switch (scope) { + case Alembic::AbcGeom::kFacevaryingScope: // 'Vertex Normals' in Houdini. + process_loop_normals(config, normsamp.getVals()); + break; + case Alembic::AbcGeom::kVertexScope: + case Alembic::AbcGeom::kVaryingScope: // 'Point Normals' in Houdini. + process_vertex_normals(config, normsamp.getVals()); + break; + case Alembic::AbcGeom::kConstantScope: + case Alembic::AbcGeom::kUniformScope: + case Alembic::AbcGeom::kUnknownScope: + process_no_normals(config); + break; + } +} + +ABC_INLINE void read_uvs_params(CDStreamConfig &config, + AbcMeshData &abc_data, + const IV2fGeomParam &uv, + const ISampleSelector &selector) +{ + if (!uv.valid()) { + return; + } + + IV2fGeomParam::Sample uvsamp; + uv.getIndexed(uvsamp, selector); + + abc_data.uvs = uvsamp.getVals(); + abc_data.uvs_indices = uvsamp.getIndices(); + + if (abc_data.uvs_indices->size() == config.totloop) { + std::string name = Alembic::Abc::GetSourceName(uv.getMetaData()); + + /* According to the convention, primary UVs should have had their name + * set using Alembic::Abc::SetSourceName, but you can't expect everyone + * to follow it! :) */ + if (name.empty()) { + name = uv.getName(); + } + + void *cd_ptr = config.add_customdata_cb(config.mesh, name.c_str(), CD_MLOOPUV); + config.mloopuv = static_cast<MLoopUV *>(cd_ptr); + } +} + +static void *add_customdata_cb(Mesh *mesh, const char *name, int data_type) +{ + CustomDataType cd_data_type = static_cast<CustomDataType>(data_type); + void *cd_ptr; + CustomData *loopdata; + int numloops; + + /* unsupported custom data type -- don't do anything. */ + if (!ELEM(cd_data_type, CD_MLOOPUV, CD_MLOOPCOL)) { + return NULL; + } + + loopdata = &mesh->ldata; + cd_ptr = CustomData_get_layer_named(loopdata, cd_data_type, name); + if (cd_ptr != NULL) { + /* layer already exists, so just return it. */ + return cd_ptr; + } + + /* Create a new layer. */ + numloops = mesh->totloop; + cd_ptr = CustomData_add_layer_named(loopdata, cd_data_type, CD_DEFAULT, NULL, numloops, name); + return cd_ptr; +} + +static void get_weight_and_index(CDStreamConfig &config, + Alembic::AbcCoreAbstract::TimeSamplingPtr time_sampling, + size_t samples_number) +{ + Alembic::AbcGeom::index_t i0, i1; + + config.weight = get_weight_and_index(config.time, time_sampling, samples_number, i0, i1); + + config.index = i0; + config.ceil_index = i1; +} + +static void read_mesh_sample(const std::string &iobject_full_name, + ImportSettings *settings, + const IPolyMeshSchema &schema, + const ISampleSelector &selector, + CDStreamConfig &config) +{ + const IPolyMeshSchema::Sample sample = schema.getValue(selector); + + AbcMeshData abc_mesh_data; + abc_mesh_data.face_counts = sample.getFaceCounts(); + abc_mesh_data.face_indices = sample.getFaceIndices(); + abc_mesh_data.positions = sample.getPositions(); + + get_weight_and_index(config, schema.getTimeSampling(), schema.getNumSamples()); + + if (config.weight != 0.0f) { + Alembic::AbcGeom::IPolyMeshSchema::Sample ceil_sample; + schema.get(ceil_sample, Alembic::Abc::ISampleSelector(config.ceil_index)); + abc_mesh_data.ceil_positions = ceil_sample.getPositions(); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { + read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { + read_mverts(config, abc_mesh_data); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { + read_mpolys(config, abc_mesh_data); + process_normals(config, schema.getNormalsParam(), selector); + } + + if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { + read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); + } +} + +CDStreamConfig get_config(Mesh *mesh) +{ + CDStreamConfig config; + + BLI_assert(mesh->mvert || mesh->totvert == 0); + + config.mesh = mesh; + config.mvert = mesh->mvert; + config.mloop = mesh->mloop; + config.mpoly = mesh->mpoly; + config.totloop = mesh->totloop; + config.totpoly = mesh->totpoly; + config.loopdata = &mesh->ldata; + config.add_customdata_cb = add_customdata_cb; + + return config; +} + +/* ************************************************************************** */ + +AbcMeshReader::AbcMeshReader(const IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + m_settings->read_flag |= MOD_MESHSEQ_READ_ALL; + + IPolyMesh ipoly_mesh(m_iobject, kWrapExisting); + m_schema = ipoly_mesh.getSchema(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcMeshReader::valid() const +{ + return m_schema.valid(); +} + +void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); + + m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); + m_object->data = mesh; + + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, NULL); + if (read_mesh != mesh) { + /* XXX fixme after 2.80; mesh->flag isn't copied by BKE_mesh_nomain_to_mesh() */ + /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ + short autosmooth = (read_mesh->flag & ME_AUTOSMOOTH); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + mesh->flag |= autosmooth; + } + + if (m_settings->validate_meshes) { + BKE_mesh_validate(mesh, false, false); + } + + readFaceSetsSample(bmain, mesh, sample_sel); + + if (has_animations(m_schema, m_settings)) { + addCacheModifier(); + } +} + +bool AbcMeshReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::IPolyMesh::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to PolyMesh when importing, but not " + "any more."; + return false; + } + + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch, Alembic object path points to PolyMesh."; + return false; + } + + return true; +} + +bool AbcMeshReader::topology_changed(Mesh *existing_mesh, const ISampleSelector &sample_sel) +{ + IPolyMeshSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + // A similar error in read_mesh() would just return existing_mesh. + return false; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + return positions->size() != existing_mesh->totvert || + face_counts->size() != existing_mesh->totpoly || + face_indices->size() != existing_mesh->totloop; +} + +Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int read_flag, + const char **err_str) +{ + IPolyMeshSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + if (err_str != nullptr) { + *err_str = "Error reading mesh sample; more detail on the console"; + } + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + Mesh *new_mesh = NULL; + + /* Only read point data when streaming meshes, unless we need to create new ones. */ + ImportSettings settings; + settings.read_flag |= read_flag; + + if (topology_changed(existing_mesh, sample_sel)) { + new_mesh = BKE_mesh_new_nomain_from_template( + existing_mesh, positions->size(), 0, 0, face_indices->size(), face_counts->size()); + + settings.read_flag |= MOD_MESHSEQ_READ_ALL; + } + else { + /* If the face count changed (e.g. by triangulation), only read points. + * This prevents crash from T49813. + * TODO(kevin): perhaps find a better way to do this? */ + if (face_counts->size() != existing_mesh->totpoly || + face_indices->size() != existing_mesh->totloop) { + settings.read_flag = MOD_MESHSEQ_READ_VERT; + + if (err_str) { + *err_str = + "Topology has changed, perhaps by triangulating the" + " mesh. Only vertices will be read!"; + } + } + } + + CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh); + config.time = sample_sel.getRequestedTime(); + + read_mesh_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config); + + if (new_mesh) { + /* Here we assume that the number of materials doesn't change, i.e. that + * the material slots that were created when the object was loaded from + * Alembic are still valid now. */ + size_t num_polys = new_mesh->totpoly; + if (num_polys > 0) { + std::map<std::string, int> mat_map; + assign_facesets_to_mpoly(sample_sel, new_mesh->mpoly, num_polys, mat_map); + } + + return new_mesh; + } + + return existing_mesh; +} + +void AbcMeshReader::assign_facesets_to_mpoly(const ISampleSelector &sample_sel, + MPoly *mpoly, + int totpoly, + std::map<std::string, int> &r_mat_map) +{ + std::vector<std::string> face_sets; + m_schema.getFaceSetNames(face_sets); + + if (face_sets.empty()) { + return; + } + + int current_mat = 0; + + for (int i = 0; i < face_sets.size(); i++) { + const std::string &grp_name = face_sets[i]; + + if (r_mat_map.find(grp_name) == r_mat_map.end()) { + r_mat_map[grp_name] = 1 + current_mat++; + } + + const int assigned_mat = r_mat_map[grp_name]; + + const IFaceSet faceset = m_schema.getFaceSet(grp_name); + + if (!faceset.valid()) { + std::cerr << " Face set " << grp_name << " invalid for " << m_object_name << "\n"; + continue; + } + + const IFaceSetSchema face_schem = faceset.getSchema(); + const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel); + const Int32ArraySamplePtr group_faces = face_sample.getFaces(); + const size_t num_group_faces = group_faces->size(); + + for (size_t l = 0; l < num_group_faces; l++) { + size_t pos = (*group_faces)[l]; + + if (pos >= totpoly) { + std::cerr << "Faceset overflow on " << faceset.getName() << '\n'; + break; + } + + MPoly &poly = mpoly[pos]; + poly.mat_nr = assigned_mat - 1; + } + } +} + +void AbcMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const ISampleSelector &sample_sel) +{ + std::map<std::string, int> mat_map; + assign_facesets_to_mpoly(sample_sel, mesh->mpoly, mesh->totpoly, mat_map); + utils::assign_materials(bmain, m_object, mat_map); +} + +/* ************************************************************************** */ + +ABC_INLINE MEdge *find_edge(MEdge *edges, int totedge, int v1, int v2) +{ + for (int i = 0, e = totedge; i < e; i++) { + MEdge &edge = edges[i]; + + if (edge.v1 == v1 && edge.v2 == v2) { + return &edge; + } + } + + return NULL; +} + +static void read_subd_sample(const std::string &iobject_full_name, + ImportSettings *settings, + const ISubDSchema &schema, + const ISampleSelector &selector, + CDStreamConfig &config) +{ + const ISubDSchema::Sample sample = schema.getValue(selector); + + AbcMeshData abc_mesh_data; + abc_mesh_data.face_counts = sample.getFaceCounts(); + abc_mesh_data.face_indices = sample.getFaceIndices(); + abc_mesh_data.positions = sample.getPositions(); + + get_weight_and_index(config, schema.getTimeSampling(), schema.getNumSamples()); + + if (config.weight != 0.0f) { + Alembic::AbcGeom::ISubDSchema::Sample ceil_sample; + schema.get(ceil_sample, Alembic::Abc::ISampleSelector(config.ceil_index)); + abc_mesh_data.ceil_positions = ceil_sample.getPositions(); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { + read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { + read_mverts(config, abc_mesh_data); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { + /* Alembic's 'SubD' scheme is used to store subdivision surfaces, i.e. the pre-subdivision + * mesh. Currently we don't add a subdivision modifier when we load such data. This code is + * assuming that the subdivided surface should be smooth. */ + read_mpolys(config, abc_mesh_data); + process_no_normals(config); + } + + if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { + read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); + } +} + +/* ************************************************************************** */ + +AbcSubDReader::AbcSubDReader(const IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + m_settings->read_flag |= MOD_MESHSEQ_READ_ALL; + + ISubD isubd_mesh(m_iobject, kWrapExisting); + m_schema = isubd_mesh.getSchema(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcSubDReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcSubDReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::ISubD::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to SubD when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch, Alembic object path points to SubD."; + return false; + } + + return true; +} + +void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); + + m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); + m_object->data = mesh; + + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, NULL); + if (read_mesh != mesh) { + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + } + + ISubDSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return; + } + + Int32ArraySamplePtr indices = sample.getCreaseIndices(); + Alembic::Abc::FloatArraySamplePtr sharpnesses = sample.getCreaseSharpnesses(); + + if (indices && sharpnesses) { + MEdge *edges = mesh->medge; + int totedge = mesh->totedge; + + for (int i = 0, s = 0, e = indices->size(); i < e; i += 2, s++) { + int v1 = (*indices)[i]; + int v2 = (*indices)[i + 1]; + + if (v2 < v1) { + /* It appears to be common to store edges with the smallest index first, in which case this + * prevents us from doing the second search below. */ + std::swap(v1, v2); + } + + MEdge *edge = find_edge(edges, totedge, v1, v2); + if (edge == NULL) { + edge = find_edge(edges, totedge, v2, v1); + } + + if (edge) { + edge->crease = unit_float_to_uchar_clamp((*sharpnesses)[s]); + } + } + + mesh->cd_flag |= ME_CDFLAG_EDGE_CREASE; + } + + if (m_settings->validate_meshes) { + BKE_mesh_validate(mesh, false, false); + } + + if (has_animations(m_schema, m_settings)) { + addCacheModifier(); + } +} + +Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int read_flag, + const char **err_str) +{ + ISubDSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + if (err_str != nullptr) { + *err_str = "Error reading mesh sample; more detail on the console"; + } + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + Mesh *new_mesh = NULL; + + ImportSettings settings; + settings.read_flag |= read_flag; + + if (existing_mesh->totvert != positions->size()) { + new_mesh = BKE_mesh_new_nomain_from_template( + existing_mesh, positions->size(), 0, 0, face_indices->size(), face_counts->size()); + + settings.read_flag |= MOD_MESHSEQ_READ_ALL; + } + else { + /* If the face count changed (e.g. by triangulation), only read points. + * This prevents crash from T49813. + * TODO(kevin): perhaps find a better way to do this? */ + if (face_counts->size() != existing_mesh->totpoly || + face_indices->size() != existing_mesh->totloop) { + settings.read_flag = MOD_MESHSEQ_READ_VERT; + + if (err_str) { + *err_str = + "Topology has changed, perhaps by triangulating the" + " mesh. Only vertices will be read!"; + } + } + } + + /* Only read point data when streaming meshes, unless we need to create new ones. */ + CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh); + config.time = sample_sel.getRequestedTime(); + read_subd_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config); + + return config.mesh; +} diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h new file mode 100644 index 00000000000..bc95c7ec134 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -0,0 +1,86 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_MESH_H__ +#define __ABC_READER_MESH_H__ + +#include "abc_customdata.h" +#include "abc_reader_object.h" + +struct Mesh; + +class AbcMeshReader : public AbcObjectReader { + Alembic::AbcGeom::IPolyMeshSchema m_schema; + + CDStreamConfig m_mesh_data; + + public: + AbcMeshReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const override; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const override; + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str) override; + bool topology_changed(Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel) override; + + private: + void readFaceSetsSample(Main *bmain, + Mesh *mesh, + const Alembic::AbcGeom::ISampleSelector &sample_sel); + + void assign_facesets_to_mpoly(const Alembic::Abc::ISampleSelector &sample_sel, + MPoly *mpoly, + int totpoly, + std::map<std::string, int> &r_mat_map); +}; + +class AbcSubDReader : public AbcObjectReader { + Alembic::AbcGeom::ISubDSchema m_schema; + + CDStreamConfig m_mesh_data; + + public: + AbcSubDReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); +}; + +void read_mverts(MVert *mverts, + const Alembic::AbcGeom::P3fArraySamplePtr positions, + const Alembic::AbcGeom::N3fArraySamplePtr normals); + +CDStreamConfig get_config(struct Mesh *mesh); + +#endif /* __ABC_READER_MESH_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc new file mode 100644 index 00000000000..0ada10baba5 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc @@ -0,0 +1,225 @@ +/* + * 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_reader_nurbs.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "BKE_curve.h" +#include "BKE_object.h" +} + +using Alembic::AbcGeom::FloatArraySamplePtr; +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::MetaData; +using Alembic::AbcGeom::P3fArraySamplePtr; + +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::INuPatch; +using Alembic::AbcGeom::INuPatchSchema; +using Alembic::AbcGeom::IObject; + +AbcNurbsReader::AbcNurbsReader(const IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + getNurbsPatches(m_iobject); + get_min_max_time(m_iobject, m_schemas[0].first, m_min_time, m_max_time); +} + +bool AbcNurbsReader::valid() const +{ + if (m_schemas.empty()) { + return false; + } + + std::vector<std::pair<INuPatchSchema, IObject>>::const_iterator it; + for (it = m_schemas.begin(); it != m_schemas.end(); ++it) { + const INuPatchSchema &schema = it->first; + + if (!schema.valid()) { + return false; + } + } + + return true; +} + +static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots) +{ + if (!knots || knots->size() == 0) { + return false; + } + + /* Skip first and last knots, as they are used for padding. */ + const size_t num_knots = knots->size() - 2; + nu_knots = static_cast<float *>(MEM_callocN(num_knots * sizeof(float), "abc_setsplineknotsu")); + + for (size_t i = 0; i < num_knots; i++) { + nu_knots[i] = (*knots)[i + 1]; + } + + return true; +} + +void AbcNurbsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Curve *cu = static_cast<Curve *>(BKE_curve_add(bmain, "abc_curve", OB_SURF)); + cu->actvert = CU_ACT_NONE; + + std::vector<std::pair<INuPatchSchema, IObject>>::iterator it; + + for (it = m_schemas.begin(); it != m_schemas.end(); ++it) { + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), "abc_getnurb")); + nu->flag = CU_SMOOTH; + nu->type = CU_NURBS; + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + + const INuPatchSchema &schema = it->first; + INuPatchSchema::Sample smp; + try { + smp = schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading nurbs sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return; + } + + nu->orderu = smp.getUOrder() - 1; + nu->orderv = smp.getVOrder() - 1; + nu->pntsu = smp.getNumU(); + nu->pntsv = smp.getNumV(); + + /* Read positions and weights. */ + + const P3fArraySamplePtr positions = smp.getPositions(); + const FloatArraySamplePtr weights = smp.getPositionWeights(); + + const size_t num_points = positions->size(); + + nu->bp = static_cast<BPoint *>(MEM_callocN(num_points * sizeof(BPoint), "abc_setsplinetype")); + + BPoint *bp = nu->bp; + float posw_in = 1.0f; + + for (int i = 0; i < num_points; i++, bp++) { + const Imath::V3f &pos_in = (*positions)[i]; + + if (weights) { + posw_in = (*weights)[i]; + } + + copy_zup_from_yup(bp->vec, pos_in.getValue()); + bp->vec[3] = posw_in; + bp->f1 = SELECT; + bp->radius = 1.0f; + bp->weight = 1.0f; + } + + /* Read knots. */ + + if (!set_knots(smp.getUKnot(), nu->knotsu)) { + BKE_nurb_knot_calc_u(nu); + } + + if (!set_knots(smp.getVKnot(), nu->knotsv)) { + BKE_nurb_knot_calc_v(nu); + } + + /* Read flags. */ + + ICompoundProperty user_props = schema.getUserProperties(); + + if (has_property(user_props, "enpoint_u")) { + nu->flagu |= CU_NURB_ENDPOINT; + } + + if (has_property(user_props, "enpoint_v")) { + nu->flagv |= CU_NURB_ENDPOINT; + } + + if (has_property(user_props, "cyclic_u")) { + nu->flagu |= CU_NURB_CYCLIC; + } + + if (has_property(user_props, "cyclic_v")) { + nu->flagv |= CU_NURB_CYCLIC; + } + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } + + BLI_strncpy(cu->id.name + 2, m_data_name.c_str(), m_data_name.size() + 1); + + m_object = BKE_object_add_only_object(bmain, OB_SURF, m_object_name.c_str()); + m_object->data = cu; +} + +void AbcNurbsReader::getNurbsPatches(const IObject &obj) +{ + if (!obj.valid()) { + return; + } + + const int num_children = obj.getNumChildren(); + + if (num_children == 0) { + INuPatch abc_nurb(obj, kWrapExisting); + INuPatchSchema schem = abc_nurb.getSchema(); + m_schemas.push_back(std::pair<INuPatchSchema, IObject>(schem, obj)); + return; + } + + for (int i = 0; i < num_children; i++) { + bool ok = true; + IObject child(obj, obj.getChildHeader(i).getName()); + + if (!m_name.empty() && child.valid() && !begins_with(child.getFullName(), m_name)) { + ok = false; + } + + if (!child.valid()) { + continue; + } + + const MetaData &md = child.getMetaData(); + + if (INuPatch::matches(md) && ok) { + INuPatch abc_nurb(child, kWrapExisting); + INuPatchSchema schem = abc_nurb.getSchema(); + m_schemas.push_back(std::pair<INuPatchSchema, IObject>(schem, child)); + } + + getNurbsPatches(child); + } +} diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.h b/source/blender/io/alembic/intern/abc_reader_nurbs.h new file mode 100644 index 00000000000..f4284c136fb --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.h @@ -0,0 +1,40 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_NURBS_H__ +#define __ABC_READER_NURBS_H__ + +#include "abc_reader_object.h" + +class AbcNurbsReader : public AbcObjectReader { + std::vector<std::pair<Alembic::AbcGeom::INuPatchSchema, Alembic::Abc::IObject>> m_schemas; + + public: + AbcNurbsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + + private: + void getNurbsPatches(const Alembic::Abc::IObject &obj); +}; + +#endif /* __ABC_READER_NURBS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc new file mode 100644 index 00000000000..3e7f87d78cc --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -0,0 +1,333 @@ +/* + * 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_reader_object.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_cachefile_types.h" +#include "DNA_constraint_types.h" +#include "DNA_modifier_types.h" +#include "DNA_space_types.h" /* for FILE_MAX */ + +#include "BKE_constraint.h" +#include "BKE_lib_id.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "BLI_utildefines.h" +#include "BLI_listbase.h" +#include "BLI_math_geom.h" +#include "BLI_string.h" +} + +using Alembic::AbcGeom::IObject; +using Alembic::AbcGeom::IXform; +using Alembic::AbcGeom::IXformSchema; + +AbcObjectReader::AbcObjectReader(const IObject &object, ImportSettings &settings) + : m_name(""), + m_object_name(""), + m_data_name(""), + m_object(NULL), + m_iobject(object), + m_settings(&settings), + m_min_time(std::numeric_limits<chrono_t>::max()), + m_max_time(std::numeric_limits<chrono_t>::min()), + m_refcount(0), + parent_reader(NULL) +{ + m_name = object.getFullName(); + std::vector<std::string> parts; + split(m_name, '/', parts); + + if (parts.size() >= 2) { + m_object_name = parts[parts.size() - 2]; + m_data_name = parts[parts.size() - 1]; + } + else { + m_object_name = m_data_name = parts[parts.size() - 1]; + } + + determine_inherits_xform(); +} + +/* Determine whether we can inherit our parent's XForm */ +void AbcObjectReader::determine_inherits_xform() +{ + m_inherits_xform = false; + + IXform ixform = xform(); + if (!ixform) { + return; + } + + const IXformSchema &schema(ixform.getSchema()); + if (!schema.valid()) { + std::cerr << "Alembic object " << ixform.getFullName() << " has an invalid schema." + << std::endl; + return; + } + + m_inherits_xform = schema.getInheritsXforms(); + + IObject ixform_parent = ixform.getParent(); + if (!ixform_parent.getParent()) { + /* The archive top object certainly is not a transform itself, so handle + * it as "no parent". */ + m_inherits_xform = false; + } + else { + m_inherits_xform = ixform_parent && m_inherits_xform; + } +} + +AbcObjectReader::~AbcObjectReader() +{ +} + +const IObject &AbcObjectReader::iobject() const +{ + return m_iobject; +} + +Object *AbcObjectReader::object() const +{ + return m_object; +} + +void AbcObjectReader::object(Object *ob) +{ + m_object = ob; +} + +static Imath::M44d blend_matrices(const Imath::M44d &m0, const Imath::M44d &m1, const float weight) +{ + float mat0[4][4], mat1[4][4], ret[4][4]; + + /* Cannot use Imath::M44d::getValue() since this returns a pointer to + * doubles and interp_m4_m4m4 expects pointers to floats. So need to convert + * the matrices manually. + */ + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mat0[i][j] = static_cast<float>(m0[i][j]); + } + } + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mat1[i][j] = static_cast<float>(m1[i][j]); + } + } + + interp_m4_m4m4(ret, mat0, mat1, weight); + + Imath::M44d m; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + m[i][j] = ret[i][j]; + } + } + + return m; +} + +Imath::M44d get_matrix(const IXformSchema &schema, const float time) +{ + Alembic::AbcGeom::index_t i0, i1; + Alembic::AbcGeom::XformSample s0, s1; + + const float weight = get_weight_and_index( + time, schema.getTimeSampling(), schema.getNumSamples(), i0, i1); + + schema.get(s0, Alembic::AbcGeom::ISampleSelector(i0)); + + if (i0 != i1) { + schema.get(s1, Alembic::AbcGeom::ISampleSelector(i1)); + return blend_matrices(s0.getMatrix(), s1.getMatrix(), weight); + } + + return s0.getMatrix(); +} + +struct Mesh *AbcObjectReader::read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &UNUSED(sample_sel), + int UNUSED(read_flag), + const char **UNUSED(err_str)) +{ + return existing_mesh; +} + +bool AbcObjectReader::topology_changed(Mesh * /*existing_mesh*/, + const Alembic::Abc::ISampleSelector & /*sample_sel*/) +{ + /* The default implementation of read_mesh() just returns the original mesh, so never changes the + * topology. */ + return false; +} + +void AbcObjectReader::setupObjectTransform(const float time) +{ + bool is_constant = false; + float transform_from_alembic[4][4]; + + /* If the parent is a camera, apply the inverse rotation to make up for the from-Maya rotation. + * This assumes that the parent object also was imported from Alembic. */ + if (m_object->parent != nullptr && m_object->parent->type == OB_CAMERA) { + axis_angle_to_mat4_single(m_object->parentinv, 'X', -M_PI_2); + } + + this->read_matrix(transform_from_alembic, time, m_settings->scale, is_constant); + + /* Apply the matrix to the object. */ + BKE_object_apply_mat4(m_object, transform_from_alembic, true, false); + BKE_object_to_mat4(m_object, m_object->obmat); + + if (!is_constant) { + bConstraint *con = BKE_constraint_add_for_object( + m_object, NULL, CONSTRAINT_TYPE_TRANSFORM_CACHE); + bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data); + BLI_strncpy(data->object_path, m_iobject.getFullName().c_str(), FILE_MAX); + + data->cache_file = m_settings->cache_file; + id_us_plus(&data->cache_file->id); + } +} + +Alembic::AbcGeom::IXform AbcObjectReader::xform() +{ + /* Check that we have an empty object (locator, bone head/tail...). */ + if (IXform::matches(m_iobject.getMetaData())) { + try { + return IXform(m_iobject, Alembic::AbcGeom::kWrapExisting); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading object transform for '%s': %s\n", + m_iobject.getFullName().c_str(), + ex.what()); + return IXform(); + } + } + + /* Check that we have an object with actual data, in which case the + * parent Alembic object should contain the transform. */ + IObject abc_parent = m_iobject.getParent(); + + /* The archive's top object can be recognised by not having a parent. */ + if (abc_parent.getParent() && IXform::matches(abc_parent.getMetaData())) { + try { + return IXform(abc_parent, Alembic::AbcGeom::kWrapExisting); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading object transform for '%s': %s\n", + abc_parent.getFullName().c_str(), + ex.what()); + return IXform(); + } + } + + /* This can happen in certain cases. For example, MeshLab exports + * point clouds without parent XForm. */ + return IXform(); +} + +void AbcObjectReader::read_matrix(float r_mat[4][4] /* local matrix */, + const float time, + const float scale, + bool &is_constant) +{ + IXform ixform = xform(); + if (!ixform) { + unit_m4(r_mat); + is_constant = true; + return; + } + + const IXformSchema &schema(ixform.getSchema()); + if (!schema.valid()) { + std::cerr << "Alembic object " << ixform.getFullName() << " has an invalid schema." + << std::endl; + return; + } + + const Imath::M44d matrix = get_matrix(schema, time); + convert_matrix_datatype(matrix, r_mat); + copy_m44_axis_swap(r_mat, r_mat, ABC_ZUP_FROM_YUP); + + /* Convert from Maya to Blender camera orientation. Children of this camera + * will have the opposite transform as their Parent Inverse matrix. + * See AbcObjectReader::setupObjectTransform(). */ + if (m_object->type == OB_CAMERA) { + float camera_rotation[4][4]; + axis_angle_to_mat4_single(camera_rotation, 'X', M_PI_2); + mul_m4_m4m4(r_mat, r_mat, camera_rotation); + } + + if (!m_inherits_xform) { + /* Only apply scaling to root objects, parenting will propagate it. */ + float scale_mat[4][4]; + scale_m4_fl(scale_mat, scale); + mul_m4_m4m4(r_mat, scale_mat, r_mat); + } + + is_constant = schema.isConstant(); +} + +void AbcObjectReader::addCacheModifier() +{ + ModifierData *md = modifier_new(eModifierType_MeshSequenceCache); + BLI_addtail(&m_object->modifiers, md); + + MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md); + + mcmd->cache_file = m_settings->cache_file; + id_us_plus(&mcmd->cache_file->id); + + BLI_strncpy(mcmd->object_path, m_iobject.getFullName().c_str(), FILE_MAX); +} + +chrono_t AbcObjectReader::minTime() const +{ + return m_min_time; +} + +chrono_t AbcObjectReader::maxTime() const +{ + return m_max_time; +} + +int AbcObjectReader::refcount() const +{ + return m_refcount; +} + +void AbcObjectReader::incref() +{ + m_refcount++; +} + +void AbcObjectReader::decref() +{ + m_refcount--; + BLI_assert(m_refcount >= 0); +} diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h new file mode 100644 index 00000000000..94923df2df9 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_object.h @@ -0,0 +1,171 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_OBJECT_H__ +#define __ABC_READER_OBJECT_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +extern "C" { +#include "DNA_ID.h" +} + +struct CacheFile; +struct Main; +struct Mesh; +struct Object; + +using Alembic::AbcCoreAbstract::chrono_t; + +struct ImportSettings { + bool do_convert_mat; + float conversion_mat[4][4]; + + int from_up; + int from_forward; + float scale; + bool is_sequence; + bool set_frame_range; + + /* Length and frame offset of file sequences. */ + int sequence_len; + int sequence_offset; + + /* From MeshSeqCacheModifierData.read_flag */ + int read_flag; + + bool validate_meshes; + + CacheFile *cache_file; + + ImportSettings() + : do_convert_mat(false), + from_up(0), + from_forward(0), + scale(1.0f), + is_sequence(false), + set_frame_range(false), + sequence_len(1), + sequence_offset(0), + read_flag(0), + validate_meshes(false), + cache_file(NULL) + { + } +}; + +template<typename Schema> static bool has_animations(Schema &schema, ImportSettings *settings) +{ + return settings->is_sequence || !schema.isConstant(); +} + +class AbcObjectReader { + protected: + std::string m_name; + std::string m_object_name; + std::string m_data_name; + Object *m_object; + Alembic::Abc::IObject m_iobject; + + ImportSettings *m_settings; + + chrono_t m_min_time; + chrono_t m_max_time; + + /* Use reference counting since the same reader may be used by multiple + * modifiers and/or constraints. */ + int m_refcount; + + bool m_inherits_xform; + + public: + AbcObjectReader *parent_reader; + + public: + explicit AbcObjectReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + virtual ~AbcObjectReader(); + + const Alembic::Abc::IObject &iobject() const; + + typedef std::vector<AbcObjectReader *> ptr_vector; + + /** + * Returns the transform of this object. This can be the Alembic object + * itself (in case of an Empty) or it can be the parent Alembic object. + */ + virtual Alembic::AbcGeom::IXform xform(); + + Object *object() const; + void object(Object *ob); + + const std::string &name() const + { + return m_name; + } + const std::string &object_name() const + { + return m_object_name; + } + const std::string &data_name() const + { + return m_data_name; + } + bool inherits_xform() const + { + return m_inherits_xform; + } + + virtual bool valid() const = 0; + virtual bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const = 0; + + virtual void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) = 0; + + virtual struct Mesh *read_mesh(struct Mesh *mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); + virtual bool topology_changed(Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel); + + /** Reads the object matrix and sets up an object transform if animated. */ + void setupObjectTransform(const float time); + + void addCacheModifier(); + + chrono_t minTime() const; + chrono_t maxTime() const; + + int refcount() const; + void incref(); + void decref(); + + void read_matrix(float r_mat[4][4], const float time, const float scale, bool &is_constant); + + protected: + void determine_inherits_xform(); +}; + +Imath::M44d get_matrix(const Alembic::AbcGeom::IXformSchema &schema, const float time); + +#endif /* __ABC_READER_OBJECT_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc new file mode 100644 index 00000000000..e4dc345f868 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_points.cc @@ -0,0 +1,157 @@ +/* + * 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_reader_points.h" +#include "abc_reader_mesh.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" + +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +} + +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::N3fArraySamplePtr; +using Alembic::AbcGeom::P3fArraySamplePtr; + +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::IN3fArrayProperty; +using Alembic::AbcGeom::IPoints; +using Alembic::AbcGeom::IPointsSchema; +using Alembic::AbcGeom::ISampleSelector; + +AbcPointsReader::AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + IPoints ipoints(m_iobject, kWrapExisting); + m_schema = ipoints.getSchema(); + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcPointsReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcPointsReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::IPoints::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to Points when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch, Alembic object path points to Points."; + return false; + } + + return true; +} + +void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, NULL); + + if (read_mesh != mesh) { + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + } + + if (m_settings->validate_meshes) { + BKE_mesh_validate(mesh, false, false); + } + + m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); + m_object->data = mesh; + + if (has_animations(m_schema, m_settings)) { + addCacheModifier(); + } +} + +void read_points_sample(const IPointsSchema &schema, + const ISampleSelector &selector, + CDStreamConfig &config) +{ + Alembic::AbcGeom::IPointsSchema::Sample sample = schema.getValue(selector); + + const P3fArraySamplePtr &positions = sample.getPositions(); + + ICompoundProperty prop = schema.getArbGeomParams(); + N3fArraySamplePtr vnormals; + + if (has_property(prop, "N")) { + const Alembic::Util::uint32_t itime = static_cast<Alembic::Util::uint32_t>( + selector.getRequestedTime()); + const IN3fArrayProperty &normals_prop = IN3fArrayProperty(prop, "N", itime); + + if (normals_prop) { + vnormals = normals_prop.getValue(selector); + } + } + + read_mverts(config.mvert, positions, vnormals); +} + +struct Mesh *AbcPointsReader::read_mesh(struct Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int /*read_flag*/, + const char **err_str) +{ + IPointsSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + *err_str = "Error reading points sample; more detail on the console"; + printf("Alembic: error reading points sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + + Mesh *new_mesh = NULL; + + if (existing_mesh->totvert != positions->size()) { + new_mesh = BKE_mesh_new_nomain(positions->size(), 0, 0, 0, 0); + } + + CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh); + read_points_sample(m_schema, sample_sel, config); + + return new_mesh ? new_mesh : existing_mesh; +} diff --git a/source/blender/io/alembic/intern/abc_reader_points.h b/source/blender/io/alembic/intern/abc_reader_points.h new file mode 100644 index 00000000000..31ad6c4589b --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_points.h @@ -0,0 +1,54 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_POINTS_H__ +#define __ABC_READER_POINTS_H__ + +#include "abc_reader_object.h" +#include "abc_customdata.h" + +class AbcPointsReader : public AbcObjectReader { + Alembic::AbcGeom::IPointsSchema m_schema; + Alembic::AbcGeom::IPointsSchema::Sample m_sample; + + public: + AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); +}; + +void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema, + const Alembic::AbcGeom::ISampleSelector &selector, + CDStreamConfig &config); + +#endif /* __ABC_READER_POINTS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_transform.cc b/source/blender/io/alembic/intern/abc_reader_transform.cc new file mode 100644 index 00000000000..ce569a9ccb5 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_transform.cc @@ -0,0 +1,76 @@ +/* + * 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_reader_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_object_types.h" + +#include "BLI_utildefines.h" + +#include "BKE_object.h" +} + +using Alembic::Abc::ISampleSelector; + +AbcEmptyReader::AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + /* Empties have no data. It makes the import of Alembic files easier to + * understand when we name the empty after its name in Alembic. */ + m_object_name = object.getName(); + + Alembic::AbcGeom::IXform xform(object, Alembic::AbcGeom::kWrapExisting); + m_schema = xform.getSchema(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcEmptyReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcEmptyReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::IXform::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to XForm when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_EMPTY) { + *err_str = "Object type mismatch, Alembic object path points to XForm."; + return false; + } + + return true; +} + +void AbcEmptyReader::readObjectData(Main *bmain, const ISampleSelector &UNUSED(sample_sel)) +{ + m_object = BKE_object_add_only_object(bmain, OB_EMPTY, m_object_name.c_str()); + m_object->data = NULL; +} diff --git a/source/blender/io/alembic/intern/abc_reader_transform.h b/source/blender/io/alembic/intern/abc_reader_transform.h new file mode 100644 index 00000000000..6b4d23c1884 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_transform.h @@ -0,0 +1,42 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_TRANSFORM_H__ +#define __ABC_READER_TRANSFORM_H__ + +#include "abc_reader_object.h" + +#include <Alembic/AbcGeom/All.h> + +class AbcEmptyReader : public AbcObjectReader { + Alembic::AbcGeom::IXformSchema m_schema; + + public: + AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); +}; + +#endif /* __ABC_READER_TRANSFORM_H__ */ diff --git a/source/blender/io/alembic/intern/abc_util.cc b/source/blender/io/alembic/intern/abc_util.cc new file mode 100644 index 00000000000..b26ef8b3b76 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_util.cc @@ -0,0 +1,393 @@ +/* + * 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_util.h" + +#include "abc_reader_camera.h" +#include "abc_reader_curves.h" +#include "abc_reader_mesh.h" +#include "abc_reader_nurbs.h" +#include "abc_reader_points.h" +#include "abc_reader_transform.h" + +#include <Alembic/AbcMaterial/IMaterial.h> + +#include <algorithm> + +extern "C" { +#include "DNA_object_types.h" + +#include "BLI_math_geom.h" + +#include "PIL_time.h" +} + +std::string get_id_name(const Object *const ob) +{ + if (!ob) { + return ""; + } + + return get_id_name(&ob->id); +} + +std::string get_id_name(const ID *const id) +{ + std::string name(id->name + 2); + std::replace(name.begin(), name.end(), ' ', '_'); + std::replace(name.begin(), name.end(), '.', '_'); + std::replace(name.begin(), name.end(), ':', '_'); + + return name; +} + +/** + * \brief get_object_dag_path_name returns the name under which the object + * will be exported in the Alembic file. It is of the form + * "[../grandparent/]parent/object" if dupli_parent is NULL, or + * "dupli_parent/[../grandparent/]parent/object" otherwise. + * \param ob: + * \param dupli_parent: + * \return + */ +std::string get_object_dag_path_name(const Object *const ob, Object *dupli_parent) +{ + std::string name = get_id_name(ob); + + Object *p = ob->parent; + + while (p) { + name = get_id_name(p) + "/" + name; + p = p->parent; + } + + if (dupli_parent && (ob != dupli_parent)) { + name = get_id_name(dupli_parent) + "/" + name; + } + + return name; +} + +Imath::M44d convert_matrix_datatype(float mat[4][4]) +{ + Imath::M44d m; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + m[i][j] = mat[i][j]; + } + } + + return m; +} + +void convert_matrix_datatype(const Imath::M44d &xform, float r_mat[4][4]) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + r_mat[i][j] = static_cast<float>(xform[i][j]); + } + } +} + +void split(const std::string &s, const char delim, std::vector<std::string> &tokens) +{ + tokens.clear(); + + std::stringstream ss(s); + std::string item; + + while (std::getline(ss, item, delim)) { + if (!item.empty()) { + tokens.push_back(item); + } + } +} + +void create_swapped_rotation_matrix(float rot_x_mat[3][3], + float rot_y_mat[3][3], + float rot_z_mat[3][3], + const float euler[3], + AbcAxisSwapMode mode) +{ + const float rx = euler[0]; + float ry; + float rz; + + /* Apply transformation */ + switch (mode) { + case ABC_ZUP_FROM_YUP: + ry = -euler[2]; + rz = euler[1]; + break; + case ABC_YUP_FROM_ZUP: + ry = euler[2]; + rz = -euler[1]; + break; + default: + ry = 0.0f; + rz = 0.0f; + BLI_assert(false); + break; + } + + unit_m3(rot_x_mat); + unit_m3(rot_y_mat); + unit_m3(rot_z_mat); + + rot_x_mat[1][1] = cos(rx); + rot_x_mat[2][1] = -sin(rx); + rot_x_mat[1][2] = sin(rx); + rot_x_mat[2][2] = cos(rx); + + rot_y_mat[2][2] = cos(ry); + rot_y_mat[0][2] = -sin(ry); + rot_y_mat[2][0] = sin(ry); + rot_y_mat[0][0] = cos(ry); + + rot_z_mat[0][0] = cos(rz); + rot_z_mat[1][0] = -sin(rz); + rot_z_mat[0][1] = sin(rz); + rot_z_mat[1][1] = cos(rz); +} + +/* Convert matrix from Z=up to Y=up or vice versa. + * Use yup_mat = zup_mat for in-place conversion. */ +void copy_m44_axis_swap(float dst_mat[4][4], float src_mat[4][4], AbcAxisSwapMode mode) +{ + float dst_rot[3][3], src_rot[3][3], dst_scale_mat[4][4]; + float rot_x_mat[3][3], rot_y_mat[3][3], rot_z_mat[3][3]; + float src_trans[3], dst_scale[3], src_scale[3], euler[3]; + + zero_v3(src_trans); + zero_v3(dst_scale); + zero_v3(src_scale); + zero_v3(euler); + unit_m3(src_rot); + unit_m3(dst_rot); + unit_m4(dst_scale_mat); + + /* TODO(Sybren): This code assumes there is no sheer component and no + * homogeneous scaling component, which is not always true when writing + * non-hierarchical (e.g. flat) objects (e.g. when parent has non-uniform + * scale and the child rotates). This is currently not taken into account + * when axis-swapping. */ + + /* Extract translation, rotation, and scale form matrix. */ + mat4_to_loc_rot_size(src_trans, src_rot, src_scale, src_mat); + + /* Get euler angles from rotation matrix. */ + mat3_to_eulO(euler, ROT_MODE_XZY, src_rot); + + /* Create X, Y, Z rotation matrices from euler angles. */ + create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, mode); + + /* Concatenate rotation matrices. */ + mul_m3_m3m3(dst_rot, dst_rot, rot_z_mat); + mul_m3_m3m3(dst_rot, dst_rot, rot_y_mat); + mul_m3_m3m3(dst_rot, dst_rot, rot_x_mat); + + mat3_to_eulO(euler, ROT_MODE_XZY, dst_rot); + + /* Start construction of dst_mat from rotation matrix */ + unit_m4(dst_mat); + copy_m4_m3(dst_mat, dst_rot); + + /* Apply translation */ + switch (mode) { + case ABC_ZUP_FROM_YUP: + copy_zup_from_yup(dst_mat[3], src_trans); + break; + case ABC_YUP_FROM_ZUP: + copy_yup_from_zup(dst_mat[3], src_trans); + break; + default: + BLI_assert(false); + } + + /* Apply scale matrix. Swaps y and z, but does not + * negate like translation does. */ + dst_scale[0] = src_scale[0]; + dst_scale[1] = src_scale[2]; + dst_scale[2] = src_scale[1]; + + size_to_mat4(dst_scale_mat, dst_scale); + mul_m4_m4m4(dst_mat, dst_mat, dst_scale_mat); +} + +/* Recompute transform matrix of object in new coordinate system + * (from Z-Up to Y-Up). */ +void create_transform_matrix(Object *obj, + float r_yup_mat[4][4], + AbcMatrixMode mode, + Object *proxy_from) +{ + float zup_mat[4][4]; + + /* get local or world matrix. */ + if (mode == ABC_MATRIX_LOCAL && obj->parent) { + /* Note that this produces another matrix than the local matrix, due to + * constraints and modifiers as well as the obj->parentinv matrix. */ + invert_m4_m4(obj->parent->imat, obj->parent->obmat); + mul_m4_m4m4(zup_mat, obj->parent->imat, obj->obmat); + } + else { + copy_m4_m4(zup_mat, obj->obmat); + } + + if (proxy_from) { + mul_m4_m4m4(zup_mat, proxy_from->obmat, zup_mat); + } + + copy_m44_axis_swap(r_yup_mat, zup_mat, ABC_YUP_FROM_ZUP); +} + +bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name) +{ + if (!prop.valid()) { + return false; + } + + return prop.getPropertyHeader(name) != NULL; +} + +typedef std::pair<Alembic::AbcCoreAbstract::index_t, float> index_time_pair_t; + +float get_weight_and_index(float time, + const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling, + int samples_number, + Alembic::AbcGeom::index_t &i0, + Alembic::AbcGeom::index_t &i1) +{ + samples_number = std::max(samples_number, 1); + + index_time_pair_t t0 = time_sampling->getFloorIndex(time, samples_number); + i0 = i1 = t0.first; + + if (samples_number == 1 || (fabs(time - t0.second) < 0.0001f)) { + return 0.0f; + } + + index_time_pair_t t1 = time_sampling->getCeilIndex(time, samples_number); + i1 = t1.first; + + if (i0 == i1) { + return 0.0f; + } + + const float bias = (time - t0.second) / (t1.second - t0.second); + + if (fabs(1.0f - bias) < 0.0001f) { + i0 = i1; + return 0.0f; + } + + return bias; +} + +//#define USE_NURBS + +AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSettings &settings) +{ + AbcObjectReader *reader = NULL; + + const Alembic::AbcGeom::MetaData &md = object.getMetaData(); + + if (Alembic::AbcGeom::IXform::matches(md)) { + reader = new AbcEmptyReader(object, settings); + } + else if (Alembic::AbcGeom::IPolyMesh::matches(md)) { + reader = new AbcMeshReader(object, settings); + } + else if (Alembic::AbcGeom::ISubD::matches(md)) { + reader = new AbcSubDReader(object, settings); + } + else if (Alembic::AbcGeom::INuPatch::matches(md)) { +#ifdef USE_NURBS + /* TODO(kevin): importing cyclic NURBS from other software crashes + * at the moment. This is due to the fact that NURBS in other + * software have duplicated points which causes buffer overflows in + * Blender. Need to figure out exactly how these points are + * duplicated, in all cases (cyclic U, cyclic V, and cyclic UV). + * Until this is fixed, disabling NURBS reading. */ + reader = new AbcNurbsReader(child, settings); +#endif + } + else if (Alembic::AbcGeom::ICamera::matches(md)) { + reader = new AbcCameraReader(object, settings); + } + else if (Alembic::AbcGeom::IPoints::matches(md)) { + reader = new AbcPointsReader(object, settings); + } + else if (Alembic::AbcMaterial::IMaterial::matches(md)) { + /* Pass for now. */ + } + else if (Alembic::AbcGeom::ILight::matches(md)) { + /* Pass for now. */ + } + else if (Alembic::AbcGeom::IFaceSet::matches(md)) { + /* Pass, those are handled in the mesh reader. */ + } + else if (Alembic::AbcGeom::ICurves::matches(md)) { + reader = new AbcCurveReader(object, settings); + } + else { + std::cerr << "Alembic: unknown how to handle objects of schema '" << md.get("schemaObjTitle") + << "', skipping object '" << object.getFullName() << "'" << std::endl; + } + + return reader; +} + +/* ********************** */ + +ScopeTimer::ScopeTimer(const char *message) + : m_message(message), m_start(PIL_check_seconds_timer()) +{ +} + +ScopeTimer::~ScopeTimer() +{ + fprintf(stderr, "%s: %fs\n", m_message, PIL_check_seconds_timer() - m_start); +} + +/* ********************** */ + +std::string SimpleLogger::str() const +{ + return m_stream.str(); +} + +void SimpleLogger::clear() +{ + m_stream.clear(); + m_stream.str(""); +} + +std::ostringstream &SimpleLogger::stream() +{ + return m_stream; +} + +std::ostream &operator<<(std::ostream &os, const SimpleLogger &logger) +{ + os << logger.str(); + return os; +} diff --git a/source/blender/io/alembic/intern/abc_util.h b/source/blender/io/alembic/intern/abc_util.h new file mode 100644 index 00000000000..0b3462c2132 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_util.h @@ -0,0 +1,236 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_UTIL_H__ +#define __ABC_UTIL_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +#ifdef _MSC_VER +# define ABC_INLINE static __forceinline +#else +# define ABC_INLINE static inline +#endif + +/** + * \brief The CacheReader struct is only used for anonymous pointers, + * to interface between C and C++ code. This library only creates + * pointers to AbcObjectReader (or subclasses thereof). + */ +struct CacheReader { + int unused; +}; + +using Alembic::Abc::chrono_t; + +class AbcObjectReader; +struct ImportSettings; + +struct ID; +struct Object; + +std::string get_id_name(const ID *const id); +std::string get_id_name(const Object *const ob); +std::string get_object_dag_path_name(const Object *const ob, Object *dupli_parent); + +/* Convert from float to Alembic matrix representations. Does NOT convert from Z-up to Y-up. */ +Imath::M44d convert_matrix_datatype(float mat[4][4]); +/* Convert from Alembic to float matrix representations. Does NOT convert from Y-up to Z-up. */ +void convert_matrix_datatype(const Imath::M44d &xform, float r_mat[4][4]); + +typedef enum { + ABC_MATRIX_WORLD = 1, + ABC_MATRIX_LOCAL = 2, +} AbcMatrixMode; +void create_transform_matrix(Object *obj, + float r_transform_mat[4][4], + AbcMatrixMode mode, + Object *proxy_from); + +void split(const std::string &s, const char delim, std::vector<std::string> &tokens); + +template<class TContainer> bool begins_with(const TContainer &input, const TContainer &match) +{ + return input.size() >= match.size() && std::equal(match.begin(), match.end(), input.begin()); +} + +template<typename Schema> +void get_min_max_time_ex(const Schema &schema, chrono_t &min, chrono_t &max) +{ + const Alembic::Abc::TimeSamplingPtr &time_samp = schema.getTimeSampling(); + + if (!schema.isConstant()) { + const size_t num_samps = schema.getNumSamples(); + + if (num_samps > 0) { + const chrono_t min_time = time_samp->getSampleTime(0); + min = std::min(min, min_time); + + const chrono_t max_time = time_samp->getSampleTime(num_samps - 1); + max = std::max(max, max_time); + } + } +} + +template<typename Schema> +void get_min_max_time(const Alembic::AbcGeom::IObject &object, + const Schema &schema, + chrono_t &min, + chrono_t &max) +{ + get_min_max_time_ex(schema, min, max); + + const Alembic::AbcGeom::IObject &parent = object.getParent(); + if (parent.valid() && Alembic::AbcGeom::IXform::matches(parent.getMetaData())) { + Alembic::AbcGeom::IXform xform(parent, Alembic::AbcGeom::kWrapExisting); + get_min_max_time_ex(xform.getSchema(), min, max); + } +} + +bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name); + +float get_weight_and_index(float time, + const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling, + int samples_number, + Alembic::AbcGeom::index_t &i0, + Alembic::AbcGeom::index_t &i1); + +AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSettings &settings); + +/* ************************** */ + +/* TODO(kevin): for now keeping these transformations hardcoded to make sure + * everything works properly, and also because Alembic is almost exclusively + * used in Y-up software, but eventually they'll be set by the user in the UI + * like other importers/exporters do, to support other axis. */ + +/* Copy from Y-up to Z-up. */ + +ABC_INLINE void copy_zup_from_yup(float zup[3], const float yup[3]) +{ + const float old_yup1 = yup[1]; /* in case zup == yup */ + zup[0] = yup[0]; + zup[1] = -yup[2]; + zup[2] = old_yup1; +} + +ABC_INLINE void copy_zup_from_yup(short zup[3], const short yup[3]) +{ + const short old_yup1 = yup[1]; /* in case zup == yup */ + zup[0] = yup[0]; + zup[1] = -yup[2]; + zup[2] = old_yup1; +} + +/* Copy from Z-up to Y-up. */ + +ABC_INLINE void copy_yup_from_zup(float yup[3], const float zup[3]) +{ + const float old_zup1 = zup[1]; /* in case yup == zup */ + yup[0] = zup[0]; + yup[1] = zup[2]; + yup[2] = -old_zup1; +} + +ABC_INLINE void copy_yup_from_zup(short yup[3], const short zup[3]) +{ + const short old_zup1 = zup[1]; /* in case yup == zup */ + yup[0] = zup[0]; + yup[1] = zup[2]; + yup[2] = -old_zup1; +} + +/* Names are given in (dst, src) order, just like + * the parameters of copy_m44_axis_swap() */ +typedef enum { + ABC_ZUP_FROM_YUP = 1, + ABC_YUP_FROM_ZUP = 2, +} AbcAxisSwapMode; + +/* Create a rotation matrix for each axis from euler angles. + * Euler angles are swapped to change coordinate system. */ +void create_swapped_rotation_matrix(float rot_x_mat[3][3], + float rot_y_mat[3][3], + float rot_z_mat[3][3], + const float euler[3], + AbcAxisSwapMode mode); + +void copy_m44_axis_swap(float dst_mat[4][4], float src_mat[4][4], AbcAxisSwapMode mode); + +/* *************************** */ + +#undef ABC_DEBUG_TIME + +class ScopeTimer { + const char *m_message; + double m_start; + + public: + ScopeTimer(const char *message); + ~ScopeTimer(); +}; + +#ifdef ABC_DEBUG_TIME +# define SCOPE_TIMER(message) ScopeTimer prof(message) +#else +# define SCOPE_TIMER(message) +#endif + +/* *************************** */ + +/** + * Utility class whose purpose is to more easily log related information. An + * instance of the SimpleLogger can be created in any context, and will hold a + * copy of all the strings passed to its output stream. + * + * Different instances of the class may be accessed from different threads, + * although accessing the same instance from different threads will lead to race + * conditions. + */ +class SimpleLogger { + std::ostringstream m_stream; + + public: + /** + * Return a copy of the string contained in the SimpleLogger's stream. + */ + std::string str() const; + + /** + * Remove the bits set on the SimpleLogger's stream and clear its string. + */ + void clear(); + + /** + * Return a reference to the SimpleLogger's stream, in order to e.g. push + * content into it. + */ + std::ostringstream &stream(); +}; + +#define ABC_LOG(logger) logger.stream() + +/** + * Pass the content of the logger's stream to the specified std::ostream. + */ +std::ostream &operator<<(std::ostream &os, const SimpleLogger &logger); + +#endif /* __ABC_UTIL_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_archive.cc b/source/blender/io/alembic/intern/abc_writer_archive.cc new file mode 100644 index 00000000000..af18d480a18 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_archive.cc @@ -0,0 +1,114 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_archive.h" +extern "C" { +#include "BKE_blender_version.h" + +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "DNA_scene_types.h" +} + +#ifdef WIN32 +# include "utfconv.h" +#endif + +#include <fstream> + +using Alembic::Abc::ErrorHandler; +using Alembic::Abc::kWrapExisting; +using Alembic::Abc::OArchive; + +/* This kinda duplicates CreateArchiveWithInfo, but Alembic does not seem to + * have a version supporting streams. */ +static OArchive create_archive(std::ostream *ostream, + const std::string &filename, + const std::string &scene_name, + double scene_fps, + bool ogawa) +{ + Alembic::Abc::MetaData abc_metadata; + + abc_metadata.set(Alembic::Abc::kApplicationNameKey, "Blender"); + abc_metadata.set(Alembic::Abc::kUserDescriptionKey, scene_name); + abc_metadata.set("blender_version", versionstr); + abc_metadata.set("FramesPerTimeUnit", std::to_string(scene_fps)); + + time_t raw_time; + time(&raw_time); + char buffer[128]; + +#if defined _WIN32 || defined _WIN64 + ctime_s(buffer, 128, &raw_time); +#else + ctime_r(&raw_time, buffer); +#endif + + const std::size_t buffer_len = strlen(buffer); + if (buffer_len > 0 && buffer[buffer_len - 1] == '\n') { + buffer[buffer_len - 1] = '\0'; + } + + abc_metadata.set(Alembic::Abc::kDateWrittenKey, buffer); + + ErrorHandler::Policy policy = ErrorHandler::kThrowPolicy; + +#ifdef WITH_ALEMBIC_HDF5 + if (!ogawa) { + return OArchive(Alembic::AbcCoreHDF5::WriteArchive(), filename, abc_metadata, policy); + } +#else + static_cast<void>(filename); + static_cast<void>(ogawa); +#endif + + Alembic::AbcCoreOgawa::WriteArchive archive_writer; + return OArchive(archive_writer(ostream, abc_metadata), kWrapExisting, policy); +} + +ArchiveWriter::ArchiveWriter(const char *filename, + const std::string &abc_scene_name, + const Scene *scene, + bool do_ogawa) +{ + /* Use stream to support unicode character paths on Windows. */ + if (do_ogawa) { +#ifdef WIN32 + UTF16_ENCODE(filename); + std::wstring wstr(filename_16); + m_outfile.open(wstr.c_str(), std::ios::out | std::ios::binary); + UTF16_UN_ENCODE(filename); +#else + m_outfile.open(filename, std::ios::out | std::ios::binary); +#endif + } + + m_archive = create_archive(&m_outfile, filename, abc_scene_name, FPS, do_ogawa); +} + +OArchive &ArchiveWriter::archive() +{ + return m_archive; +} diff --git a/source/blender/io/alembic/intern/abc_writer_archive.h b/source/blender/io/alembic/intern/abc_writer_archive.h new file mode 100644 index 00000000000..e261e60990a --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_archive.h @@ -0,0 +1,58 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_ARCHIVE_H__ +#define __ABC_WRITER_ARCHIVE_H__ + +#include <Alembic/Abc/All.h> + +#ifdef WITH_ALEMBIC_HDF5 +# include <Alembic/AbcCoreHDF5/All.h> +#endif + +#include <Alembic/AbcCoreOgawa/All.h> + +#include <fstream> + +struct Main; +struct Scene; + +/* Wrappers around input and output archives. The goal is to be able to use + * streams so that unicode paths work on Windows (T49112), and to make sure that + * the stream objects remain valid as long as the archives are open. + */ + +class ArchiveWriter { + std::ofstream m_outfile; + Alembic::Abc::OArchive m_archive; + + public: + ArchiveWriter(const char *filename, + const std::string &abc_scene_name, + const Scene *scene, + bool do_ogawa); + + Alembic::Abc::OArchive &archive(); +}; + +#endif /* __ABC_WRITER_ARCHIVE_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_camera.cc b/source/blender/io/alembic/intern/abc_writer_camera.cc new file mode 100644 index 00000000000..e705e5ba911 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_camera.cc @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_camera.h" +#include "abc_writer_transform.h" + +extern "C" { +#include "DNA_camera_types.h" +#include "DNA_object_types.h" +} + +using Alembic::AbcGeom::OCamera; +using Alembic::AbcGeom::OFloatProperty; + +AbcCameraWriter::AbcCameraWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + OCamera camera(parent->alembicXform(), m_name, m_time_sampling); + m_camera_schema = camera.getSchema(); + + m_custom_data_container = m_camera_schema.getUserProperties(); + m_stereo_distance = OFloatProperty(m_custom_data_container, "stereoDistance", m_time_sampling); + m_eye_separation = OFloatProperty(m_custom_data_container, "eyeSeparation", m_time_sampling); +} + +void AbcCameraWriter::do_write() +{ + Camera *cam = static_cast<Camera *>(m_object->data); + + m_stereo_distance.set(cam->stereo.convergence_distance); + m_eye_separation.set(cam->stereo.interocular_distance); + + const double apperture_x = cam->sensor_x / 10.0; + const double apperture_y = cam->sensor_y / 10.0; + const double film_aspect = apperture_x / apperture_y; + + m_camera_sample.setFocalLength(cam->lens); + m_camera_sample.setHorizontalAperture(apperture_x); + m_camera_sample.setVerticalAperture(apperture_y); + m_camera_sample.setHorizontalFilmOffset(apperture_x * cam->shiftx); + m_camera_sample.setVerticalFilmOffset(apperture_y * cam->shifty * film_aspect); + m_camera_sample.setNearClippingPlane(cam->clip_start); + m_camera_sample.setFarClippingPlane(cam->clip_end); + + if (cam->dof.focus_object) { + Imath::V3f v(m_object->loc[0] - cam->dof.focus_object->loc[0], + m_object->loc[1] - cam->dof.focus_object->loc[1], + m_object->loc[2] - cam->dof.focus_object->loc[2]); + m_camera_sample.setFocusDistance(v.length()); + } + else { + m_camera_sample.setFocusDistance(cam->dof.focus_distance); + } + + /* Blender camera does not have an fstop param, so try to find a custom prop + * instead. */ + m_camera_sample.setFStop(cam->dof.aperture_fstop); + + m_camera_sample.setLensSqueezeRatio(1.0); + m_camera_schema.set(m_camera_sample); +} diff --git a/source/blender/io/alembic/intern/abc_writer_camera.h b/source/blender/io/alembic/intern/abc_writer_camera.h new file mode 100644 index 00000000000..3b515911a48 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_camera.h @@ -0,0 +1,45 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_CAMERA_H__ +#define __ABC_WRITER_CAMERA_H__ + +#include "abc_writer_object.h" + +/* ************************************************************************** */ + +class AbcCameraWriter : public AbcObjectWriter { + Alembic::AbcGeom::OCameraSchema m_camera_schema; + Alembic::AbcGeom::CameraSample m_camera_sample; + Alembic::AbcGeom::OCompoundProperty m_custom_data_container; + Alembic::AbcGeom::OFloatProperty m_stereo_distance; + Alembic::AbcGeom::OFloatProperty m_eye_separation; + + public: + AbcCameraWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + private: + virtual void do_write(); +}; + +#endif /* __ABC_WRITER_CAMERA_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_curves.cc b/source/blender/io/alembic/intern/abc_writer_curves.cc new file mode 100644 index 00000000000..3ab9b365a72 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_curves.cc @@ -0,0 +1,189 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_curves.h" +#include "abc_reader_curves.h" +#include "abc_writer_transform.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +} + +using Alembic::AbcGeom::OCompoundProperty; +using Alembic::AbcGeom::OCurves; +using Alembic::AbcGeom::OCurvesSchema; +using Alembic::AbcGeom::OInt16Property; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OV2fGeomParam; + +AbcCurveWriter::AbcCurveWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + OCurves curves(parent->alembicXform(), m_name, m_time_sampling); + m_schema = curves.getSchema(); + + Curve *cu = static_cast<Curve *>(m_object->data); + OCompoundProperty user_props = m_schema.getUserProperties(); + OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME); + user_prop_resolu.set(cu->resolu); +} + +void AbcCurveWriter::do_write() +{ + Curve *curve = static_cast<Curve *>(m_object->data); + + std::vector<Imath::V3f> verts; + std::vector<int32_t> vert_counts; + std::vector<float> widths; + std::vector<float> weights; + std::vector<float> knots; + std::vector<uint8_t> orders; + Imath::V3f temp_vert; + + Alembic::AbcGeom::BasisType curve_basis; + Alembic::AbcGeom::CurveType curve_type; + Alembic::AbcGeom::CurvePeriodicity periodicity; + + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (; nurbs; nurbs = nurbs->next) { + if (nurbs->bp) { + curve_basis = Alembic::AbcGeom::kNoBasis; + curve_type = Alembic::AbcGeom::kVariableOrder; + + const int totpoint = nurbs->pntsu * nurbs->pntsv; + + const BPoint *point = nurbs->bp; + + for (int i = 0; i < totpoint; i++, point++) { + copy_yup_from_zup(temp_vert.getValue(), point->vec); + verts.push_back(temp_vert); + weights.push_back(point->vec[3]); + widths.push_back(point->radius); + } + } + else if (nurbs->bezt) { + curve_basis = Alembic::AbcGeom::kBezierBasis; + curve_type = Alembic::AbcGeom::kCubic; + + const int totpoint = nurbs->pntsu; + + const BezTriple *bezier = nurbs->bezt; + + /* TODO(kevin): store info about handles, Alembic doesn't have this. */ + for (int i = 0; i < totpoint; i++, bezier++) { + copy_yup_from_zup(temp_vert.getValue(), bezier->vec[1]); + verts.push_back(temp_vert); + widths.push_back(bezier->radius); + } + } + + if ((nurbs->flagu & CU_NURB_ENDPOINT) != 0) { + periodicity = Alembic::AbcGeom::kNonPeriodic; + } + else if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) { + periodicity = Alembic::AbcGeom::kPeriodic; + + /* Duplicate the start points to indicate that the curve is actually + * cyclic since other software need those. + */ + + for (int i = 0; i < nurbs->orderu; i++) { + verts.push_back(verts[i]); + } + } + + if (nurbs->knotsu != NULL) { + const size_t num_knots = KNOTSU(nurbs); + + /* Add an extra knot at the beginning and end of the array since most apps + * require/expect them. */ + knots.resize(num_knots + 2); + + for (int i = 0; i < num_knots; i++) { + knots[i + 1] = nurbs->knotsu[i]; + } + + if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) { + knots[0] = nurbs->knotsu[0]; + knots[num_knots - 1] = nurbs->knotsu[num_knots - 1]; + } + else { + knots[0] = (2.0f * nurbs->knotsu[0] - nurbs->knotsu[1]); + knots[num_knots - 1] = (2.0f * nurbs->knotsu[num_knots - 1] - + nurbs->knotsu[num_knots - 2]); + } + } + + orders.push_back(nurbs->orderu); + vert_counts.push_back(verts.size()); + } + + Alembic::AbcGeom::OFloatGeomParam::Sample width_sample; + width_sample.setVals(widths); + + m_sample = OCurvesSchema::Sample(verts, + vert_counts, + curve_type, + periodicity, + width_sample, + OV2fGeomParam::Sample(), /* UVs */ + ON3fGeomParam::Sample(), /* normals */ + curve_basis, + weights, + orders, + knots); + + m_sample.setSelfBounds(bounds()); + m_schema.set(m_sample); +} + +AbcCurveMeshWriter::AbcCurveMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcGenericMeshWriter(ob, parent, time_sampling, settings) +{ +} + +Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/, + Object *ob_eval, + bool &r_needsfree) +{ + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); + if (mesh_eval != NULL) { + /* Mesh_eval only exists when generative modifiers are in use. */ + r_needsfree = false; + return mesh_eval; + } + + r_needsfree = true; + return BKE_mesh_new_nomain_from_curve(ob_eval); +} diff --git a/source/blender/io/alembic/intern/abc_writer_curves.h b/source/blender/io/alembic/intern/abc_writer_curves.h new file mode 100644 index 00000000000..d6d8c0a7f11 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_curves.h @@ -0,0 +1,55 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_CURVES_H__ +#define __ABC_WRITER_CURVES_H__ + +#include "abc_writer_object.h" +#include "abc_writer_mesh.h" + +class AbcCurveWriter : public AbcObjectWriter { + Alembic::AbcGeom::OCurvesSchema m_schema; + Alembic::AbcGeom::OCurvesSchema::Sample m_sample; + + public: + AbcCurveWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + protected: + void do_write(); +}; + +class AbcCurveMeshWriter : public AbcGenericMeshWriter { + public: + AbcCurveMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + protected: + Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree); +}; + +#endif /* __ABC_WRITER_CURVES_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_hair.cc b/source/blender/io/alembic/intern/abc_writer_hair.cc new file mode 100644 index 00000000000..bbba03ed7f4 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_hair.cc @@ -0,0 +1,292 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_hair.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +#include <cstdio> + +extern "C" { +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "BLI_math_geom.h" + +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_particle.h" +} + +using Alembic::Abc::P3fArraySamplePtr; + +using Alembic::AbcGeom::OCurves; +using Alembic::AbcGeom::OCurvesSchema; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OV2fGeomParam; + +/* ************************************************************************** */ + +AbcHairWriter::AbcHairWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys) + : AbcObjectWriter(ob, time_sampling, settings, parent), m_uv_warning_shown(false) +{ + m_psys = psys; + + OCurves curves(parent->alembicXform(), psys->name, m_time_sampling); + m_schema = curves.getSchema(); +} + +void AbcHairWriter::do_write() +{ + if (!m_psys) { + return; + } + Mesh *mesh = mesh_get_eval_final( + m_settings.depsgraph, m_settings.scene, m_object, &CD_MASK_MESH); + BKE_mesh_tessface_ensure(mesh); + + std::vector<Imath::V3f> verts; + std::vector<int32_t> hvertices; + std::vector<Imath::V2f> uv_values; + std::vector<Imath::V3f> norm_values; + + if (m_psys->pathcache) { + ParticleSettings *part = m_psys->part; + bool export_children = m_settings.export_child_hairs && m_psys->childcache && + part->childtype != 0; + + if (!export_children || part->draw & PART_DRAW_PARENT) { + write_hair_sample(mesh, part, verts, norm_values, uv_values, hvertices); + } + + if (export_children) { + write_hair_child_sample(mesh, part, verts, norm_values, uv_values, hvertices); + } + } + + Alembic::Abc::P3fArraySample iPos(verts); + m_sample = OCurvesSchema::Sample(iPos, hvertices); + m_sample.setBasis(Alembic::AbcGeom::kNoBasis); + m_sample.setType(Alembic::AbcGeom::kLinear); + m_sample.setWrap(Alembic::AbcGeom::kNonPeriodic); + + if (!uv_values.empty()) { + OV2fGeomParam::Sample uv_smp; + uv_smp.setVals(uv_values); + m_sample.setUVs(uv_smp); + } + + if (!norm_values.empty()) { + ON3fGeomParam::Sample norm_smp; + norm_smp.setVals(norm_values); + m_sample.setNormals(norm_smp); + } + + m_sample.setSelfBounds(bounds()); + m_schema.set(m_sample); +} + +void AbcHairWriter::write_hair_sample(Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices) +{ + /* Get untransformed vertices, there's a xform under the hair. */ + float inv_mat[4][4]; + invert_m4_m4_safe(inv_mat, m_object->obmat); + + MTFace *mtface = mesh->mtface; + MFace *mface = mesh->mface; + MVert *mverts = mesh->mvert; + + if ((!mtface || !mface) && !m_uv_warning_shown) { + std::fprintf(stderr, + "Warning, no UV set found for underlying geometry of %s.\n", + m_object->id.name + 2); + m_uv_warning_shown = true; + } + + ParticleData *pa = m_psys->particles; + int k; + + ParticleCacheKey **cache = m_psys->pathcache; + ParticleCacheKey *path; + float normal[3]; + Imath::V3f tmp_nor; + + for (int p = 0; p < m_psys->totpart; p++, pa++) { + /* underlying info for faces-only emission */ + path = cache[p]; + + /* Write UV and normal vectors */ + if (part->from == PART_FROM_FACE && mtface) { + const int num = pa->num_dmcache >= 0 ? pa->num_dmcache : pa->num; + + if (num < mesh->totface) { + /* TODO(Sybren): check whether the NULL check here and if(mface) are actually required */ + MFace *face = mface == NULL ? NULL : &mface[num]; + MTFace *tface = mtface + num; + + if (mface) { + float r_uv[2], mapfw[4], vec[3]; + + psys_interpolate_uvs(tface, face->v4, pa->fuv, r_uv); + uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1])); + + psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, normal, NULL, NULL, NULL); + + copy_yup_from_zup(tmp_nor.getValue(), normal); + norm_values.push_back(tmp_nor); + } + } + else { + std::fprintf(stderr, "Particle to faces overflow (%d/%d)\n", num, mesh->totface); + } + } + else if (part->from == PART_FROM_VERT && mtface) { + /* vertex id */ + const int num = (pa->num_dmcache >= 0) ? pa->num_dmcache : pa->num; + + /* iterate over all faces to find a corresponding underlying UV */ + for (int n = 0; n < mesh->totface; n++) { + MFace *face = &mface[n]; + MTFace *tface = mtface + n; + unsigned int vtx[4]; + vtx[0] = face->v1; + vtx[1] = face->v2; + vtx[2] = face->v3; + vtx[3] = face->v4; + bool found = false; + + for (int o = 0; o < 4; o++) { + if (o > 2 && vtx[o] == 0) { + break; + } + + if (vtx[o] == num) { + uv_values.push_back(Imath::V2f(tface->uv[o][0], tface->uv[o][1])); + + MVert *mv = mverts + vtx[o]; + + normal_short_to_float_v3(normal, mv->no); + copy_yup_from_zup(tmp_nor.getValue(), normal); + norm_values.push_back(tmp_nor); + found = true; + break; + } + } + + if (found) { + break; + } + } + } + + int steps = path->segments + 1; + hvertices.push_back(steps); + + for (k = 0; k < steps; k++, path++) { + float vert[3]; + copy_v3_v3(vert, path->co); + mul_m4_v3(inv_mat, vert); + + /* Convert Z-up to Y-up. */ + verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1])); + } + } +} + +void AbcHairWriter::write_hair_child_sample(Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices) +{ + /* Get untransformed vertices, there's a xform under the hair. */ + float inv_mat[4][4]; + invert_m4_m4_safe(inv_mat, m_object->obmat); + + MTFace *mtface = mesh->mtface; + MVert *mverts = mesh->mvert; + + ParticleCacheKey **cache = m_psys->childcache; + ParticleCacheKey *path; + + ChildParticle *pc = m_psys->child; + + for (int p = 0; p < m_psys->totchild; p++, pc++) { + path = cache[p]; + + if (part->from == PART_FROM_FACE && part->childtype != PART_CHILD_PARTICLES && mtface) { + const int num = pc->num; + if (num < 0) { + ABC_LOG(m_settings.logger) + << "Warning, child particle of hair system " << m_psys->name + << " has unknown face index of geometry of " << (m_object->id.name + 2) + << ", skipping child hair." << std::endl; + continue; + } + + MFace *face = &mesh->mface[num]; + MTFace *tface = mtface + num; + + float r_uv[2], tmpnor[3], mapfw[4], vec[3]; + + psys_interpolate_uvs(tface, face->v4, pc->fuv, r_uv); + uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1])); + + psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, tmpnor, NULL, NULL, NULL); + + /* Convert Z-up to Y-up. */ + norm_values.push_back(Imath::V3f(tmpnor[0], tmpnor[2], -tmpnor[1])); + } + else { + if (uv_values.size()) { + uv_values.push_back(uv_values[pc->parent]); + } + if (norm_values.size()) { + norm_values.push_back(norm_values[pc->parent]); + } + } + + int steps = path->segments + 1; + hvertices.push_back(steps); + + for (int k = 0; k < steps; k++) { + float vert[3]; + copy_v3_v3(vert, path->co); + mul_m4_v3(inv_mat, vert); + + /* Convert Z-up to Y-up. */ + verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1])); + + path++; + } + } +} diff --git a/source/blender/io/alembic/intern/abc_writer_hair.h b/source/blender/io/alembic/intern/abc_writer_hair.h new file mode 100644 index 00000000000..67d1b7b3d23 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_hair.h @@ -0,0 +1,62 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_HAIR_H__ +#define __ABC_WRITER_HAIR_H__ + +#include "abc_writer_object.h" + +struct ParticleSettings; +struct ParticleSystem; + +class AbcHairWriter : public AbcObjectWriter { + ParticleSystem *m_psys; + + Alembic::AbcGeom::OCurvesSchema m_schema; + Alembic::AbcGeom::OCurvesSchema::Sample m_sample; + + bool m_uv_warning_shown; + + public: + AbcHairWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys); + + private: + virtual void do_write(); + + void write_hair_sample(struct Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices); + + void write_hair_child_sample(struct Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices); +}; + +#endif /* __ABC_WRITER_HAIR_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_mball.cc b/source/blender/io/alembic/intern/abc_writer_mball.cc new file mode 100644 index 00000000000..cc0775bd537 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mball.cc @@ -0,0 +1,97 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_mball.h" +#include "abc_writer_mesh.h" + +extern "C" { +#include "DNA_meta_types.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" + +#include "BKE_displist.h" +#include "BKE_lib_id.h" +#include "BKE_mball.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_utildefines.h" +} + +AbcMBallWriter::AbcMBallWriter(Main *bmain, + Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcGenericMeshWriter(ob, parent, time_sampling, settings), m_bmain(bmain) +{ + m_is_animated = isAnimated(); +} + +AbcMBallWriter::~AbcMBallWriter() +{ +} + +bool AbcMBallWriter::isAnimated() const +{ + return true; +} + +Mesh *AbcMBallWriter::getEvaluatedMesh(Scene * /*scene_eval*/, Object *ob_eval, bool &r_needsfree) +{ + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); + if (mesh_eval != NULL) { + /* Mesh_eval only exists when generative modifiers are in use. */ + r_needsfree = false; + return mesh_eval; + } + r_needsfree = true; + + /* The approach below is copied from BKE_mesh_new_from_object() */ + Mesh *tmpmesh = BKE_mesh_add(m_bmain, ((ID *)m_object->data)->name + 2); + BLI_assert(tmpmesh != NULL); + + /* BKE_mesh_add gives us a user count we don't need */ + id_us_min(&tmpmesh->id); + + ListBase disp = {NULL, NULL}; + /* TODO(sergey): This is gonna to work for until Depsgraph + * only contains for_render flag. As soon as CoW is + * implemented, this is to be rethought. + */ + BKE_displist_make_mball_forRender(m_settings.depsgraph, m_settings.scene, m_object, &disp); + BKE_mesh_from_metaball(&disp, tmpmesh); + BKE_displist_free(&disp); + + BKE_mesh_texspace_copy_from_object(tmpmesh, m_object); + + return tmpmesh; +} + +void AbcMBallWriter::freeEvaluatedMesh(struct Mesh *mesh) +{ + BKE_id_free(m_bmain, mesh); +} + +bool AbcMBallWriter::isBasisBall(Scene *scene, Object *ob) +{ + Object *basis_ob = BKE_mball_basis_find(scene, ob); + return ob == basis_ob; +} diff --git a/source/blender/io/alembic/intern/abc_writer_mball.h b/source/blender/io/alembic/intern/abc_writer_mball.h new file mode 100644 index 00000000000..c752472c86d --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mball.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_MBALL_H__ +#define __ABC_WRITER_MBALL_H__ + +#include "abc_writer_object.h" +#include "abc_writer_mesh.h" + +struct Main; +struct Object; + +/* AbcMBallWriter converts the metaballs to meshes at every frame, + * and defers to AbcGenericMeshWriter to perform the writing + * to the Alembic file. Only the basis balls are exported, as this + * results in the entire shape as one mesh. */ +class AbcMBallWriter : public AbcGenericMeshWriter { + Main *m_bmain; + + public: + explicit AbcMBallWriter(Main *bmain, + Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + ~AbcMBallWriter(); + + static bool isBasisBall(Scene *scene, Object *ob); + + protected: + Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override; + void freeEvaluatedMesh(struct Mesh *mesh) override; + + private: + bool isAnimated() const override; +}; + +#endif /* __ABC_WRITER_MBALL_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_mesh.cc b/source/blender/io/alembic/intern/abc_writer_mesh.cc new file mode 100644 index 00000000000..b55d2473f99 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mesh.cc @@ -0,0 +1,592 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_mesh.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_fluidsim_types.h" + +#include "BKE_animsys.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_modifier.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::Abc::FloatArraySample; +using Alembic::Abc::Int32ArraySample; +using Alembic::Abc::V2fArraySample; +using Alembic::Abc::V3fArraySample; + +using Alembic::AbcGeom::kFacevaryingScope; +using Alembic::AbcGeom::OBoolProperty; +using Alembic::AbcGeom::OCompoundProperty; +using Alembic::AbcGeom::OFaceSet; +using Alembic::AbcGeom::OFaceSetSchema; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OPolyMesh; +using Alembic::AbcGeom::OPolyMeshSchema; +using Alembic::AbcGeom::OSubD; +using Alembic::AbcGeom::OSubDSchema; +using Alembic::AbcGeom::OV2fGeomParam; +using Alembic::AbcGeom::UInt32ArraySample; + +/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */ + +static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points) +{ + points.clear(); + points.resize(mesh->totvert); + + MVert *verts = mesh->mvert; + + for (int i = 0, e = mesh->totvert; i < e; i++) { + copy_yup_from_zup(points[i].getValue(), verts[i].co); + } +} + +static void get_topology(struct Mesh *mesh, + std::vector<int32_t> &poly_verts, + std::vector<int32_t> &loop_counts, + bool &r_has_flat_shaded_poly) +{ + const int num_poly = mesh->totpoly; + const int num_loops = mesh->totloop; + MLoop *mloop = mesh->mloop; + MPoly *mpoly = mesh->mpoly; + r_has_flat_shaded_poly = false; + + poly_verts.clear(); + loop_counts.clear(); + poly_verts.reserve(num_loops); + loop_counts.reserve(num_poly); + + /* NOTE: data needs to be written in the reverse order. */ + for (int i = 0; i < num_poly; i++) { + MPoly &poly = mpoly[i]; + loop_counts.push_back(poly.totloop); + + r_has_flat_shaded_poly |= (poly.flag & ME_SMOOTH) == 0; + + MLoop *loop = mloop + poly.loopstart + (poly.totloop - 1); + + for (int j = 0; j < poly.totloop; j++, loop--) { + poly_verts.push_back(loop->v); + } + } +} + +static void get_creases(struct Mesh *mesh, + std::vector<int32_t> &indices, + std::vector<int32_t> &lengths, + std::vector<float> &sharpnesses) +{ + const float factor = 1.0f / 255.0f; + + indices.clear(); + lengths.clear(); + sharpnesses.clear(); + + MEdge *edge = mesh->medge; + + for (int i = 0, e = mesh->totedge; i < e; i++) { + const float sharpness = static_cast<float>(edge[i].crease) * factor; + + if (sharpness != 0.0f) { + indices.push_back(edge[i].v1); + indices.push_back(edge[i].v2); + sharpnesses.push_back(sharpness); + } + } + + lengths.resize(sharpnesses.size(), 2); +} + +static void get_loop_normals(struct Mesh *mesh, + std::vector<Imath::V3f> &normals, + bool has_flat_shaded_poly) +{ + normals.clear(); + + /* If all polygons are smooth shaded, and there are no custom normals, we don't need to export + * normals at all. This is also done by other software, see T71246. */ + if (!has_flat_shaded_poly && !CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL)) { + return; + } + + BKE_mesh_calc_normals_split(mesh); + const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL)); + BLI_assert(lnors != NULL || !"BKE_mesh_calc_normals_split() should have computed CD_NORMAL"); + + normals.resize(mesh->totloop); + + /* NOTE: data needs to be written in the reverse order. */ + int abc_index = 0; + MPoly *mp = mesh->mpoly; + for (int i = 0, e = mesh->totpoly; i < e; i++, mp++) { + for (int j = mp->totloop - 1; j >= 0; j--, abc_index++) { + int blender_index = mp->loopstart + j; + copy_yup_from_zup(normals[abc_index].getValue(), lnors[blender_index]); + } + } +} + +/* *************** Modifiers *************** */ + +/* check if the mesh is a subsurf, ignoring disabled modifiers and + * displace if it's after subsurf. */ +static ModifierData *get_subsurf_modifier(Scene *scene, Object *ob) +{ + ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last); + + for (; md; md = md->prev) { + if (!modifier_isEnabled(scene, md, eModifierMode_Render)) { + continue; + } + + if (md->type == eModifierType_Subsurf) { + SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md); + + if (smd->subdivType == ME_CC_SUBSURF) { + return md; + } + } + + /* mesh is not a subsurf. break */ + if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) { + return NULL; + } + } + + return NULL; +} + +static ModifierData *get_liquid_sim_modifier(Scene *scene, Object *ob) +{ + ModifierData *md = modifiers_findByType(ob, eModifierType_Fluidsim); + + if (md && (modifier_isEnabled(scene, md, eModifierMode_Render))) { + FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); + + if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) { + return md; + } + } + + return NULL; +} + +/* ************************************************************************** */ + +AbcGenericMeshWriter::AbcGenericMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + m_is_animated = isAnimated(); + m_subsurf_mod = NULL; + m_is_subd = false; + + /* If the object is static, use the default static time sampling. */ + if (!m_is_animated) { + time_sampling = 0; + } + + if (!m_settings.apply_subdiv) { + m_subsurf_mod = get_subsurf_modifier(m_settings.scene, m_object); + m_is_subd = (m_subsurf_mod != NULL); + } + + m_is_liquid = (get_liquid_sim_modifier(m_settings.scene, m_object) != NULL); + + while (parent->alembicXform().getChildHeader(m_name)) { + m_name.append("_"); + } + + if (m_settings.use_subdiv_schema && m_is_subd) { + OSubD subd(parent->alembicXform(), m_name, m_time_sampling); + m_subdiv_schema = subd.getSchema(); + } + else { + OPolyMesh mesh(parent->alembicXform(), m_name, m_time_sampling); + m_mesh_schema = mesh.getSchema(); + + OCompoundProperty typeContainer = m_mesh_schema.getUserProperties(); + OBoolProperty type(typeContainer, "meshtype"); + type.set(m_is_subd); + } +} + +AbcGenericMeshWriter::~AbcGenericMeshWriter() +{ + if (m_subsurf_mod) { + m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary; + } +} + +bool AbcGenericMeshWriter::isAnimated() const +{ + if (BKE_animdata_id_is_animated(static_cast<ID *>(m_object->data))) { + return true; + } + if (BKE_key_from_object(m_object) != NULL) { + return true; + } + + /* Test modifiers. */ + ModifierData *md = static_cast<ModifierData *>(m_object->modifiers.first); + while (md) { + + if (md->type != eModifierType_Subsurf) { + return true; + } + + md = md->next; + } + + return false; +} + +void AbcGenericMeshWriter::setIsAnimated(bool is_animated) +{ + m_is_animated = is_animated; +} + +void AbcGenericMeshWriter::do_write() +{ + /* We have already stored a sample for this object. */ + if (!m_first_frame && !m_is_animated) { + return; + } + + bool needsfree; + struct Mesh *mesh = getFinalMesh(needsfree); + + try { + if (m_settings.use_subdiv_schema && m_subdiv_schema.valid()) { + writeSubD(mesh); + } + else { + writeMesh(mesh); + } + + if (needsfree) { + freeEvaluatedMesh(mesh); + } + } + catch (...) { + if (needsfree) { + freeEvaluatedMesh(mesh); + } + throw; + } +} + +void AbcGenericMeshWriter::freeEvaluatedMesh(struct Mesh *mesh) +{ + BKE_id_free(NULL, mesh); +} + +void AbcGenericMeshWriter::writeMesh(struct Mesh *mesh) +{ + std::vector<Imath::V3f> points, normals; + std::vector<int32_t> poly_verts, loop_counts; + std::vector<Imath::V3f> velocities; + bool has_flat_shaded_poly = false; + + get_vertices(mesh, points); + get_topology(mesh, poly_verts, loop_counts, has_flat_shaded_poly); + + if (m_first_frame && m_settings.export_face_sets) { + writeFaceSets(mesh, m_mesh_schema); + } + + m_mesh_sample = OPolyMeshSchema::Sample( + V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); + + UVSample sample; + if (m_first_frame && m_settings.export_uvs) { + const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); + + if (!sample.indices.empty() && !sample.uvs.empty()) { + OV2fGeomParam::Sample uv_sample; + uv_sample.setVals(V2fArraySample(sample.uvs)); + uv_sample.setIndices(UInt32ArraySample(sample.indices)); + uv_sample.setScope(kFacevaryingScope); + + m_mesh_schema.setUVSourceName(name); + m_mesh_sample.setUVs(uv_sample); + } + + write_custom_data( + m_mesh_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + } + + if (m_settings.export_normals) { + get_loop_normals(mesh, normals, has_flat_shaded_poly); + + ON3fGeomParam::Sample normals_sample; + if (!normals.empty()) { + normals_sample.setScope(kFacevaryingScope); + normals_sample.setVals(V3fArraySample(normals)); + } + + m_mesh_sample.setNormals(normals_sample); + } + + if (m_is_liquid) { + getVelocities(mesh, velocities); + m_mesh_sample.setVelocities(V3fArraySample(velocities)); + } + + m_mesh_sample.setSelfBounds(bounds()); + + m_mesh_schema.set(m_mesh_sample); + + writeArbGeoParams(mesh); +} + +void AbcGenericMeshWriter::writeSubD(struct Mesh *mesh) +{ + std::vector<float> crease_sharpness; + std::vector<Imath::V3f> points; + std::vector<int32_t> poly_verts, loop_counts; + std::vector<int32_t> crease_indices, crease_lengths; + bool has_flat_poly = false; + + get_vertices(mesh, points); + get_topology(mesh, poly_verts, loop_counts, has_flat_poly); + get_creases(mesh, crease_indices, crease_lengths, crease_sharpness); + + if (m_first_frame && m_settings.export_face_sets) { + writeFaceSets(mesh, m_subdiv_schema); + } + + m_subdiv_sample = OSubDSchema::Sample( + V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); + + UVSample sample; + if (m_first_frame && m_settings.export_uvs) { + const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); + + if (!sample.indices.empty() && !sample.uvs.empty()) { + OV2fGeomParam::Sample uv_sample; + uv_sample.setVals(V2fArraySample(sample.uvs)); + uv_sample.setIndices(UInt32ArraySample(sample.indices)); + uv_sample.setScope(kFacevaryingScope); + + m_subdiv_schema.setUVSourceName(name); + m_subdiv_sample.setUVs(uv_sample); + } + + write_custom_data( + m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + } + + if (!crease_indices.empty()) { + m_subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices)); + m_subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths)); + m_subdiv_sample.setCreaseSharpnesses(FloatArraySample(crease_sharpness)); + } + + m_subdiv_sample.setSelfBounds(bounds()); + m_subdiv_schema.set(m_subdiv_sample); + + writeArbGeoParams(mesh); +} + +template<typename Schema> void AbcGenericMeshWriter::writeFaceSets(struct Mesh *me, Schema &schema) +{ + std::map<std::string, std::vector<int32_t>> geo_groups; + getGeoGroups(me, geo_groups); + + std::map<std::string, std::vector<int32_t>>::iterator it; + for (it = geo_groups.begin(); it != geo_groups.end(); ++it) { + OFaceSet face_set = schema.createFaceSet(it->first); + OFaceSetSchema::Sample samp; + samp.setFaces(Int32ArraySample(it->second)); + face_set.getSchema().set(samp); + } +} + +Mesh *AbcGenericMeshWriter::getFinalMesh(bool &r_needsfree) +{ + /* We don't want subdivided mesh data */ + if (m_subsurf_mod) { + m_subsurf_mod->mode |= eModifierMode_DisableTemporary; + } + + r_needsfree = false; + + Scene *scene = DEG_get_evaluated_scene(m_settings.depsgraph); + Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object); + struct Mesh *mesh = getEvaluatedMesh(scene, ob_eval, r_needsfree); + + if (m_subsurf_mod) { + m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary; + } + + if (m_settings.triangulate) { + const bool tag_only = false; + const int quad_method = m_settings.quad_method; + const int ngon_method = m_settings.ngon_method; + + struct BMeshCreateParams bmcp = {false}; + struct BMeshFromMeshParams bmfmp = {true, false, false, 0}; + BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmcp, &bmfmp); + + BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, NULL, NULL, NULL); + + Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); + BM_mesh_free(bm); + + if (r_needsfree) { + BKE_id_free(NULL, mesh); + } + + mesh = result; + r_needsfree = true; + } + + m_custom_data_config.pack_uvs = m_settings.pack_uv; + m_custom_data_config.mpoly = mesh->mpoly; + m_custom_data_config.mloop = mesh->mloop; + m_custom_data_config.totpoly = mesh->totpoly; + m_custom_data_config.totloop = mesh->totloop; + m_custom_data_config.totvert = mesh->totvert; + + return mesh; +} + +void AbcGenericMeshWriter::writeArbGeoParams(struct Mesh *me) +{ + if (m_is_liquid) { + /* We don't need anything more for liquid meshes. */ + return; + } + + if (m_first_frame && m_settings.export_vcols) { + if (m_subdiv_schema.valid()) { + write_custom_data( + m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL); + } + else { + write_custom_data( + m_mesh_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL); + } + } +} + +void AbcGenericMeshWriter::getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) +{ + const int totverts = mesh->totvert; + + vels.clear(); + vels.resize(totverts); + + ModifierData *md = get_liquid_sim_modifier(m_settings.scene, m_object); + FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(md); + FluidsimSettings *fss = fmd->fss; + + if (fss->meshVelocities) { + float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities); + + for (int i = 0; i < totverts; i++) { + copy_yup_from_zup(vels[i].getValue(), mesh_vels); + mesh_vels += 3; + } + } + else { + std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f)); + } +} + +void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh, + std::map<std::string, std::vector<int32_t>> &geo_groups) +{ + const int num_poly = mesh->totpoly; + MPoly *polygons = mesh->mpoly; + + for (int i = 0; i < num_poly; i++) { + MPoly ¤t_poly = polygons[i]; + short mnr = current_poly.mat_nr; + + Material *mat = BKE_object_material_get(m_object, mnr + 1); + + if (!mat) { + continue; + } + + std::string name = get_id_name(&mat->id); + + if (geo_groups.find(name) == geo_groups.end()) { + std::vector<int32_t> faceArray; + geo_groups[name] = faceArray; + } + + geo_groups[name].push_back(i); + } + + if (geo_groups.size() == 0) { + Material *mat = BKE_object_material_get(m_object, 1); + + std::string name = (mat) ? get_id_name(&mat->id) : "default"; + + std::vector<int32_t> faceArray; + + for (int i = 0, e = mesh->totface; i < e; i++) { + faceArray.push_back(i); + } + + geo_groups[name] = faceArray; + } +} + +AbcMeshWriter::AbcMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcGenericMeshWriter(ob, parent, time_sampling, settings) +{ +} + +AbcMeshWriter::~AbcMeshWriter() +{ +} + +Mesh *AbcMeshWriter::getEvaluatedMesh(Scene *scene_eval, + Object *ob_eval, + bool &UNUSED(r_needsfree)) +{ + return mesh_get_eval_final(m_settings.depsgraph, scene_eval, ob_eval, &CD_MASK_MESH); +} diff --git a/source/blender/io/alembic/intern/abc_writer_mesh.h b/source/blender/io/alembic/intern/abc_writer_mesh.h new file mode 100644 index 00000000000..9152a370e4f --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mesh.h @@ -0,0 +1,91 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_MESH_H__ +#define __ABC_WRITER_MESH_H__ + +#include "abc_customdata.h" +#include "abc_writer_object.h" + +struct Mesh; +struct ModifierData; + +/* Writer for Alembic meshes. Does not assume the object is a mesh object. */ +class AbcGenericMeshWriter : public AbcObjectWriter { + protected: + Alembic::AbcGeom::OPolyMeshSchema m_mesh_schema; + Alembic::AbcGeom::OPolyMeshSchema::Sample m_mesh_sample; + + Alembic::AbcGeom::OSubDSchema m_subdiv_schema; + Alembic::AbcGeom::OSubDSchema::Sample m_subdiv_sample; + + Alembic::Abc::OArrayProperty m_mat_indices; + + bool m_is_animated; + ModifierData *m_subsurf_mod; + + CDStreamConfig m_custom_data_config; + + bool m_is_liquid; + bool m_is_subd; + + public: + AbcGenericMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + ~AbcGenericMeshWriter(); + void setIsAnimated(bool is_animated); + + protected: + virtual void do_write(); + virtual bool isAnimated() const; + virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) = 0; + virtual void freeEvaluatedMesh(struct Mesh *mesh); + + Mesh *getFinalMesh(bool &r_needsfree); + + void writeMesh(struct Mesh *mesh); + void writeSubD(struct Mesh *mesh); + + void writeArbGeoParams(struct Mesh *mesh); + void getGeoGroups(struct Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geoGroups); + + /* fluid surfaces support */ + void getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels); + + template<typename Schema> void writeFaceSets(struct Mesh *mesh, Schema &schema); +}; + +class AbcMeshWriter : public AbcGenericMeshWriter { + public: + AbcMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + ~AbcMeshWriter(); + + protected: + virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override; +}; + +#endif /* __ABC_WRITER_MESH_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_nurbs.cc b/source/blender/io/alembic/intern/abc_writer_nurbs.cc new file mode 100644 index 00000000000..9796eaf54c3 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_nurbs.cc @@ -0,0 +1,172 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_nurbs.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" + +#include "BKE_curve.h" +} + +using Alembic::AbcGeom::FloatArraySample; +using Alembic::AbcGeom::OBoolProperty; +using Alembic::AbcGeom::OCompoundProperty; +using Alembic::AbcGeom::ONuPatch; +using Alembic::AbcGeom::ONuPatchSchema; + +AbcNurbsWriter::AbcNurbsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + m_is_animated = isAnimated(); + + /* if the object is static, use the default static time sampling */ + if (!m_is_animated) { + m_time_sampling = 0; + } + + Curve *curve = static_cast<Curve *>(m_object->data); + size_t numNurbs = BLI_listbase_count(&curve->nurb); + + for (size_t i = 0; i < numNurbs; i++) { + std::stringstream str; + str << m_name << '_' << i; + + while (parent->alembicXform().getChildHeader(str.str())) { + str << "_"; + } + + ONuPatch nurbs(parent->alembicXform(), str.str().c_str(), m_time_sampling); + m_nurbs_schema.push_back(nurbs.getSchema()); + } +} + +bool AbcNurbsWriter::isAnimated() const +{ + /* check if object has shape keys */ + Curve *cu = static_cast<Curve *>(m_object->data); + return (cu->key != NULL); +} + +static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_knots) +{ + if (num_knots <= 1) { + return; + } + + /* Add an extra knot at the beginning and end of the array since most apps + * require/expect them. */ + knots.reserve(num_knots + 2); + + knots.push_back(0.0f); + + for (int i = 0; i < num_knots; i++) { + knots.push_back(nu_knots[i]); + } + + knots[0] = 2.0f * knots[1] - knots[2]; + knots.push_back(2.0f * knots[num_knots] - knots[num_knots - 1]); +} + +void AbcNurbsWriter::do_write() +{ + /* we have already stored a sample for this object. */ + if (!m_first_frame && !m_is_animated) { + return; + } + + if (!ELEM(m_object->type, OB_SURF, OB_CURVE)) { + return; + } + + Curve *curve = static_cast<Curve *>(m_object->data); + ListBase *nulb; + + if (m_object->runtime.curve_cache->deformed_nurbs.first != NULL) { + nulb = &m_object->runtime.curve_cache->deformed_nurbs; + } + else { + nulb = BKE_curve_nurbs_get(curve); + } + + size_t count = 0; + for (Nurb *nu = static_cast<Nurb *>(nulb->first); nu; nu = nu->next, count++) { + std::vector<float> knotsU; + get_knots(knotsU, KNOTSU(nu), nu->knotsu); + + std::vector<float> knotsV; + get_knots(knotsV, KNOTSV(nu), nu->knotsv); + + const int size = nu->pntsu * nu->pntsv; + std::vector<Imath::V3f> positions(size); + std::vector<float> weights(size); + + const BPoint *bp = nu->bp; + + for (int i = 0; i < size; i++, bp++) { + copy_yup_from_zup(positions[i].getValue(), bp->vec); + weights[i] = bp->vec[3]; + } + + ONuPatchSchema::Sample sample; + sample.setUOrder(nu->orderu + 1); + sample.setVOrder(nu->orderv + 1); + sample.setPositions(positions); + sample.setPositionWeights(weights); + sample.setUKnot(FloatArraySample(knotsU)); + sample.setVKnot(FloatArraySample(knotsV)); + sample.setNu(nu->pntsu); + sample.setNv(nu->pntsv); + + /* TODO(kevin): to accommodate other software we should duplicate control + * points to indicate that a NURBS is cyclic. */ + OCompoundProperty user_props = m_nurbs_schema[count].getUserProperties(); + + if ((nu->flagu & CU_NURB_ENDPOINT) != 0) { + OBoolProperty prop(user_props, "endpoint_u"); + prop.set(true); + } + + if ((nu->flagv & CU_NURB_ENDPOINT) != 0) { + OBoolProperty prop(user_props, "endpoint_v"); + prop.set(true); + } + + if ((nu->flagu & CU_NURB_CYCLIC) != 0) { + OBoolProperty prop(user_props, "cyclic_u"); + prop.set(true); + } + + if ((nu->flagv & CU_NURB_CYCLIC) != 0) { + OBoolProperty prop(user_props, "cyclic_v"); + prop.set(true); + } + + m_nurbs_schema[count].set(sample); + } +} diff --git a/source/blender/io/alembic/intern/abc_writer_nurbs.h b/source/blender/io/alembic/intern/abc_writer_nurbs.h new file mode 100644 index 00000000000..c6a3c399b66 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_nurbs.h @@ -0,0 +1,42 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_NURBS_H__ +#define __ABC_WRITER_NURBS_H__ + +#include "abc_writer_object.h" + +class AbcNurbsWriter : public AbcObjectWriter { + std::vector<Alembic::AbcGeom::ONuPatchSchema> m_nurbs_schema; + bool m_is_animated; + + public: + AbcNurbsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + private: + virtual void do_write(); + + bool isAnimated() const; +}; + +#endif /* __ABC_WRITER_NURBS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_object.cc b/source/blender/io/alembic/intern/abc_writer_object.cc new file mode 100644 index 00000000000..75dc93bd08e --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_object.cc @@ -0,0 +1,79 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_object.h" + +extern "C" { +#include "DNA_object_types.h" + +#include "BKE_object.h" +} + +AbcObjectWriter::AbcObjectWriter(Object *ob, + uint32_t time_sampling, + ExportSettings &settings, + AbcObjectWriter *parent) + : m_object(ob), m_settings(settings), m_time_sampling(time_sampling), m_first_frame(true) +{ + m_name = get_id_name(m_object) + "Shape"; + + if (parent) { + parent->addChild(this); + } +} + +AbcObjectWriter::~AbcObjectWriter() +{ +} + +void AbcObjectWriter::addChild(AbcObjectWriter *child) +{ + m_children.push_back(child); +} + +Imath::Box3d AbcObjectWriter::bounds() +{ + BoundBox *bb = BKE_object_boundbox_get(this->m_object); + + if (!bb) { + if (this->m_object->type != OB_CAMERA) { + ABC_LOG(m_settings.logger) << "Bounding box is null!\n"; + } + + return Imath::Box3d(); + } + + /* Convert Z-up to Y-up. This also changes which vector goes into which min/max property. */ + this->m_bounds.min.x = bb->vec[0][0]; + this->m_bounds.min.y = bb->vec[0][2]; + this->m_bounds.min.z = -bb->vec[6][1]; + + this->m_bounds.max.x = bb->vec[6][0]; + this->m_bounds.max.y = bb->vec[6][2]; + this->m_bounds.max.z = -bb->vec[0][1]; + + return this->m_bounds; +} + +void AbcObjectWriter::write() +{ + do_write(); + m_first_frame = false; +} diff --git a/source/blender/io/alembic/intern/abc_writer_object.h b/source/blender/io/alembic/intern/abc_writer_object.h new file mode 100644 index 00000000000..c3511566372 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_object.h @@ -0,0 +1,71 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_OBJECT_H__ +#define __ABC_WRITER_OBJECT_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +#include "abc_exporter.h" + +extern "C" { +#include "DNA_ID.h" +} + +class AbcTransformWriter; + +struct Main; +struct Object; + +class AbcObjectWriter { + protected: + Object *m_object; + ExportSettings &m_settings; + + uint32_t m_time_sampling; + + Imath::Box3d m_bounds; + std::vector<AbcObjectWriter *> m_children; + + std::vector<std::pair<std::string, IDProperty *>> m_props; + + bool m_first_frame; + std::string m_name; + + public: + AbcObjectWriter(Object *ob, + uint32_t time_sampling, + ExportSettings &settings, + AbcObjectWriter *parent = NULL); + + virtual ~AbcObjectWriter(); + + void addChild(AbcObjectWriter *child); + + virtual Imath::Box3d bounds(); + + void write(); + + private: + virtual void do_write() = 0; +}; + +#endif /* __ABC_WRITER_OBJECT_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_points.cc b/source/blender/io/alembic/intern/abc_writer_points.cc new file mode 100644 index 00000000000..cc4abe8ec4b --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_points.cc @@ -0,0 +1,123 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_points.h" +#include "abc_writer_mesh.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_object_types.h" +#include "DNA_particle_types.h" + +#include "BKE_lattice.h" +#include "BKE_particle.h" + +#include "BLI_math.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::AbcGeom::kVertexScope; +using Alembic::AbcGeom::OPoints; +using Alembic::AbcGeom::OPointsSchema; + +/* ************************************************************************** */ + +AbcPointsWriter::AbcPointsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + m_psys = psys; + + OPoints points(parent->alembicXform(), psys->name, m_time_sampling); + m_schema = points.getSchema(); +} + +void AbcPointsWriter::do_write() +{ + if (!m_psys) { + return; + } + + std::vector<Imath::V3f> points; + std::vector<Imath::V3f> velocities; + std::vector<float> widths; + std::vector<uint64_t> ids; + + ParticleKey state; + + ParticleSimulationData sim; + sim.depsgraph = m_settings.depsgraph; + sim.scene = m_settings.scene; + sim.ob = m_object; + sim.psys = m_psys; + + m_psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + + uint64_t index = 0; + for (int p = 0; p < m_psys->totpart; p++) { + float pos[3], vel[3]; + + if (m_psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) { + continue; + } + + state.time = DEG_get_ctime(m_settings.depsgraph); + + if (psys_get_particle_state(&sim, p, &state, 0) == 0) { + continue; + } + + /* location */ + mul_v3_m4v3(pos, m_object->imat, state.co); + + /* velocity */ + sub_v3_v3v3(vel, state.co, m_psys->particles[p].prev_state.co); + + /* Convert Z-up to Y-up. */ + points.push_back(Imath::V3f(pos[0], pos[2], -pos[1])); + velocities.push_back(Imath::V3f(vel[0], vel[2], -vel[1])); + widths.push_back(m_psys->particles[p].size); + ids.push_back(index++); + } + + if (m_psys->lattice_deform_data) { + end_latt_deform(m_psys->lattice_deform_data); + m_psys->lattice_deform_data = NULL; + } + + Alembic::Abc::P3fArraySample psample(points); + Alembic::Abc::UInt64ArraySample idsample(ids); + Alembic::Abc::V3fArraySample vsample(velocities); + Alembic::Abc::FloatArraySample wsample_array(widths); + Alembic::AbcGeom::OFloatGeomParam::Sample wsample(wsample_array, kVertexScope); + + m_sample = OPointsSchema::Sample(psample, idsample, vsample, wsample); + m_sample.setSelfBounds(bounds()); + + m_schema.set(m_sample); +} diff --git a/source/blender/io/alembic/intern/abc_writer_points.h b/source/blender/io/alembic/intern/abc_writer_points.h new file mode 100644 index 00000000000..77dd10c4b26 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_points.h @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2016 Kévin Dietrich. + * All rights reserved. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_POINTS_H__ +#define __ABC_WRITER_POINTS_H__ + +#include "abc_writer_object.h" +#include "abc_customdata.h" + +struct ParticleSystem; + +/* ************************************************************************** */ + +class AbcPointsWriter : public AbcObjectWriter { + Alembic::AbcGeom::OPointsSchema m_schema; + Alembic::AbcGeom::OPointsSchema::Sample m_sample; + ParticleSystem *m_psys; + + public: + AbcPointsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys); + + void do_write(); +}; + +#endif /* __ABC_WRITER_POINTS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_transform.cc b/source/blender/io/alembic/intern/abc_writer_transform.cc new file mode 100644 index 00000000000..d7bcc46d96f --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_transform.cc @@ -0,0 +1,121 @@ +/* + * 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_util.h" + +#include <OpenEXR/ImathBoxAlgo.h> + +extern "C" { +#include "DNA_object_types.h" + +#include "BLI_math.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::AbcGeom::OObject; +using Alembic::AbcGeom::OXform; + +AbcTransformWriter::AbcTransformWriter(Object *ob, + const OObject &abc_parent, + AbcTransformWriter *parent, + unsigned int time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent), m_proxy_from(NULL) +{ + m_is_animated = hasAnimation(m_object); + + if (!m_is_animated) { + time_sampling = 0; + } + + m_xform = OXform(abc_parent, get_id_name(m_object), time_sampling); + m_schema = m_xform.getSchema(); + + /* Blender objects can't have a parent without inheriting the transform. */ + m_inherits_xform = parent != NULL; +} + +void AbcTransformWriter::do_write() +{ + Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object); + + if (m_first_frame) { + m_visibility = Alembic::AbcGeom::CreateVisibilityProperty( + m_xform, m_xform.getSchema().getTimeSampling()); + } + + m_visibility.set(!(ob_eval->restrictflag & OB_RESTRICT_VIEWPORT)); + + if (!m_first_frame && !m_is_animated) { + return; + } + + float yup_mat[4][4]; + create_transform_matrix( + ob_eval, yup_mat, m_inherits_xform ? ABC_MATRIX_LOCAL : ABC_MATRIX_WORLD, m_proxy_from); + + /* If the parent is a camera, undo its to-Maya rotation (see below). */ + bool is_root_object = !m_inherits_xform || ob_eval->parent == nullptr; + if (!is_root_object && ob_eval->parent->type == OB_CAMERA) { + float rot_mat[4][4]; + axis_angle_to_mat4_single(rot_mat, 'X', M_PI_2); + mul_m4_m4m4(yup_mat, rot_mat, yup_mat); + } + + /* If the object is a camera, apply an extra rotation to Maya camera orientation. */ + if (ob_eval->type == OB_CAMERA) { + float rot_mat[4][4]; + axis_angle_to_mat4_single(rot_mat, 'X', -M_PI_2); + mul_m4_m4m4(yup_mat, yup_mat, rot_mat); + } + + if (is_root_object) { + /* Only apply scaling to root objects, parenting will propagate it. */ + float scale_mat[4][4]; + scale_m4_fl(scale_mat, m_settings.global_scale); + scale_mat[3][3] = m_settings.global_scale; /* also scale translation */ + mul_m4_m4m4(yup_mat, yup_mat, scale_mat); + yup_mat[3][3] /= m_settings.global_scale; /* normalise the homogeneous component */ + } + + m_matrix = convert_matrix_datatype(yup_mat); + m_sample.setMatrix(m_matrix); + m_sample.setInheritsXforms(m_inherits_xform); + m_schema.set(m_sample); +} + +Imath::Box3d AbcTransformWriter::bounds() +{ + Imath::Box3d bounds; + + for (int i = 0; i < m_children.size(); i++) { + Imath::Box3d box(m_children[i]->bounds()); + bounds.extendBy(box); + } + + return Imath::transform(bounds, m_matrix); +} + +bool AbcTransformWriter::hasAnimation(Object * /*ob*/) const +{ + return true; +} diff --git a/source/blender/io/alembic/intern/abc_writer_transform.h b/source/blender/io/alembic/intern/abc_writer_transform.h new file mode 100644 index 00000000000..4397b220761 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_transform.h @@ -0,0 +1,60 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_TRANSFORM_H__ +#define __ABC_WRITER_TRANSFORM_H__ + +#include "abc_writer_object.h" + +#include <Alembic/AbcGeom/All.h> + +class AbcTransformWriter : public AbcObjectWriter { + Alembic::AbcGeom::OXform m_xform; + Alembic::AbcGeom::OXformSchema m_schema; + Alembic::AbcGeom::XformSample m_sample; + Alembic::AbcGeom::OVisibilityProperty m_visibility; + Alembic::Abc::M44d m_matrix; + + bool m_is_animated; + bool m_inherits_xform; + + public: + Object *m_proxy_from; + + public: + AbcTransformWriter(Object *ob, + const Alembic::AbcGeom::OObject &abc_parent, + AbcTransformWriter *parent, + unsigned int time_sampling, + ExportSettings &settings); + + Alembic::AbcGeom::OXform &alembicXform() + { + return m_xform; + } + virtual Imath::Box3d bounds(); + + private: + virtual void do_write(); + + bool hasAnimation(Object *ob) const; +}; + +#endif /* __ABC_WRITER_TRANSFORM_H__ */ diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc new file mode 100644 index 00000000000..c6f9e284d53 --- /dev/null +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -0,0 +1,1052 @@ +/* + * 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_alembic.h" + +#include <Alembic/AbcMaterial/IMaterial.h> + +#include "abc_reader_archive.h" +#include "abc_reader_camera.h" +#include "abc_reader_curves.h" +#include "abc_reader_mesh.h" +#include "abc_reader_nurbs.h" +#include "abc_reader_points.h" +#include "abc_reader_transform.h" +#include "abc_util.h" +#include "abc_writer_camera.h" +#include "abc_writer_curves.h" +#include "abc_writer_hair.h" +#include "abc_writer_mesh.h" +#include "abc_writer_nurbs.h" +#include "abc_writer_points.h" +#include "abc_writer_transform.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_cachefile_types.h" +#include "DNA_curve_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_cachefile.h" +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_global.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +/* SpaceType struct has a member called 'new' which obviously conflicts with C++ + * so temporarily redefining the new keyword to make it compile. */ +#define new extern_new +#include "BKE_screen.h" +#undef new + +#include "BLI_fileops.h" +#include "BLI_ghash.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "WM_api.h" +#include "WM_types.h" +} + +using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::ObjectHeader; + +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::MetaData; +using Alembic::AbcGeom::P3fArraySamplePtr; + +using Alembic::AbcGeom::ICamera; +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::ICurves; +using Alembic::AbcGeom::ICurvesSchema; +using Alembic::AbcGeom::IFaceSet; +using Alembic::AbcGeom::ILight; +using Alembic::AbcGeom::IN3fArrayProperty; +using Alembic::AbcGeom::IN3fGeomParam; +using Alembic::AbcGeom::INuPatch; +using Alembic::AbcGeom::IObject; +using Alembic::AbcGeom::IPoints; +using Alembic::AbcGeom::IPointsSchema; +using Alembic::AbcGeom::IPolyMesh; +using Alembic::AbcGeom::IPolyMeshSchema; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::ISubD; +using Alembic::AbcGeom::IV2fGeomParam; +using Alembic::AbcGeom::IXform; +using Alembic::AbcGeom::IXformSchema; +using Alembic::AbcGeom::N3fArraySamplePtr; +using Alembic::AbcGeom::V3fArraySamplePtr; +using Alembic::AbcGeom::XformSample; + +using Alembic::AbcMaterial::IMaterial; + +struct AbcArchiveHandle { + int unused; +}; + +ABC_INLINE ArchiveReader *archive_from_handle(AbcArchiveHandle *handle) +{ + return reinterpret_cast<ArchiveReader *>(handle); +} + +ABC_INLINE AbcArchiveHandle *handle_from_archive(ArchiveReader *archive) +{ + return reinterpret_cast<AbcArchiveHandle *>(archive); +} + +//#define USE_NURBS + +/* NOTE: this function is similar to visit_objects below, need to keep them in + * sync. */ +static bool gather_objects_paths(const IObject &object, ListBase *object_paths) +{ + if (!object.valid()) { + return false; + } + + size_t children_claiming_this_object = 0; + size_t num_children = object.getNumChildren(); + + for (size_t i = 0; i < num_children; i++) { + bool child_claims_this_object = gather_objects_paths(object.getChild(i), object_paths); + children_claiming_this_object += child_claims_this_object ? 1 : 0; + } + + const MetaData &md = object.getMetaData(); + bool get_path = false; + bool parent_is_part_of_this_object = false; + + if (!object.getParent()) { + /* The root itself is not an object we should import. */ + } + else if (IXform::matches(md)) { + if (has_property(object.getProperties(), "locator")) { + get_path = true; + } + else { + get_path = children_claiming_this_object == 0; + } + + /* Transforms are never "data" for their parent. */ + parent_is_part_of_this_object = false; + } + else { + /* These types are "data" for their parent. */ + get_path = IPolyMesh::matches(md) || ISubD::matches(md) || +#ifdef USE_NURBS + INuPatch::matches(md) || +#endif + ICamera::matches(md) || IPoints::matches(md) || ICurves::matches(md); + parent_is_part_of_this_object = get_path; + } + + if (get_path) { + void *abc_path_void = MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath"); + AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>(abc_path_void); + + BLI_strncpy(abc_path->path, object.getFullName().c_str(), sizeof(abc_path->path)); + BLI_addtail(object_paths, abc_path); + } + + return parent_is_part_of_this_object; +} + +AbcArchiveHandle *ABC_create_handle(struct Main *bmain, + const char *filename, + ListBase *object_paths) +{ + ArchiveReader *archive = new ArchiveReader(bmain, filename); + + if (!archive->valid()) { + delete archive; + return NULL; + } + + if (object_paths) { + gather_objects_paths(archive->getTop(), object_paths); + } + + return handle_from_archive(archive); +} + +void ABC_free_handle(AbcArchiveHandle *handle) +{ + delete archive_from_handle(handle); +} + +int ABC_get_version() +{ + return ALEMBIC_LIBRARY_VERSION; +} + +static void find_iobject(const IObject &object, IObject &ret, const std::string &path) +{ + if (!object.valid()) { + return; + } + + std::vector<std::string> tokens; + split(path, '/', tokens); + + IObject tmp = object; + + std::vector<std::string>::iterator iter; + for (iter = tokens.begin(); iter != tokens.end(); ++iter) { + IObject child = tmp.getChild(*iter); + tmp = child; + } + + ret = tmp; +} + +struct ExportJobData { + ViewLayer *view_layer; + Main *bmain; + wmWindowManager *wm; + + char filename[1024]; + ExportSettings settings; + + short *stop; + short *do_update; + float *progress; + + bool was_canceled; + bool export_ok; +}; + +static void export_startjob(void *customdata, short *stop, short *do_update, float *progress) +{ + ExportJobData *data = static_cast<ExportJobData *>(customdata); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + + /* XXX annoying hack: needed to prevent data corruption when changing + * scene frame in separate threads + */ + G.is_rendering = true; + WM_set_locked_interface(data->wm, true); + G.is_break = false; + + DEG_graph_build_from_view_layer( + data->settings.depsgraph, data->bmain, data->settings.scene, data->view_layer); + BKE_scene_graph_update_tagged(data->settings.depsgraph, data->bmain); + + try { + AbcExporter exporter(data->bmain, data->filename, data->settings); + + Scene *scene = data->settings.scene; /* for the CFRA macro */ + const int orig_frame = CFRA; + + data->was_canceled = false; + exporter(do_update, progress, &data->was_canceled); + + if (CFRA != orig_frame) { + CFRA = orig_frame; + + BKE_scene_graph_update_for_newframe(data->settings.depsgraph, data->bmain); + } + + data->export_ok = !data->was_canceled; + } + catch (const std::exception &e) { + ABC_LOG(data->settings.logger) << "Abc Export error: " << e.what() << '\n'; + } + catch (...) { + ABC_LOG(data->settings.logger) << "Abc Export: unknown error...\n"; + } +} + +static void export_endjob(void *customdata) +{ + ExportJobData *data = static_cast<ExportJobData *>(customdata); + + DEG_graph_free(data->settings.depsgraph); + + if (data->was_canceled && BLI_exists(data->filename)) { + BLI_delete(data->filename, false, false); + } + + std::string log = data->settings.logger.str(); + if (!log.empty()) { + std::cerr << log; + WM_report(RPT_ERROR, "Errors occurred during the export, look in the console to know more..."); + } + + G.is_rendering = false; + WM_set_locked_interface(data->wm, false); +} + +bool ABC_export(Scene *scene, + bContext *C, + const char *filepath, + const struct AlembicExportParams *params, + bool as_background_job) +{ + ExportJobData *job = static_cast<ExportJobData *>( + MEM_mallocN(sizeof(ExportJobData), "ExportJobData")); + + job->view_layer = CTX_data_view_layer(C); + job->bmain = CTX_data_main(C); + job->wm = CTX_wm_manager(C); + job->export_ok = false; + BLI_strncpy(job->filename, filepath, 1024); + + /* Alright, alright, alright.... + * + * ExportJobData contains an ExportSettings containing a SimpleLogger. + * + * Since ExportJobData is a C-style struct dynamically allocated with + * MEM_mallocN (see above), its constructor is never called, therefore the + * ExportSettings constructor is not called which implies that the + * SimpleLogger one is not called either. SimpleLogger in turn does not call + * the constructor of its data members which ultimately means that its + * std::ostringstream member has a NULL pointer. To be able to properly use + * the stream's operator<<, the pointer needs to be set, therefore we have + * to properly construct everything. And this is done using the placement + * new operator as here below. It seems hackish, but I'm too lazy to + * do bigger refactor and maybe there is a better way which does not involve + * hardcore refactoring. */ + new (&job->settings) ExportSettings(); + job->settings.scene = scene; + job->settings.depsgraph = DEG_graph_new(job->bmain, scene, job->view_layer, DAG_EVAL_RENDER); + + /* TODO(Sybren): for now we only export the active scene layer. + * Later in the 2.8 development process this may be replaced by using + * a specific collection for Alembic I/O, which can then be toggled + * between "real" objects and cached Alembic files. */ + job->settings.view_layer = job->view_layer; + + job->settings.frame_start = params->frame_start; + job->settings.frame_end = params->frame_end; + job->settings.frame_samples_xform = params->frame_samples_xform; + job->settings.frame_samples_shape = params->frame_samples_shape; + job->settings.shutter_open = params->shutter_open; + job->settings.shutter_close = params->shutter_close; + + /* TODO(Sybren): For now this is ignored, until we can get selection + * detection working through Base pointers (instead of ob->flags). */ + job->settings.selected_only = params->selected_only; + + job->settings.export_face_sets = params->face_sets; + job->settings.export_normals = params->normals; + job->settings.export_uvs = params->uvs; + job->settings.export_vcols = params->vcolors; + job->settings.export_hair = params->export_hair; + job->settings.export_particles = params->export_particles; + job->settings.apply_subdiv = params->apply_subdiv; + job->settings.curves_as_mesh = params->curves_as_mesh; + job->settings.flatten_hierarchy = params->flatten_hierarchy; + + /* TODO(Sybren): visible_layer & renderable only is ignored for now, + * to be replaced with collections later in the 2.8 dev process + * (also see note above). */ + job->settings.visible_objects_only = params->visible_objects_only; + job->settings.renderable_only = params->renderable_only; + + job->settings.use_subdiv_schema = params->use_subdiv_schema; + job->settings.export_ogawa = (params->compression_type == ABC_ARCHIVE_OGAWA); + job->settings.pack_uv = params->packuv; + job->settings.global_scale = params->global_scale; + job->settings.triangulate = params->triangulate; + job->settings.quad_method = params->quad_method; + job->settings.ngon_method = params->ngon_method; + + if (job->settings.frame_start > job->settings.frame_end) { + std::swap(job->settings.frame_start, job->settings.frame_end); + } + + bool export_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + job->settings.scene, + "Alembic Export", + WM_JOB_PROGRESS, + WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, MEM_freeN); + WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); + WM_jobs_callbacks(wm_job, export_startjob, NULL, NULL, export_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + /* Fake a job context, so that we don't need NULL pointer checks while exporting. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + export_startjob(job, &stop, &do_update, &progress); + export_endjob(job); + export_ok = job->export_ok; + + MEM_freeN(job); + } + + return export_ok; +} + +/* ********************** Import file ********************** */ + +/** + * Generates an AbcObjectReader for this Alembic object and its children. + * + * \param object: The Alembic IObject to visit. + * \param readers: The created AbcObjectReader * will be appended to this vector. + * \param settings: Import settings, not used directly but passed to the + * AbcObjectReader subclass constructors. + * \param r_assign_as_parent: Return parameter, contains a list of reader + * pointers, whose parent pointer should still be set. + * This is filled when this call to visit_object() didn't create + * a reader that should be the parent. + * \return A pair of boolean and reader pointer. The boolean indicates whether + * this IObject claims its parent as part of the same object + * (for example an IPolyMesh object would claim its parent, as the mesh + * is interpreted as the object's data, and the parent IXform as its + * Blender object). The pointer is the AbcObjectReader that represents + * the IObject parameter. + * + * NOTE: this function is similar to gather_object_paths above, need to keep + * them in sync. */ +static std::pair<bool, AbcObjectReader *> visit_object( + const IObject &object, + AbcObjectReader::ptr_vector &readers, + ImportSettings &settings, + AbcObjectReader::ptr_vector &r_assign_as_parent) +{ + const std::string &full_name = object.getFullName(); + + if (!object.valid()) { + std::cerr << " - " << full_name << ": object is invalid, skipping it and all its children.\n"; + return std::make_pair(false, static_cast<AbcObjectReader *>(NULL)); + } + + /* The interpretation of data by the children determine the role of this + * object. This is especially important for Xform objects, as they can be + * either part of a Blender object or a Blender object (Empty) themselves. + */ + size_t children_claiming_this_object = 0; + size_t num_children = object.getNumChildren(); + AbcObjectReader::ptr_vector claiming_child_readers; + AbcObjectReader::ptr_vector nonclaiming_child_readers; + AbcObjectReader::ptr_vector assign_as_parent; + for (size_t i = 0; i < num_children; i++) { + const IObject ichild = object.getChild(i); + + /* TODO: When we only support C++11, use std::tie() instead. */ + std::pair<bool, AbcObjectReader *> child_result; + child_result = visit_object(ichild, readers, settings, assign_as_parent); + + bool child_claims_this_object = child_result.first; + AbcObjectReader *child_reader = child_result.second; + + if (child_reader == NULL) { + BLI_assert(!child_claims_this_object); + } + else { + if (child_claims_this_object) { + claiming_child_readers.push_back(child_reader); + } + else { + nonclaiming_child_readers.push_back(child_reader); + } + } + + children_claiming_this_object += child_claims_this_object ? 1 : 0; + } + BLI_assert(children_claiming_this_object == claiming_child_readers.size()); + + AbcObjectReader *reader = NULL; + const MetaData &md = object.getMetaData(); + bool parent_is_part_of_this_object = false; + + if (!object.getParent()) { + /* The root itself is not an object we should import. */ + } + else if (IXform::matches(md)) { + bool create_empty; + + /* An xform can either be a Blender Object (if it contains a mesh, for + * example), but it can also be an Empty. Its correct translation to + * Blender's data model depends on its children. */ + + /* Check whether or not this object is a Maya locator, which is + * similar to empties used as parent object in Blender. */ + if (has_property(object.getProperties(), "locator")) { + create_empty = true; + } + else { + create_empty = claiming_child_readers.empty(); + } + + if (create_empty) { + reader = new AbcEmptyReader(object, settings); + } + } + else if (IPolyMesh::matches(md)) { + reader = new AbcMeshReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (ISubD::matches(md)) { + reader = new AbcSubDReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (INuPatch::matches(md)) { +#ifdef USE_NURBS + /* TODO(kevin): importing cyclic NURBS from other software crashes + * at the moment. This is due to the fact that NURBS in other + * software have duplicated points which causes buffer overflows in + * Blender. Need to figure out exactly how these points are + * duplicated, in all cases (cyclic U, cyclic V, and cyclic UV). + * Until this is fixed, disabling NURBS reading. */ + reader = new AbcNurbsReader(object, settings); + parent_is_part_of_this_object = true; +#endif + } + else if (ICamera::matches(md)) { + reader = new AbcCameraReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (IPoints::matches(md)) { + reader = new AbcPointsReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (IMaterial::matches(md)) { + /* Pass for now. */ + } + else if (ILight::matches(md)) { + /* Pass for now. */ + } + else if (IFaceSet::matches(md)) { + /* Pass, those are handled in the mesh reader. */ + } + else if (ICurves::matches(md)) { + reader = new AbcCurveReader(object, settings); + parent_is_part_of_this_object = true; + } + else { + std::cerr << "Alembic object " << full_name << " is of unsupported schema type '" + << object.getMetaData().get("schemaObjTitle") << "'" << std::endl; + } + + if (reader) { + /* We have created a reader, which should imply that this object is + * not claimed as part of any child Alembic object. */ + BLI_assert(claiming_child_readers.empty()); + + readers.push_back(reader); + reader->incref(); + + AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>( + MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); + BLI_strncpy(abc_path->path, full_name.c_str(), sizeof(abc_path->path)); + BLI_addtail(&settings.cache_file->object_paths, abc_path); + + /* We can now assign this reader as parent for our children. */ + if (nonclaiming_child_readers.size() + assign_as_parent.size() > 0) { + for (AbcObjectReader *child_reader : nonclaiming_child_readers) { + child_reader->parent_reader = reader; + } + for (AbcObjectReader *child_reader : assign_as_parent) { + child_reader->parent_reader = reader; + } + } + } + else if (object.getParent()) { + if (claiming_child_readers.size() > 0) { + /* The first claiming child will serve just fine as parent to + * our non-claiming children. Since all claiming children share + * the same XForm, it doesn't really matter which one we pick. */ + AbcObjectReader *claiming_child = claiming_child_readers[0]; + for (AbcObjectReader *child_reader : nonclaiming_child_readers) { + child_reader->parent_reader = claiming_child; + } + for (AbcObjectReader *child_reader : assign_as_parent) { + child_reader->parent_reader = claiming_child; + } + /* Claiming children should have our parent set as their parent. */ + for (AbcObjectReader *child_reader : claiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + } + else { + /* This object isn't claimed by any child, and didn't produce + * a reader. Odd situation, could be the top Alembic object, or + * an unsupported Alembic schema. Delegate to our parent. */ + for (AbcObjectReader *child_reader : claiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + for (AbcObjectReader *child_reader : nonclaiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + for (AbcObjectReader *child_reader : assign_as_parent) { + r_assign_as_parent.push_back(child_reader); + } + } + } + + return std::make_pair(parent_is_part_of_this_object, reader); +} + +enum { + ABC_NO_ERROR = 0, + ABC_ARCHIVE_FAIL, + ABC_UNSUPPORTED_HDF5, +}; + +struct ImportJobData { + Main *bmain; + Scene *scene; + ViewLayer *view_layer; + wmWindowManager *wm; + + char filename[1024]; + ImportSettings settings; + + ArchiveReader *archive; + std::vector<AbcObjectReader *> readers; + + short *stop; + short *do_update; + float *progress; + + char error_code; + bool was_cancelled; + bool import_ok; +}; + +static void import_startjob(void *user_data, short *stop, short *do_update, float *progress) +{ + SCOPE_TIMER("Alembic import, objects reading and creation"); + + ImportJobData *data = static_cast<ImportJobData *>(user_data); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + + WM_set_locked_interface(data->wm, true); + + ArchiveReader *archive = new ArchiveReader(data->bmain, data->filename); + + if (!archive->valid()) { +#ifndef WITH_ALEMBIC_HDF5 + data->error_code = archive->is_hdf5() ? ABC_UNSUPPORTED_HDF5 : ABC_ARCHIVE_FAIL; +#else + data->error_code = ABC_ARCHIVE_FAIL; +#endif + delete archive; + return; + } + + CacheFile *cache_file = static_cast<CacheFile *>( + BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename))); + + /* Decrement the ID ref-count because it is going to be incremented for each + * modifier and constraint that it will be attached to, so since currently + * it is not used by anyone, its use count will off by one. */ + id_us_min(&cache_file->id); + + cache_file->is_sequence = data->settings.is_sequence; + cache_file->scale = data->settings.scale; + STRNCPY(cache_file->filepath, data->filename); + + data->archive = archive; + data->settings.cache_file = cache_file; + + *data->do_update = true; + *data->progress = 0.05f; + + /* Parse Alembic Archive. */ + AbcObjectReader::ptr_vector assign_as_parent; + visit_object(archive->getTop(), data->readers, data->settings, assign_as_parent); + + /* There shouldn't be any orphans. */ + BLI_assert(assign_as_parent.size() == 0); + + if (G.is_break) { + data->was_cancelled = true; + return; + } + + *data->do_update = true; + *data->progress = 0.1f; + + /* Create objects and set scene frame range. */ + + const float size = static_cast<float>(data->readers.size()); + size_t i = 0; + + chrono_t min_time = std::numeric_limits<chrono_t>::max(); + chrono_t max_time = std::numeric_limits<chrono_t>::min(); + + ISampleSelector sample_sel(0.0f); + std::vector<AbcObjectReader *>::iterator iter; + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + AbcObjectReader *reader = *iter; + + if (reader->valid()) { + reader->readObjectData(data->bmain, sample_sel); + + min_time = std::min(min_time, reader->minTime()); + max_time = std::max(max_time, reader->maxTime()); + } + else { + std::cerr << "Object " << reader->name() << " in Alembic file " << data->filename + << " is invalid.\n"; + } + + *data->progress = 0.1f + 0.3f * (++i / size); + *data->do_update = true; + + if (G.is_break) { + data->was_cancelled = true; + return; + } + } + + if (data->settings.set_frame_range) { + Scene *scene = data->scene; + + if (data->settings.is_sequence) { + SFRA = data->settings.sequence_offset; + EFRA = SFRA + (data->settings.sequence_len - 1); + CFRA = SFRA; + } + else if (min_time < max_time) { + SFRA = static_cast<int>(round(min_time * FPS)); + EFRA = static_cast<int>(round(max_time * FPS)); + CFRA = SFRA; + } + } + + /* Setup parenthood. */ + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + const AbcObjectReader *reader = *iter; + const AbcObjectReader *parent_reader = reader->parent_reader; + Object *ob = reader->object(); + + if (parent_reader == NULL || !reader->inherits_xform()) { + ob->parent = NULL; + } + else { + ob->parent = parent_reader->object(); + } + } + + /* Setup transformations and constraints. */ + i = 0; + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + AbcObjectReader *reader = *iter; + reader->setupObjectTransform(0.0f); + + *data->progress = 0.7f + 0.3f * (++i / size); + *data->do_update = true; + + if (G.is_break) { + data->was_cancelled = true; + return; + } + } +} + +static void import_endjob(void *user_data) +{ + SCOPE_TIMER("Alembic import, cleanup"); + + ImportJobData *data = static_cast<ImportJobData *>(user_data); + + std::vector<AbcObjectReader *>::iterator iter; + + /* Delete objects on cancellation. */ + if (data->was_cancelled) { + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + Object *ob = (*iter)->object(); + + /* It's possible that cancellation occurred between the creation of + * the reader and the creation of the Blender object. */ + if (ob == NULL) { + continue; + } + + BKE_id_free_us(data->bmain, ob); + } + } + else { + /* Add object to scene. */ + Base *base; + LayerCollection *lc; + ViewLayer *view_layer = data->view_layer; + + BKE_view_layer_base_deselect_all(view_layer); + + lc = BKE_layer_collection_get_active(view_layer); + + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + Object *ob = (*iter)->object(); + + BKE_collection_object_add(data->bmain, lc->collection, ob); + + base = BKE_view_layer_base_find(view_layer, ob); + /* TODO: is setting active needed? */ + BKE_view_layer_base_select_and_set_active(view_layer, base); + + DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update_ex(data->bmain, + &ob->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION | + ID_RECALC_BASE_FLAGS); + } + + DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); + DEG_relations_tag_update(data->bmain); + } + + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + AbcObjectReader *reader = *iter; + reader->decref(); + + if (reader->refcount() == 0) { + delete reader; + } + } + + WM_set_locked_interface(data->wm, false); + + switch (data->error_code) { + default: + case ABC_NO_ERROR: + data->import_ok = !data->was_cancelled; + break; + case ABC_ARCHIVE_FAIL: + WM_report(RPT_ERROR, "Could not open Alembic archive for reading! See console for detail."); + break; + case ABC_UNSUPPORTED_HDF5: + WM_report(RPT_ERROR, "Alembic archive in obsolete HDF5 format is not supported."); + break; + } + + WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); +} + +static void import_freejob(void *user_data) +{ + ImportJobData *data = static_cast<ImportJobData *>(user_data); + delete data->archive; + delete data; +} + +bool ABC_import(bContext *C, + const char *filepath, + float scale, + bool is_sequence, + bool set_frame_range, + int sequence_len, + int offset, + bool validate_meshes, + bool as_background_job) +{ + /* Using new here since MEM_* funcs do not call ctor to properly initialize + * data. */ + ImportJobData *job = new ImportJobData(); + job->bmain = CTX_data_main(C); + job->scene = CTX_data_scene(C); + job->view_layer = CTX_data_view_layer(C); + job->wm = CTX_wm_manager(C); + job->import_ok = false; + BLI_strncpy(job->filename, filepath, 1024); + + job->settings.scale = scale; + job->settings.is_sequence = is_sequence; + job->settings.set_frame_range = set_frame_range; + job->settings.sequence_len = sequence_len; + job->settings.sequence_offset = offset; + job->settings.validate_meshes = validate_meshes; + job->error_code = ABC_NO_ERROR; + job->was_cancelled = false; + job->archive = NULL; + + G.is_break = false; + + bool import_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + job->scene, + "Alembic Import", + WM_JOB_PROGRESS, + WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, import_freejob); + WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); + WM_jobs_callbacks(wm_job, import_startjob, NULL, NULL, import_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 importing. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + import_startjob(job, &stop, &do_update, &progress); + import_endjob(job); + import_ok = job->import_ok; + + import_freejob(job); + } + + return import_ok; +} + +/* ************************************************************************** */ + +void ABC_get_transform(CacheReader *reader, float r_mat[4][4], float time, float scale) +{ + if (!reader) { + return; + } + + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + + bool is_constant = false; + abc_reader->read_matrix(r_mat, time, scale, is_constant); +} + +/* ************************************************************************** */ + +static AbcObjectReader *get_abc_reader(CacheReader *reader, Object *ob, const char **err_str) +{ + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + IObject iobject = abc_reader->iobject(); + + if (!iobject.valid()) { + *err_str = "Invalid object: verify object path"; + return NULL; + } + + const ObjectHeader &header = iobject.getHeader(); + if (!abc_reader->accepts_object_type(header, ob, err_str)) { + /* err_str is set by acceptsObjectType() */ + return NULL; + } + + return abc_reader; +} + +static ISampleSelector sample_selector_for_time(float time) +{ + /* kFloorIndex is used to be compatible with non-interpolating + * properties; they use the floor. */ + return ISampleSelector(time, ISampleSelector::kFloorIndex); +} + +Mesh *ABC_read_mesh(CacheReader *reader, + Object *ob, + Mesh *existing_mesh, + const float time, + const char **err_str, + int read_flag) +{ + AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str); + if (abc_reader == NULL) { + return NULL; + } + + ISampleSelector sample_sel = sample_selector_for_time(time); + return abc_reader->read_mesh(existing_mesh, sample_sel, read_flag, err_str); +} + +bool ABC_mesh_topology_changed( + CacheReader *reader, Object *ob, Mesh *existing_mesh, const float time, const char **err_str) +{ + AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str); + if (abc_reader == NULL) { + return false; + } + + ISampleSelector sample_sel = sample_selector_for_time(time); + return abc_reader->topology_changed(existing_mesh, sample_sel); +} + +/* ************************************************************************** */ + +void CacheReader_free(CacheReader *reader) +{ + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + abc_reader->decref(); + + if (abc_reader->refcount() == 0) { + delete abc_reader; + } +} + +void CacheReader_incref(CacheReader *reader) +{ + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + abc_reader->incref(); +} + +CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle, + CacheReader *reader, + Object *object, + const char *object_path) +{ + if (object_path[0] == '\0') { + return reader; + } + + ArchiveReader *archive = archive_from_handle(handle); + + if (!archive || !archive->valid()) { + return reader; + } + + IObject iobject; + find_iobject(archive->getTop(), iobject, object_path); + + if (reader) { + CacheReader_free(reader); + } + + ImportSettings settings; + AbcObjectReader *abc_reader = create_reader(iobject, settings); + if (abc_reader == NULL) { + /* This object is not supported */ + return NULL; + } + abc_reader->object(object); + abc_reader->incref(); + + return reinterpret_cast<CacheReader *>(abc_reader); +} |