From 61050f75b13ef706d3a80b86137436d3fb0bfa93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Sat, 6 Aug 2016 06:20:37 +0200 Subject: Basic Alembic support All in all, this patch adds an Alembic importer, an Alembic exporter, and a new CacheFile data block which, for now, wraps around an Alembic archive. This data block is made available through a new modifier ("Mesh Sequence Cache") as well as a new constraint ("Transform Cache") to somewhat properly support respectively geometric and transformation data streaming from alembic caches. A more in-depth documentation is to be found on the wiki, as well as a guide to compile alembic: https://wiki.blender.org/index.php/ User:Kevindietrich/AlembicBasicIo. Many thanks to everyone involved in this little project, and huge shout out to "cgstrive" for the thorough testings with Maya, 3ds Max, Houdini and Realflow as well as @fjuhec, @jensverwiebe and @jasperge for the custom builds and compile fixes. Reviewers: sergey, campbellbarton, mont29 Reviewed By: sergey, campbellbarton, mont29 Differential Revision: https://developer.blender.org/D2060 --- source/blender/alembic/intern/alembic_capi.cc | 1136 +++++++++++++++++++++++++ 1 file changed, 1136 insertions(+) create mode 100644 source/blender/alembic/intern/alembic_capi.cc (limited to 'source/blender/alembic/intern/alembic_capi.cc') diff --git a/source/blender/alembic/intern/alembic_capi.cc b/source/blender/alembic/intern/alembic_capi.cc new file mode 100644 index 00000000000..0e96ac22e11 --- /dev/null +++ b/source/blender/alembic/intern/alembic_capi.cc @@ -0,0 +1,1136 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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. + * + * Contributor(s): Esteban Tovagliari, Cedric Paille, Kevin Dietrich + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include "../ABC_alembic.h" + +#ifdef WITH_ALEMBIC_HDF5 +# include +#endif + +#include +#include + +#include "abc_camera.h" +#include "abc_curves.h" +#include "abc_hair.h" +#include "abc_mesh.h" +#include "abc_nurbs.h" +#include "abc_points.h" +#include "abc_transform.h" +#include "abc_util.h" + +extern "C" { +#include "MEM_guardedalloc.h" + +#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_cdderivedmesh.h" +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_depsgraph.h" +#include "BKE_global.h" +#include "BKE_library.h" +#include "BKE_main.h" +#include "BKE_scene.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::ErrorHandler; +using Alembic::AbcGeom::Exception; +using Alembic::AbcGeom::MetaData; +using Alembic::AbcGeom::P3fArraySamplePtr; +using Alembic::AbcGeom::kWrapExisting; + +using Alembic::AbcGeom::IArchive; +using Alembic::AbcGeom::ICamera; +using Alembic::AbcGeom::ICurves; +using Alembic::AbcGeom::ICurvesSchema; +using Alembic::AbcGeom::IFaceSet; +using Alembic::AbcGeom::ILight; +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::XformSample; +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::IN3fArrayProperty; +using Alembic::AbcGeom::IN3fGeomParam; +using Alembic::AbcGeom::V3fArraySamplePtr; + +using Alembic::AbcMaterial::IMaterial; + +struct AbcArchiveHandle { + int unused; +}; + +ABC_INLINE IArchive *archive_from_handle(AbcArchiveHandle *handle) +{ + return reinterpret_cast(handle); +} + +ABC_INLINE AbcArchiveHandle *handle_from_archive(IArchive *archive) +{ + return reinterpret_cast(archive); +} + +static IArchive *open_archive(const std::string &filename) +{ + Alembic::AbcCoreAbstract::ReadArraySampleCachePtr cache_ptr; + IArchive *archive; + + try { + archive = new IArchive(Alembic::AbcCoreOgawa::ReadArchive(), + filename.c_str(), ErrorHandler::kThrowPolicy, + cache_ptr); + } + catch (const Exception &e) { + std::cerr << e.what() << '\n'; + +#ifdef WITH_ALEMBIC_HDF5 + try { + archive = new IArchive(Alembic::AbcCoreHDF5::ReadArchive(), + filename.c_str(), ErrorHandler::kThrowPolicy, + cache_ptr); + } + catch (const Exception &) { + std::cerr << e.what() << '\n'; + return NULL; + } +#else + return NULL; +#endif + } + + return archive; +} + +//#define USE_NURBS + +/* NOTE: this function is similar to visit_objects below, need to keep them in + * sync. */ +static void gather_objects_paths(const IObject &object, ListBase *object_paths) +{ + if (!object.valid()) { + return; + } + + for (int i = 0; i < object.getNumChildren(); ++i) { + IObject child = object.getChild(i); + + if (!child.valid()) { + continue; + } + + bool get_path = false; + + const MetaData &md = child.getMetaData(); + + if (IXform::matches(md)) { + /* Check whether or not this object is a Maya locator, which is + * similar to empties used as parent object in Blender. */ + if (has_property(child.getProperties(), "locator")) { + get_path = true; + } + else { + /* Avoid creating an empty object if the child of this transform + * is not a transform (that is an empty). */ + if (child.getNumChildren() == 1) { + if (IXform::matches(child.getChild(0).getMetaData())) { + get_path = true; + } +#if 0 + else { + std::cerr << "Skipping " << child.getFullName() << '\n'; + } +#endif + } + else { + get_path = true; + } + } + } + else if (IPolyMesh::matches(md)) { + get_path = true; + } + else if (ISubD::matches(md)) { + get_path = true; + } + else if (INuPatch::matches(md)) { +#ifdef USE_NURBS + get_path = true; +#endif + } + else if (ICamera::matches(md)) { + get_path = true; + } + else if (IPoints::matches(md)) { + get_path = 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)) { + get_path = true; + } + else { + assert(false); + } + + if (get_path) { + AlembicObjectPath *abc_path = static_cast( + MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); + + BLI_strncpy(abc_path->path, child.getFullName().c_str(), PATH_MAX); + + BLI_addtail(object_paths, abc_path); + } + + gather_objects_paths(child, object_paths); + } +} + +AbcArchiveHandle *ABC_create_handle(const char *filename, ListBase *object_paths) +{ + IArchive *archive = open_archive(filename); + + if (!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 tokens; + split(path, '/', tokens); + + IObject tmp = object; + + std::vector::iterator iter; + for (iter = tokens.begin(); iter != tokens.end(); ++iter) { + IObject child = tmp.getChild(*iter); + tmp = child; + } + + ret = tmp; +} + +struct ExportJobData { + Scene *scene; + Main *bmain; + + char filename[1024]; + ExportSettings settings; + + short *stop; + short *do_update; + float *progress; + + bool was_canceled; +}; + +static void export_startjob(void *customdata, short *stop, short *do_update, float *progress) +{ + ExportJobData *data = static_cast(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; + BKE_spacedata_draw_locks(true); + + G.is_break = false; + + try { + Scene *scene = data->scene; + AbcExporter exporter(scene, data->filename, data->settings); + + const int orig_frame = CFRA; + + data->was_canceled = false; + exporter(data->bmain, *data->progress, data->was_canceled); + + if (CFRA != orig_frame) { + CFRA = orig_frame; + + BKE_scene_update_for_newframe(data->bmain->eval_ctx, data->bmain, + scene, scene->lay); + } + } + catch (const std::exception &e) { + std::cerr << "Abc Export error: " << e.what() << '\n'; + } + catch (...) { + std::cerr << "Abc Export error\n"; + } +} + +static void export_endjob(void *customdata) +{ + ExportJobData *data = static_cast(customdata); + + if (data->was_canceled && BLI_exists(data->filename)) { + BLI_delete(data->filename, false, false); + } + + G.is_rendering = false; + BKE_spacedata_draw_locks(false); +} + +void ABC_export( + Scene *scene, + bContext *C, + const char *filepath, + const struct AlembicExportParams *params) +{ + ExportJobData *job = static_cast(MEM_mallocN(sizeof(ExportJobData), "ExportJobData")); + job->scene = scene; + job->bmain = CTX_data_main(C); + BLI_strncpy(job->filename, filepath, 1024); + + job->settings.scene = job->scene; + job->settings.frame_start = params->frame_start; + job->settings.frame_end = params->frame_end; + job->settings.frame_step_xform = params->frame_step_xform; + job->settings.frame_step_shape = params->frame_step_shape; + job->settings.shutter_open = params->shutter_open; + job->settings.shutter_close = params->shutter_close; + 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.apply_subdiv = params->apply_subdiv; + job->settings.flatten_hierarchy = params->flatten_hierarchy; + job->settings.visible_layers_only = params->visible_layers_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; + + if (job->settings.frame_start > job->settings.frame_end) { + std::swap(job->settings.frame_start, job->settings.frame_end); + } + + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + job->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); +} + +/* ********************** Import file ********************** */ + +static void visit_object(const IObject &object, + std::vector &readers, + GHash *parent_map, + ImportSettings &settings) +{ + if (!object.valid()) { + return; + } + + for (int i = 0; i < object.getNumChildren(); ++i) { + IObject child = object.getChild(i); + + if (!child.valid()) { + continue; + } + + AbcObjectReader *reader = NULL; + + const MetaData &md = child.getMetaData(); + + if (IXform::matches(md)) { + bool create_xform = false; + + /* Check whether or not this object is a Maya locator, which is + * similar to empties used as parent object in Blender. */ + if (has_property(child.getProperties(), "locator")) { + create_xform = true; + } + else { + /* Avoid creating an empty object if the child of this transform + * is not a transform (that is an empty). */ + if (child.getNumChildren() == 1) { + if (IXform::matches(child.getChild(0).getMetaData())) { + create_xform = true; + } +#if 0 + else { + std::cerr << "Skipping " << child.getFullName() << '\n'; + } +#endif + } + else { + create_xform = true; + } + } + + if (create_xform) { + reader = new AbcEmptyReader(child, settings); + } + } + else if (IPolyMesh::matches(md)) { + reader = new AbcMeshReader(child, settings); + } + else if (ISubD::matches(md)) { + reader = new AbcSubDReader(child, settings); + } + 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(child, settings); +#endif + } + else if (ICamera::matches(md)) { + reader = new AbcCameraReader(child, settings); + } + else if (IPoints::matches(md)) { + reader = new AbcPointsReader(child, settings); + } + 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(child, settings); + } + else { + assert(false); + } + + if (reader) { + readers.push_back(reader); + + AlembicObjectPath *abc_path = static_cast( + MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); + + BLI_strncpy(abc_path->path, child.getFullName().c_str(), PATH_MAX); + + BLI_addtail(&settings.cache_file->object_paths, abc_path); + + /* Cast to `void *` explicitly to avoid compiler errors because it + * is a `const char *` which the compiler cast to `const void *` + * instead of the expected `void *`. */ + BLI_ghash_insert(parent_map, (void *)child.getFullName().c_str(), reader); + } + + visit_object(child, readers, parent_map, settings); + } +} + +enum { + ABC_NO_ERROR = 0, + ABC_ARCHIVE_FAIL, +}; + +struct ImportJobData { + Main *bmain; + Scene *scene; + + char filename[1024]; + ImportSettings settings; + + GHash *parent_map; + std::vector readers; + + short *stop; + short *do_update; + float *progress; + + char error_code; + bool was_cancelled; +}; + +ABC_INLINE bool is_mesh_and_strands(const IObject &object) +{ + if (object.getNumChildren() != 2) { + return false; + } + + bool has_mesh = false; + bool has_curve = false; + + for (int i = 0; i < object.getNumChildren(); ++i) { + const IObject &child = object.getChild(i); + + if (!child.valid()) { + continue; + } + + const MetaData &md = child.getMetaData(); + + if (IPolyMesh::matches(md)) { + has_mesh = true; + } + else if (ISubD::matches(md)) { + has_mesh = true; + } + else if (ICurves::matches(md)) { + has_curve = true; + } + } + + return has_mesh && has_curve; +} + +static void import_startjob(void *user_data, short *stop, short *do_update, float *progress) +{ + ImportJobData *data = static_cast(user_data); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + + IArchive *archive = open_archive(data->filename); + + if (!archive || !archive->valid()) { + data->error_code = ABC_ARCHIVE_FAIL; + return; + } + + CacheFile *cache_file = static_cast(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; + cache_file->handle = handle_from_archive(archive); + BLI_strncpy(cache_file->filepath, data->filename, 1024); + + data->settings.cache_file = cache_file; + + *data->do_update = true; + *data->progress = 0.05f; + + data->parent_map = BLI_ghash_str_new("alembic parent ghash"); + + /* Parse Alembic Archive. */ + + visit_object(archive->getTop(), data->readers, data->parent_map, data->settings); + + 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(data->readers.size()); + size_t i = 0; + + chrono_t min_time = std::numeric_limits::max(); + chrono_t max_time = std::numeric_limits::min(); + + std::vector::iterator iter; + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + AbcObjectReader *reader = *iter; + + if (reader->valid()) { + reader->readObjectData(data->bmain, 0.0f); + reader->readObjectMatrix(0.0f); + + min_time = std::min(min_time, reader->minTime()); + max_time = std::max(max_time, reader->maxTime()); + } + + *data->progress = 0.1f + 0.6f * (++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.offset; + EFRA = SFRA + (data->settings.sequence_len - 1); + CFRA = SFRA; + } + else if (min_time < max_time) { + SFRA = min_time * FPS; + EFRA = max_time * FPS; + CFRA = SFRA; + } + } + + /* Setup parentship. */ + + i = 0; + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + const AbcObjectReader *reader = *iter; + const AbcObjectReader *parent_reader = NULL; + const IObject &iobject = reader->iobject(); + + IObject parent = iobject.getParent(); + + if (!IXform::matches(iobject.getHeader())) { + /* In the case of an non XForm node, the parent is the transform + * matrix of the data itself, so we get the its grand parent. + */ + + /* Special case with object only containing a mesh and some strands, + * we want both objects to be parented to the same object. */ + if (!is_mesh_and_strands(parent)) { + parent = parent.getParent(); + } + } + + parent_reader = reinterpret_cast( + BLI_ghash_lookup(data->parent_map, parent.getFullName().c_str())); + + if (parent_reader) { + Object *parent = parent_reader->object(); + + if (parent != NULL && reader->object() != parent) { + Object *ob = reader->object(); + ob->parent = parent; + } + } + + *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) +{ + ImportJobData *data = static_cast(user_data); + + std::vector::iterator iter; + + /* Delete objects on cancelation. */ + if (data->was_cancelled) { + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + Object *ob = (*iter)->object(); + + if (ob->data) { + BKE_libblock_free_us(data->bmain, ob->data); + ob->data = NULL; + } + + BKE_libblock_free_us(data->bmain, ob); + } + } + else { + /* Add object to scene. */ + BKE_scene_base_deselect_all(data->scene); + + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + Object *ob = (*iter)->object(); + ob->lay = data->scene->lay; + + BKE_scene_base_add(data->scene, ob); + + DAG_id_tag_update_ex(data->bmain, &ob->id, OB_RECALC_OB | OB_RECALC_DATA | OB_RECALC_TIME); + } + + DAG_relations_tag_update(data->bmain); + } + + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + delete *iter; + } + + if (data->parent_map) { + BLI_ghash_free(data->parent_map, NULL, NULL); + } + + switch (data->error_code) { + default: + case ABC_NO_ERROR: + break; + case ABC_ARCHIVE_FAIL: + WM_report(RPT_ERROR, "Could not open Alembic archive for reading! See console for detail."); + break; + } + + WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); +} + +static void import_freejob(void *user_data) +{ + ImportJobData *data = static_cast(user_data); + delete data; +} + +void ABC_import(bContext *C, const char *filepath, float scale, bool is_sequence, bool set_frame_range, int sequence_len, int offset, bool validate_meshes) +{ + /* 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); + 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.offset = offset; + job->settings.validate_meshes = validate_meshes; + job->parent_map = NULL; + job->error_code = ABC_NO_ERROR; + job->was_cancelled = false; + + G.is_break = false; + + 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); +} + +/* ******************************* */ + +void ABC_get_transform(AbcArchiveHandle *handle, Object *ob, const char *object_path, float r_mat[4][4], float time, float scale) +{ + IArchive *archive = archive_from_handle(handle); + + if (!archive || !archive->valid()) { + return; + } + + IObject tmp; + find_iobject(archive->getTop(), tmp, object_path); + + IXform ixform; + + if (IXform::matches(tmp.getHeader())) { + ixform = IXform(tmp, kWrapExisting); + } + else { + ixform = IXform(tmp.getParent(), kWrapExisting); + } + + IXformSchema schema = ixform.getSchema(); + + if (!schema.valid()) { + return; + } + + ISampleSelector sample_sel(time); + + create_input_transform(sample_sel, ixform, ob, r_mat, scale); +} + +/* ***************************************** */ + +static bool check_smooth_poly_flag(DerivedMesh *dm) +{ + MPoly *mpolys = dm->getPolyArray(dm); + + for (int i = 0, e = dm->getNumPolys(dm); i < e; ++i) { + MPoly &poly = mpolys[i]; + + if ((poly.flag & ME_SMOOTH) != 0) { + return true; + } + } + + return false; +} + +static void set_smooth_poly_flag(DerivedMesh *dm) +{ + MPoly *mpolys = dm->getPolyArray(dm); + + for (int i = 0, e = dm->getNumPolys(dm); i < e; ++i) { + MPoly &poly = mpolys[i]; + poly.flag |= ME_SMOOTH; + } +} + +static void *add_customdata_cb(void *user_data, const char *name, int data_type) +{ + DerivedMesh *dm = static_cast(user_data); + CustomDataType cd_data_type = static_cast(data_type); + void *cd_ptr = NULL; + + if (ELEM(cd_data_type, CD_MLOOPUV, CD_MLOOPCOL)) { + cd_ptr = CustomData_get_layer_named(dm->getLoopDataLayout(dm), cd_data_type, name); + + if (cd_ptr == NULL) { + cd_ptr = CustomData_add_layer_named(dm->getLoopDataLayout(dm), + cd_data_type, + CD_DEFAULT, + NULL, + dm->getNumLoops(dm), + name); + } + } + + return cd_ptr; +} + +ABC_INLINE CDStreamConfig get_config(DerivedMesh *dm) +{ + CDStreamConfig config; + + config.user_data = dm; + config.mvert = dm->getVertArray(dm); + config.mloop = dm->getLoopArray(dm); + config.mpoly = dm->getPolyArray(dm); + config.totloop = dm->getNumLoops(dm); + config.totpoly = dm->getNumPolys(dm); + config.loopdata = dm->getLoopDataLayout(dm); + config.add_customdata_cb = add_customdata_cb; + + return config; +} + +static DerivedMesh *read_mesh_sample(DerivedMesh *dm, const IObject &iobject, const float time, int read_flag) +{ + IPolyMesh mesh(iobject, kWrapExisting); + IPolyMeshSchema schema = mesh.getSchema(); + ISampleSelector sample_sel(time); + const IPolyMeshSchema::Sample sample = schema.getValue(sample_sel); + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + DerivedMesh *new_dm = NULL; + + /* Only read point data when streaming meshes, unless we need to create new ones. */ + ImportSettings settings; + settings.read_flag |= read_flag; + + if (dm->getNumVerts(dm) != positions->size()) { + new_dm = CDDM_from_template(dm, + positions->size(), + 0, + 0, + face_indices->size(), + face_counts->size()); + + settings.read_flag |= MOD_MESHSEQ_READ_ALL; + } + + CDStreamConfig config = get_config(new_dm ? new_dm : dm); + + bool has_loop_normals = false; + read_mesh_sample(&settings, schema, sample_sel, config, has_loop_normals); + + if (new_dm) { + /* Check if we had ME_SMOOTH flag set to restore it. */ + if (!has_loop_normals && check_smooth_poly_flag(dm)) { + set_smooth_poly_flag(new_dm); + } + + CDDM_calc_normals(new_dm); + CDDM_calc_edges(new_dm); + + return new_dm; + } + + return dm; +} + +using Alembic::AbcGeom::ISubDSchema; + +static DerivedMesh *read_subd_sample(DerivedMesh *dm, const IObject &iobject, const float time, int read_flag) +{ + ISubD mesh(iobject, kWrapExisting); + ISubDSchema schema = mesh.getSchema(); + ISampleSelector sample_sel(time); + const ISubDSchema::Sample sample = schema.getValue(sample_sel); + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + DerivedMesh *new_dm = NULL; + + ImportSettings settings; + settings.read_flag |= read_flag; + + if (dm->getNumVerts(dm) != positions->size()) { + new_dm = CDDM_from_template(dm, + positions->size(), + 0, + 0, + face_indices->size(), + face_counts->size()); + + settings.read_flag |= MOD_MESHSEQ_READ_ALL; + } + + /* Only read point data when streaming meshes, unless we need to create new ones. */ + CDStreamConfig config = get_config(new_dm ? new_dm : dm); + read_subd_sample(&settings, schema, sample_sel, config); + + if (new_dm) { + /* Check if we had ME_SMOOTH flag set to restore it. */ + if (check_smooth_poly_flag(dm)) { + set_smooth_poly_flag(new_dm); + } + + CDDM_calc_normals(new_dm); + CDDM_calc_edges(new_dm); + + return new_dm; + } + + return dm; +} + +static DerivedMesh *read_points_sample(DerivedMesh *dm, const IObject &iobject, const float time) +{ + IPoints points(iobject, kWrapExisting); + IPointsSchema schema = points.getSchema(); + ISampleSelector sample_sel(time); + const IPointsSchema::Sample sample = schema.getValue(sample_sel); + + const P3fArraySamplePtr &positions = sample.getPositions(); + + DerivedMesh *new_dm = NULL; + + if (dm->getNumVerts(dm) != positions->size()) { + new_dm = CDDM_new(positions->size(), 0, 0, 0, 0); + } + + CDStreamConfig config = get_config(new_dm ? new_dm : dm); + read_points_sample(schema, sample_sel, config, time); + + return new_dm ? new_dm : dm; +} + +/* NOTE: Alembic only stores data about control points, but the DerivedMesh + * 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 DerivedMesh from that. Also we might need to + * create new or delete existing NURBS in the curve. + */ +static DerivedMesh *read_curves_sample(Object *ob, const IObject &iobject, const float time) +{ + ICurves points(iobject, kWrapExisting); + ICurvesSchema schema = points.getSchema(); + ISampleSelector sample_sel(time); + const ICurvesSchema::Sample sample = schema.getValue(sample_sel); + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Int32ArraySamplePtr num_vertices = sample.getCurvesNumVertices(); + + int vertex_idx = 0; + int curve_idx = 0; + Curve *curve = static_cast(ob->data); + + const int curve_count = BLI_listbase_count(&curve->nurb); + + if (curve_count != num_vertices->size()) { + BKE_nurbList_free(&curve->nurb); + read_curve_sample(curve, schema, time); + } + else { + Nurb *nurbs = static_cast(curve->nurb.first); + for (; 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_yup_zup(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_yup_zup(bezier->vec[1], pos.getValue()); + } + } + } + } + + return CDDM_from_curve(ob); +} + +DerivedMesh *ABC_read_mesh(AbcArchiveHandle *handle, + Object *ob, + DerivedMesh *dm, + const char *object_path, + const float time, + const char **err_str, + int read_flag) +{ + IArchive *archive = archive_from_handle(handle); + + if (!archive || !archive->valid()) { + *err_str = "Invalid archive!"; + return NULL; + } + + IObject iobject; + find_iobject(archive->getTop(), iobject, object_path); + + if (!iobject.valid()) { + *err_str = "Invalid object: verify object path"; + return NULL; + } + + const ObjectHeader &header = iobject.getHeader(); + + if (IPolyMesh::matches(header)) { + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch: object path points to a mesh!"; + return NULL; + } + + return read_mesh_sample(dm, iobject, time, read_flag); + } + else if (ISubD::matches(header)) { + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch: object path points to a subdivision mesh!"; + return NULL; + } + + return read_subd_sample(dm, iobject, time, read_flag); + } + else if (IPoints::matches(header)) { + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch: object path points to a point cloud (requires a mesh object)!"; + return NULL; + } + + return read_points_sample(dm, iobject, time); + } + else if (ICurves::matches(header)) { + if (ob->type != OB_CURVE) { + *err_str = "Object type mismatch: object path points to a curve!"; + return NULL; + } + + return read_curves_sample(ob, iobject, time); + } + + *err_str = "Unsupported object type: verify object path"; // or poke developer + return NULL; +} -- cgit v1.2.3