From f9567f6c63e75feaf701fa7b78669b9a436f13dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 18 May 2021 19:01:57 +0200 Subject: Alembic: read/write generated coordinates of meshes Read and write generated coordinates (also known as "original coordinates", "reference coordinates", or "orcos") from and to Alembic. A custom geometry property named "Pref" is used for (hopefully) interoperability with Maya and Houdini. For now it's only guaranteed for Blender-to-Blender. Export: writing generated coordinates is optional (on by default). Import: generated coordinates are always read whenever the reading of vertex data is enabled. Manifest Task: T88081 --- source/blender/editors/io/io_alembic.c | 8 +++ source/blender/io/alembic/ABC_alembic.h | 1 + .../blender/io/alembic/exporter/abc_writer_mesh.cc | 9 +++ source/blender/io/alembic/intern/abc_customdata.cc | 72 ++++++++++++++++++++++ source/blender/io/alembic/intern/abc_customdata.h | 18 ++++-- .../blender/io/alembic/intern/abc_reader_mesh.cc | 5 +- 6 files changed, 107 insertions(+), 6 deletions(-) diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c index 592467c2a85..28838d677f0 100644 --- a/source/blender/editors/io/io_alembic.c +++ b/source/blender/editors/io/io_alembic.c @@ -121,6 +121,7 @@ static int wm_alembic_export_exec(bContext *C, wmOperator *op) .uvs = RNA_boolean_get(op->ptr, "uvs"), .normals = RNA_boolean_get(op->ptr, "normals"), .vcolors = RNA_boolean_get(op->ptr, "vcolors"), + .orcos = RNA_boolean_get(op->ptr, "orcos"), .apply_subdiv = RNA_boolean_get(op->ptr, "apply_subdiv"), .curves_as_mesh = RNA_boolean_get(op->ptr, "curves_as_mesh"), .flatten_hierarchy = RNA_boolean_get(op->ptr, "flatten"), @@ -210,6 +211,7 @@ static void ui_alembic_export_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(col, imfptr, "normals", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "vcolors", 0, NULL, ICON_NONE); + uiItemR(col, imfptr, "orcos", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "face_sets", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "curves_as_mesh", 0, NULL, ICON_NONE); @@ -378,6 +380,12 @@ void WM_OT_alembic_export(wmOperatorType *ot) RNA_def_boolean(ot->srna, "vcolors", 0, "Vertex Colors", "Export vertex colors"); + RNA_def_boolean(ot->srna, + "orcos", + true, + "Generated Coordinates", + "Export undeformed mesh vertex coordinates"); + RNA_def_boolean( ot->srna, "face_sets", 0, "Face Sets", "Export per face shading group assignments"); diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 9785f6d68ab..5664a43233a 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -49,6 +49,7 @@ struct AlembicExportParams { bool uvs; bool normals; bool vcolors; + bool orcos; bool apply_subdiv; bool curves_as_mesh; bool flatten_hierarchy; diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index 29b29324ee3..fd7db005dd2 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -200,6 +200,7 @@ void ABCGenericMeshWriter::do_write(HierarchyContext &context) } m_custom_data_config.pack_uvs = args_.export_params->packuv; + m_custom_data_config.mesh = mesh; m_custom_data_config.mpoly = mesh->mpoly; m_custom_data_config.mloop = mesh->mloop; m_custom_data_config.totpoly = mesh->totpoly; @@ -279,6 +280,10 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) mesh_sample.setNormals(normals_sample); } + if (args_.export_params->orcos) { + write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); + } + if (liquid_sim_modifier_ != nullptr) { get_velocities(mesh, velocities); mesh_sample.setVelocities(V3fArraySample(velocities)); @@ -329,6 +334,10 @@ void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *me abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); } + if (args_.export_params->orcos) { + write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); + } + if (!crease_indices.empty()) { subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices)); subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths)); diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc index 66e05504303..b7a43c339ff 100644 --- a/source/blender/io/alembic/intern/abc_customdata.cc +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -22,12 +22,14 @@ */ #include "abc_customdata.h" +#include "abc_axis_conversion.h" #include #include #include #include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "BLI_math_base.h" @@ -50,8 +52,13 @@ using Alembic::Abc::V2fArraySample; using Alembic::AbcGeom::OC4fGeomParam; using Alembic::AbcGeom::OV2fGeomParam; +using Alembic::AbcGeom::OV3fGeomParam; namespace blender::io::alembic { +/* ORCO, Generated Coordinates, and Reference Points ("Pref") are all terms for the same thing. + * Other applications (Maya, Houdini) write these to a property called "Pref". */ +static const std::string propNameOriginalCoordinates("Pref"); + static void get_uvs(const CDStreamConfig &config, std::vector &uvs, std::vector &uvidx, @@ -222,6 +229,32 @@ static void write_mcol(const OCompoundProperty &prop, param.set(sample); } +void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config) +{ + const void *customdata = CustomData_get_layer(&config.mesh->vdata, CD_ORCO); + if (customdata == nullptr) { + /* Data not available, so don't even bother creating an Alembic property for it. */ + return; + } + const float(*orcodata)[3] = static_cast(customdata); + + /* Convert 3D vertices from float[3] z=up to V3f y=up. */ + std::vector coords(config.totvert); + float orco_yup[3]; + for (int vertex_idx = 0; vertex_idx < config.totvert; vertex_idx++) { + copy_yup_from_zup(orco_yup, orcodata[vertex_idx]); + coords[vertex_idx].setValue(orco_yup[0], orco_yup[1], orco_yup[2]); + } + + if (!config.abc_ocro.valid()) { + /* Create the Alembic property and keep a reference so future frames can reuse it. */ + config.abc_ocro = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1); + } + + OV3fGeomParam::Sample sample(coords, kVertexScope); + config.abc_ocro.set(sample); +} + void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, @@ -263,6 +296,7 @@ using Alembic::Abc::PropertyHeader; using Alembic::AbcGeom::IC3fGeomParam; using Alembic::AbcGeom::IC4fGeomParam; using Alembic::AbcGeom::IV2fGeomParam; +using Alembic::AbcGeom::IV3fGeomParam; static void read_uvs(const CDStreamConfig &config, void *data, @@ -448,6 +482,44 @@ static void read_custom_data_uvs(const ICompoundProperty &prop, read_uvs(config, cd_data, sample.getVals(), sample.getIndices()); } +void read_generated_coordinates(const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + if (prop.getPropertyHeader(propNameOriginalCoordinates) == nullptr) { + /* The ORCO property isn't there, so don't bother trying to process it. */ + return; + } + + IV3fGeomParam param(prop, propNameOriginalCoordinates); + if (!param.valid() || param.isIndexed()) { + /* Invalid or indexed coordinates aren't supported. */ + return; + } + if (param.getScope() != kVertexScope) { + /* These are original vertex coordinates, so must be vertex-scoped. */ + return; + } + + IV3fGeomParam::Sample sample = param.getExpandedValue(iss); + Alembic::AbcGeom::V3fArraySamplePtr abc_ocro = sample.getVals(); + const size_t totvert = abc_ocro.get()->size(); + + void *cd_data; + if (CustomData_has_layer(&config.mesh->vdata, CD_ORCO)) { + cd_data = CustomData_get_layer(&config.mesh->vdata, CD_ORCO); + } + else { + cd_data = CustomData_add_layer(&config.mesh->vdata, CD_ORCO, CD_CALLOC, NULL, totvert); + } + + float(*orcodata)[3] = static_cast(cd_data); + for (int vertex_idx = 0; vertex_idx < totvert; ++vertex_idx) { + const Imath::V3f &abc_coords = (*abc_ocro)[vertex_idx]; + copy_zup_from_yup(orcodata[vertex_idx], abc_coords.getValue()); + } +} + void read_custom_data(const std::string &iobject_full_name, const ICompoundProperty &prop, const CDStreamConfig &config, diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index 4eb515f132c..9ee964c0545 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -72,12 +72,16 @@ struct CDStreamConfig { const char **modifier_error_message; - /* Alembic needs Blender to keep references to C++ objects (the destructors - * finalize the writing to ABC). This map stores OV2fGeomParam objects for the - * 2nd and subsequent UV maps; the primary UV map is kept alive by the Alembic - * mesh sample itself. */ + /* Alembic needs Blender to keep references to C++ objects (the destructors finalize the writing + * to ABC). The following fields are all used to keep these references. */ + + /* Mapping from UV map name to its ABC property, for the 2nd and subsequent UV maps; the primary + * UV map is kept alive by the Alembic mesh sample itself. */ std::map abc_uv_maps; + /* OCRO coordinates, aka Generated Coordinates. */ + Alembic::AbcGeom::OV3fGeomParam abc_ocro; + CDStreamConfig() : mloop(NULL), totloop(0), @@ -102,6 +106,12 @@ struct CDStreamConfig { * 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_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config); + +void read_generated_coordinates(const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss); + void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 8133f615080..11b6c1c18ca 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -439,6 +439,7 @@ static void read_mesh_sample(const std::string &iobject_full_name, if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { read_mverts(config, abc_mesh_data); + read_generated_coordinates(schema.getArbGeomParams(), config, selector); } if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { @@ -558,7 +559,7 @@ void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec /* 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); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); mesh->flag |= autosmooth; } @@ -868,7 +869,7 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr); if (read_mesh != mesh) { - BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); } ISubDSchema::Sample sample; -- cgit v1.2.3