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
This commit is contained in:
Sybren A. Stüvel 2021-05-18 19:01:57 +02:00
parent a881b5272b
commit f9567f6c63
Notes: blender-bot 2023-02-14 10:29:30 +01:00
Referenced by issue #93906, Alembic not importing UVs (if file incorrectly sets geoScope to kVertexScope - even though actual data implies kFacevaryingScope - which 2.93 handled gracefully)
6 changed files with 107 additions and 6 deletions

View File

@ -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");

View File

@ -49,6 +49,7 @@ struct AlembicExportParams {
bool uvs;
bool normals;
bool vcolors;
bool orcos;
bool apply_subdiv;
bool curves_as_mesh;
bool flatten_hierarchy;

View File

@ -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));

View File

@ -22,12 +22,14 @@
*/
#include "abc_customdata.h"
#include "abc_axis_conversion.h"
#include <Alembic/AbcGeom/All.h>
#include <algorithm>
#include <unordered_map>
#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<Imath::V2f> &uvs,
std::vector<uint32_t> &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<const float(*)[3]>(customdata);
/* Convert 3D vertices from float[3] z=up to V3f y=up. */
std::vector<Imath::V3f> 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<float(*)[3]>(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,

View File

@ -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<std::string, Alembic::AbcGeom::OV2fGeomParam> 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,

View File

@ -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;