Alembic/USD: use geometry sets to import data #115623

Merged
Jesse Yurkovich merged 14 commits from kevindietrich/blender:abc_usd_geometry_sets_review into main 2024-02-28 03:02:48 +01:00
23 changed files with 907 additions and 569 deletions

View File

@ -1276,7 +1276,7 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type)
}
if (ELEM(ob->type, OB_POINTCLOUD, OB_CURVES)) {
return modifier_type == eModifierType_Nodes;
return ELEM(modifier_type, eModifierType_Nodes, eModifierType_MeshSequenceCache);
}
if (ob->type == OB_VOLUME) {
return mti->modify_geometry_set != nullptr;

View File

@ -124,12 +124,18 @@ typedef struct ABCReadParams {
float velocity_scale;
} ABCReadParams;
/* Either modifies existing_mesh in-place or constructs a new mesh. */
struct Mesh *ABC_read_mesh(struct CacheReader *reader,
struct Object *ob,
struct Mesh *existing_mesh,
const ABCReadParams *params,
const char **err_str);
#ifdef __cplusplus
namespace blender::bke {
struct GeometrySet;
}
/* Either modifies the existing geometry component, or create a new one. */
void ABC_read_geometry(CacheReader *reader,
Object *ob,
blender::bke::GeometrySet &geometry_set,
const ABCReadParams *params,
const char **err_str);
#endif
bool ABC_mesh_topology_changed(struct CacheReader *reader,
struct Object *ob,

View File

@ -8,24 +8,23 @@
#include "abc_reader_curves.h"
#include "abc_axis_conversion.h"
#include "abc_reader_transform.h"
#include "abc_util.h"
#include <cstdio>
#include "MEM_guardedalloc.h"
#include "DNA_curve_types.h"
#include "DNA_curves_types.h"
#include "DNA_object_types.h"
#include "BLI_listbase.h"
#include "BKE_attribute.hh"
#include "BKE_curve.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_object.hh"
#include "BLI_vector.hh"
#include "BLT_translation.hh"
#include "BKE_curve.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
using Alembic::Abc::FloatArraySamplePtr;
using Alembic::Abc::Int32ArraySamplePtr;
using Alembic::Abc::P3fArraySamplePtr;
@ -42,6 +41,239 @@ using Alembic::AbcGeom::ISampleSelector;
using Alembic::AbcGeom::kWrapExisting;
namespace blender::io::alembic {
static int16_t get_curve_resolution(const ICurvesSchema &schema,
const Alembic::Abc::ISampleSelector &sample_sel)
{
ICompoundProperty user_props = schema.getUserProperties();
if (!user_props) {
return 1;
}
const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME);
if (!header || !header->isScalar() || !IInt16Property::matches(*header)) {
return 1;
}
IInt16Property resolu(user_props, header->getName());
return resolu.getValue(sample_sel);
}
static int16_t get_curve_order(const Alembic::AbcGeom::CurveType abc_curve_type,
const UcharArraySamplePtr orders,
const size_t curve_index)
{
switch (abc_curve_type) {
case Alembic::AbcGeom::kCubic:
return 4;
case Alembic::AbcGeom::kVariableOrder:
if (orders && orders->size() > curve_index) {
return int16_t((*orders)[curve_index]);
}
ATTR_FALLTHROUGH;
case Alembic::AbcGeom::kLinear:
default:
return 2;
}
}
static int get_curve_overlap(const Alembic::AbcGeom::CurvePeriodicity periodicity,
const P3fArraySamplePtr positions,
const int idx,
const int num_verts,
const int16_t order)
{
if (periodicity != Alembic::AbcGeom::kPeriodic) {
/* kNonPeriodic is always assumed to have no overlap. */
return 0;
}
/* 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;
const int safe_order = order <= num_verts ? order : num_verts;
for (int j = start, k = end - safe_order; j < (start + safe_order); 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. */
return overlap;
}
static CurveType get_curve_type(const Alembic::AbcGeom::BasisType basis)
{
switch (basis) {
case Alembic::AbcGeom::kNoBasis:
return CURVE_TYPE_POLY;
case Alembic::AbcGeom::kBezierBasis:
return CURVE_TYPE_BEZIER;
case Alembic::AbcGeom::kBsplineBasis:
return CURVE_TYPE_NURBS;
case Alembic::AbcGeom::kCatmullromBasis:
return CURVE_TYPE_CATMULL_ROM;
case Alembic::AbcGeom::kHermiteBasis:
case Alembic::AbcGeom::kPowerBasis:
/* Those types are unknown to Blender, use a default poly type. */
return CURVE_TYPE_POLY;
}
return CURVE_TYPE_POLY;
}
static bool curves_topology_changed(const bke::CurvesGeometry &geometry,
Span<int> preprocessed_offsets)
{
/* Offsets have an extra element. */
if (geometry.curve_num != preprocessed_offsets.size() - 1) {
return true;
}
const Span<int> offsets = geometry.offsets();
for (const int i_curve : preprocessed_offsets.index_range()) {
if (offsets[i_curve] != preprocessed_offsets[i_curve]) {
return true;
}
}
return false;
}
/* Preprocessed data to help and simplify converting curve data from Alembic to Blender.
* As some operations may require to look up the Alembic sample multiple times, we just
* do it once and cache the results in this.
*/
struct PreprocessedSampleData {
/* This holds one value for each spline. This will be used to lookup the data at the right
* indices, and will also be used to set #CurveGeometry.offsets. */
Vector<int> offset_in_blender;
/* This holds one value for each spline, and tells where in the Alembic curve sample the spline
* actually starts, accounting for duplicate points indicating cyclicity. */
Vector<int> offset_in_alembic;
/* This holds one value for each spline to tell whether it is cyclic. */
Vector<bool> curves_cyclic;
/* This holds one value for each spline which define its order. */
Vector<int8_t> curves_orders;
/* True if any values of `curves_overlaps` is true. If so, we will need to copy the
* `curves_overlaps` to an attribute on the Blender curves. */
bool do_cyclic = false;
/* Only one curve type for the whole objects. */
CurveType curve_type;
/* Store the pointers during preprocess so we do not have to look up the sample twice. */
P3fArraySamplePtr positions = nullptr;
FloatArraySamplePtr weights = nullptr;
FloatArraySamplePtr radii = nullptr;
};
/* Compute topological information about the curves. We do this step mainly to properly account
* for curves overlaps which imply different offsets between Blender and Alembic, but also to
* validate the data and cache some values. */
static std::optional<PreprocessedSampleData> preprocess_sample(StringRefNull iobject_name,
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",
iobject_name.c_str(),
schema.getName().c_str(),
sample_sel.getRequestedTime(),
ex.what());
return {};
}
/* Note: although Alembic can store knots, we do not read them as the functionality is not
* exposed by the Blender's Curves API yet. */
const Int32ArraySamplePtr per_curve_vertices_count = smp.getCurvesNumVertices();
const P3fArraySamplePtr positions = smp.getPositions();
const FloatArraySamplePtr weights = smp.getPositionWeights();
const CurvePeriodicity periodicity = smp.getWrap();
const UcharArraySamplePtr orders = smp.getOrders();
const IFloatGeomParam widths_param = schema.getWidthsParam();
FloatArraySamplePtr radii;
if (widths_param.valid()) {
IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel);
radii = wsample.getVals();
}
const int curve_count = per_curve_vertices_count->size();
PreprocessedSampleData data;
/* Add 1 as these store offsets with the actual value being `offset[i + 1] - offset[i]`. */
data.offset_in_blender.resize(curve_count + 1);
data.offset_in_alembic.resize(curve_count + 1);
data.curves_cyclic.resize(curve_count);
data.curve_type = get_curve_type(smp.getBasis());
if (data.curve_type == CURVE_TYPE_NURBS) {
data.curves_orders.resize(curve_count);
}
/* Compute topological information. */
int blender_offset = 0;
int alembic_offset = 0;
for (size_t i = 0; i < curve_count; i++) {
const int vertices_count = (*per_curve_vertices_count)[i];
const int curve_order = get_curve_order(smp.getType(), orders, i);
/* Check if the curve is cyclic. */
const int overlap = get_curve_overlap(
periodicity, positions, alembic_offset, vertices_count, curve_order);
data.offset_in_blender[i] = blender_offset;
data.offset_in_alembic[i] = alembic_offset;
data.curves_cyclic[i] = overlap != 0;
if (data.curve_type == CURVE_TYPE_NURBS) {
data.curves_orders[i] = curve_order;
}
data.do_cyclic |= data.curves_cyclic[i];
blender_offset += (overlap >= vertices_count) ? vertices_count : (vertices_count - overlap);
alembic_offset += vertices_count;
}
data.offset_in_blender[curve_count] = blender_offset;
data.offset_in_alembic[curve_count] = alembic_offset;
/* Store relevant pointers. */
data.positions = positions;
if (weights && weights->size() > 1) {
data.weights = weights;
}
if (radii && radii->size() > 1) {
data.radii = radii;
}
return data;
}
AbcCurveReader::AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
: AbcObjectReader(object, settings)
@ -64,13 +296,13 @@ bool AbcCurveReader::accepts_object_type(
{
if (!Alembic::AbcGeom::ICurves::matches(alembic_header)) {
*err_str = RPT_(
"Object type mismatch, Alembic object path pointed to Curves when importing, but not any "
"more");
"Object type mismatch, Alembic object path pointed to Curves when importing, but not "
"anymore.");
return false;
}
if (ob->type != OB_CURVES_LEGACY) {
*err_str = RPT_("Object type mismatch, Alembic object path points to Curves");
if (ob->type != OB_CURVES) {
*err_str = RPT_("Object type mismatch, Alembic object path points to Curves.");
return false;
}
@ -79,262 +311,103 @@ bool AbcCurveReader::accepts_object_type(
void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
{
Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVES_LEGACY);
Curves *curves = static_cast<Curves *>(BKE_curves_add(bmain, m_data_name.c_str()));
cu->flag |= CU_3D;
cu->actvert = CU_ACT_NONE;
cu->resolu = 1;
m_object = BKE_object_add_only_object(bmain, OB_CURVES, m_object_name.c_str());
m_object->data = curves;
ICompoundProperty user_props = m_curves_schema.getUserProperties();
if (user_props) {
const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME);
if (header != nullptr && 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_CURVES_LEGACY, m_object_name.c_str());
m_object->data = cu;
read_curve_sample(cu, m_curves_schema, sample_sel);
read_curves_sample(curves, m_curves_schema, sample_sel);
if (m_settings->always_add_cache_reader || has_animations(m_curves_schema, m_settings)) {
addCacheModifier();
}
}
void AbcCurveReader::read_curve_sample(Curve *cu,
const ICurvesSchema &schema,
const ISampleSelector &sample_sel)
void AbcCurveReader::read_curves_sample(Curves *curves,
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());
std::optional<PreprocessedSampleData> opt_preprocess = preprocess_sample(
m_iobject.getFullName(), schema, sample_sel);
if (!opt_preprocess) {
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 PreprocessedSampleData &data = opt_preprocess.value();
const IFloatGeomParam widths_param = schema.getWidthsParam();
FloatArraySamplePtr radiuses;
const int point_count = data.offset_in_blender.last();
const int curve_count = data.offset_in_blender.size() - 1;
if (widths_param.valid()) {
IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel);
radiuses = wsample.getVals();
bke::CurvesGeometry &geometry = curves->geometry.wrap();
if (curves_topology_changed(geometry, data.offset_in_blender)) {
geometry.resize(point_count, curve_count);
geometry.offsets_for_write().copy_from(data.offset_in_blender);
}
int knot_offset = 0;
geometry.fill_curve_types(data.curve_type);
size_t idx = 0;
for (size_t i = 0; i < num_vertices->size(); i++) {
const int num_verts = (*num_vertices)[i];
if (data.curve_type != CURVE_TYPE_POLY) {
geometry.resolution_for_write().fill(get_curve_resolution(schema, sample_sel));
}
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;
MutableSpan<float3> curves_positions = geometry.positions_for_write();
for (const int i_curve : geometry.curves_range()) {
int position_offset = data.offset_in_alembic[i_curve];
for (const int i_point : geometry.points_by_curve()[i_curve]) {
const Imath::V3f &pos = (*data.positions)[position_offset++];
copy_zup_from_yup(curves_positions[i_point], pos.getValue());
}
}
switch (smp.getType()) {
case Alembic::AbcGeom::kCubic:
nu->orderu = 4;
break;
case Alembic::AbcGeom::kVariableOrder:
if (orders && orders->size() > i) {
nu->orderu = short((*orders)[i]);
break;
}
ATTR_FALLTHROUGH;
case Alembic::AbcGeom::kLinear:
default:
nu->orderu = 2;
if (data.do_cyclic) {
geometry.cyclic_for_write().copy_from(data.curves_cyclic);
geometry.handle_types_left_for_write().fill(BEZIER_HANDLE_AUTO);
geometry.handle_types_right_for_write().fill(BEZIER_HANDLE_AUTO);
}
if (data.radii) {
bke::SpanAttributeWriter<float> radii =
geometry.attributes_for_write().lookup_or_add_for_write_span<float>(
"radius", bke::AttrDomain::Point);
for (const int i_curve : geometry.curves_range()) {
int position_offset = data.offset_in_alembic[i_curve];
for (const int i_point : geometry.points_by_curve()[i_curve]) {
radii.span[i_point] = (*data.radii)[position_offset++];
}
}
if (periodicity == Alembic::AbcGeom::kNonPeriodic) {
nu->flagu |= CU_NURB_ENDPOINT;
radii.finish();
}
if (data.curve_type == CURVE_TYPE_NURBS) {
geometry.nurbs_orders_for_write().copy_from(data.curves_orders);
if (data.weights) {
MutableSpan<float> curves_weights = geometry.nurbs_weights_for_write();
Span<float> data_weights_span = {data.weights->get(), int64_t(data.weights->size())};
for (const int i_curve : geometry.curves_range()) {
const int alembic_offset = data.offset_in_alembic[i_curve];
const IndexRange points = geometry.points_by_curve()[i_curve];
curves_weights.slice(points).copy_from(
data_weights_span.slice(alembic_offset, points.size()));
}
}
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 != nullptr) && (weights->size() > 1);
float weight = 1.0f;
const bool do_radius = (radiuses != nullptr) && (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);
}
}
Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh,
const ISampleSelector &sample_sel,
int /*read_flag*/,
const char * /*velocity_name*/,
const float /*velocity_scale*/,
const char **err_str)
void AbcCurveReader::read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int /*read_flag*/,
const char * /*velocity_name*/,
const float /*velocity_scale*/,
const char ** /*err_str*/)
{
ICurvesSchema::Sample sample;
Curves *curves = geometry_set.get_curves_for_write();
try {
sample = m_curves_schema.getValue(sample_sel);
}
catch (Alembic::Util::Exception &ex) {
*err_str = RPT_("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);
read_curves_sample(curves, m_curves_schema, sample_sel);
}
} // namespace blender::io::alembic

View File

@ -10,7 +10,7 @@
#include "abc_reader_mesh.h"
#include "abc_reader_object.h"
struct Curve;
struct Curves;
#define ABC_CURVE_RESOLUTION_U_PROPNAME "blender:resolution"
@ -28,23 +28,17 @@ class AbcCurveReader final : public AbcObjectReader {
const char **err_str) const override;
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
/**
* \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.
*/
struct Mesh *read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
void read_curve_sample(Curve *cu,
const Alembic::AbcGeom::ICurvesSchema &schema,
const Alembic::Abc::ISampleSelector &sample_selector);
void read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
void read_curves_sample(Curves *curves,
const Alembic::AbcGeom::ICurvesSchema &schema,
const Alembic::Abc::ISampleSelector &sample_selector);
};
} // namespace blender::io::alembic

View File

@ -29,6 +29,7 @@
#include "BKE_attribute.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_material.h"
@ -687,6 +688,24 @@ bool AbcMeshReader::topology_changed(const Mesh *existing_mesh, const ISampleSel
return false;
}
void AbcMeshReader::read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
const int read_flag,
const char *velocity_name,
const float velocity_scale,
const char **err_str)
{
Mesh *mesh = geometry_set.get_mesh_for_write();
if (mesh == nullptr) {
return;
}
Mesh *new_mesh = read_mesh(mesh, sample_sel, read_flag, velocity_name, velocity_scale, err_str);
geometry_set.replace_mesh(new_mesh);
}
Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh,
const ISampleSelector &sample_sel,
const int read_flag,
@ -1099,4 +1118,22 @@ Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh,
return mesh_to_export;
}
void AbcSubDReader::read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
const int read_flag,
const char *velocity_name,
const float velocity_scale,
const char **err_str)
{
Mesh *mesh = geometry_set.get_mesh_for_write();
if (mesh == nullptr) {
return;
}
Mesh *new_mesh = read_mesh(mesh, sample_sel, read_flag, velocity_name, velocity_scale, err_str);
geometry_set.replace_mesh(new_mesh);
}
} // namespace blender::io::alembic

View File

@ -33,7 +33,15 @@ class AbcMeshReader final : public AbcObjectReader {
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
const char **err_str);
void read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
bool topology_changed(const Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel) override;
@ -58,18 +66,27 @@ class AbcSubDReader final : public AbcObjectReader {
const Object *const ob,
const char **err_str) const override;
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
void read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
const float velocity_scale,
const char **err_str) override;
private:
struct Mesh *read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
const float velocity_scale,
const char **err_str);
};
void read_mverts(Mesh &mesh,
const Alembic::AbcGeom::P3fArraySamplePtr positions,
const Alembic::AbcGeom::N3fArraySamplePtr normals);
CDStreamConfig get_config(struct Mesh *mesh);
CDStreamConfig get_config(struct Mesh *mesh, const std::string &iobject_full_name);
} // namespace blender::io::alembic

View File

@ -142,14 +142,13 @@ Imath::M44d get_matrix(const IXformSchema &schema, const chrono_t time)
return blend_matrices(s0.getMatrix(), s1.getMatrix(), interpolation_settings->weight);
}
Mesh *AbcObjectReader::read_mesh(Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector & /*sample_sel*/,
int /*read_flag*/,
const char * /*velocity_name*/,
const float /*velocity_scale*/,
const char ** /*err_str*/)
void AbcObjectReader::read_geometry(bke::GeometrySet & /*geometry_set*/,
const Alembic::Abc::ISampleSelector & /*sample_sel*/,
int /*read_flag*/,
const char * /*velocity_name*/,
const float /*velocity_scale*/,
const char ** /*err_str*/)
{
return existing_mesh;
}
bool AbcObjectReader::topology_changed(const Mesh * /*existing_mesh*/,

View File

@ -17,6 +17,10 @@ struct Main;
struct Mesh;
struct Object;
namespace blender::bke {
struct GeometrySet;
}
using Alembic::AbcCoreAbstract::chrono_t;
namespace blender::io::alembic {
@ -139,12 +143,13 @@ class AbcObjectReader {
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 *velocity_name,
float velocity_scale,
const char **err_str);
virtual void read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str);
virtual bool topology_changed(const Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel);

View File

@ -7,6 +7,7 @@
*/
#include "abc_reader_points.h"
#include "abc_axis_conversion.h"
#include "abc_reader_mesh.h"
#include "abc_reader_transform.h"
#include "abc_util.h"
@ -14,22 +15,19 @@
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_pointcloud_types.h"
#include "BLT_translation.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "BKE_pointcloud.hh"
using Alembic::AbcGeom::kWrapExisting;
using Alembic::AbcGeom::N3fArraySamplePtr;
using Alembic::AbcGeom::P3fArraySamplePtr;
#include "BLI_math_vector.h"
using Alembic::AbcGeom::ICompoundProperty;
using Alembic::AbcGeom::IN3fArrayProperty;
using Alembic::AbcGeom::IPoints;
using Alembic::AbcGeom::IPointsSchema;
using Alembic::AbcGeom::ISampleSelector;
using namespace Alembic::AbcGeom;
namespace blender::io::alembic {
@ -58,8 +56,8 @@ bool AbcPointsReader::accepts_object_type(
return false;
}
if (ob->type != OB_MESH) {
*err_str = RPT_("Object type mismatch, Alembic object path points to Points");
if (ob->type != OB_POINTCLOUD) {
*err_str = RPT_("Object type mismatch, Alembic object path points to Points.");
return false;
}
@ -68,29 +66,38 @@ bool AbcPointsReader::accepts_object_type(
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, "", 0.0f, nullptr);
PointCloud *point_cloud = static_cast<PointCloud *>(
BKE_pointcloud_add_default(bmain, m_data_name.c_str()));
if (read_mesh != mesh) {
BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object);
bke::GeometrySet geometry_set = bke::GeometrySet::from_pointcloud(
point_cloud, bke::GeometryOwnershipType::Editable);
read_geometry(geometry_set, sample_sel, 0, "", 1.0f, nullptr);
PointCloud *read_point_cloud =
geometry_set.get_component_for_write<bke::PointCloudComponent>().release();
if (read_point_cloud != point_cloud) {
BKE_pointcloud_nomain_to_pointcloud(read_point_cloud, point_cloud);
}
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;
m_object = BKE_object_add_only_object(bmain, OB_POINTCLOUD, m_object_name.c_str());
m_object->data = point_cloud;
if (m_settings->always_add_cache_reader || has_animations(m_schema, m_settings)) {
addCacheModifier();
}
}
void read_points_sample(const IPointsSchema &schema,
const ISampleSelector &selector,
CDStreamConfig &config,
ImportSettings *settings)
static void read_points(const P3fArraySamplePtr positions, MutableSpan<float3> r_points)
{
for (size_t i = 0; i < positions->size(); i++) {
copy_zup_from_yup(r_points[i], (*positions)[i].getValue());
}
}
static N3fArraySamplePtr read_points_sample(const IPointsSchema &schema,
const ISampleSelector &selector,
MutableSpan<float3> r_points)
{
Alembic::AbcGeom::IPointsSchema::Sample sample = schema.getValue(selector);
@ -109,23 +116,19 @@ void read_points_sample(const IPointsSchema &schema,
}
}
read_mverts(*config.mesh, positions, vnormals);
if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) {
V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name);
if (velocities) {
read_velocity(velocities, config, settings->velocity_scale);
}
}
read_points(positions, r_points);
return vnormals;
}
Mesh *AbcPointsReader::read_mesh(Mesh *existing_mesh,
const ISampleSelector &sample_sel,
int /*read_flag*/,
const char *velocity_name,
const float velocity_scale,
const char **err_str)
void AbcPointsReader::read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int /*read_flag*/,
const char * /*velocity_name*/,
const float /*velocity_scale*/,
const char **err_str)
{
BLI_assert(geometry_set.has_pointcloud());
IPointsSchema::Sample sample;
try {
sample = m_schema.getValue(sample_sel);
@ -137,26 +140,60 @@ Mesh *AbcPointsReader::read_mesh(Mesh *existing_mesh,
m_schema.getName().c_str(),
sample_sel.getRequestedTime(),
ex.what());
return existing_mesh;
return;
}
PointCloud *existing_point_cloud = geometry_set.get_pointcloud_for_write();
PointCloud *point_cloud = existing_point_cloud;
const P3fArraySamplePtr &positions = sample.getPositions();
Mesh *new_mesh = nullptr;
const IFloatGeomParam widths_param = m_schema.getWidthsParam();
FloatArraySamplePtr radii;
if (existing_mesh->verts_num != positions->size()) {
new_mesh = BKE_mesh_new_nomain(positions->size(), 0, 0, 0);
if (widths_param.valid()) {
IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel);
radii = wsample.getVals();
}
ImportSettings settings;
settings.velocity_name = velocity_name;
settings.velocity_scale = velocity_scale;
if (point_cloud->totpoint != positions->size()) {
point_cloud = BKE_pointcloud_new_nomain(positions->size());
}
Mesh *mesh_to_export = new_mesh ? new_mesh : existing_mesh;
CDStreamConfig config = get_config(mesh_to_export);
read_points_sample(m_schema, sample_sel, config, &settings);
bke::MutableAttributeAccessor attribute_accessor = point_cloud->attributes_for_write();
return mesh_to_export;
bke::SpanAttributeWriter<float3> positions_writer =
attribute_accessor.lookup_or_add_for_write_span<float3>("position", bke::AttrDomain::Point);
MutableSpan<float3> point_positions = positions_writer.span;
N3fArraySamplePtr normals = read_points_sample(m_schema, sample_sel, point_positions);
positions_writer.finish();
bke::SpanAttributeWriter<float> point_radii_writer =
attribute_accessor.lookup_or_add_for_write_span<float>("radius", bke::AttrDomain::Point);
MutableSpan<float> point_radii = point_radii_writer.span;
if (radii) {
for (size_t i = 0; i < radii->size(); i++) {
point_radii[i] = (*radii)[i];
}
}
else {
point_radii.fill(0.01f);
}
point_radii_writer.finish();
if (normals) {
bke::SpanAttributeWriter<float3> normals_writer =
attribute_accessor.lookup_or_add_for_write_span<float3>("N", bke::AttrDomain::Point);
MutableSpan<float3> point_normals = normals_writer.span;
for (size_t i = 0; i < normals->size(); i++) {
Imath::V3f nor_in = (*normals)[i];
copy_zup_from_yup(point_normals[i], nor_in.getValue());
}
normals_writer.finish();
}
geometry_set.replace_pointcloud(point_cloud);
}
} // namespace blender::io::alembic

View File

@ -26,17 +26,12 @@ class AbcPointsReader final : public AbcObjectReader {
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 *velocity_name,
float velocity_scale,
const char **err_str) override;
void read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
};
void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema,
const Alembic::AbcGeom::ISampleSelector &selector,
CDStreamConfig &config,
ImportSettings *settings);
} // namespace blender::io::alembic

View File

@ -792,24 +792,24 @@ static ISampleSelector sample_selector_for_time(chrono_t time)
return ISampleSelector(time, ISampleSelector::kFloorIndex);
}
Mesh *ABC_read_mesh(CacheReader *reader,
Object *ob,
Mesh *existing_mesh,
const ABCReadParams *params,
const char **err_str)
void ABC_read_geometry(CacheReader *reader,
Object *ob,
blender::bke::GeometrySet &geometry_set,
const ABCReadParams *params,
const char **err_str)
{
AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str);
if (abc_reader == nullptr) {
return nullptr;
return;
}
ISampleSelector sample_sel = sample_selector_for_time(params->time);
return abc_reader->read_mesh(existing_mesh,
sample_sel,
params->read_flags,
params->velocity_name,
params->velocity_scale,
err_str);
return abc_reader->read_geometry(geometry_set,
sample_sel,
params->read_flags,
params->velocity_name,
params->velocity_scale,
err_str);
}
bool ABC_mesh_topology_changed(CacheReader *reader,

View File

@ -574,19 +574,19 @@ USDMeshReadParams create_mesh_read_params(const double motion_sample_time, const
return params;
}
Mesh *USD_read_mesh(CacheReader *reader,
Object *ob,
Mesh *existing_mesh,
const USDMeshReadParams params,
const char **err_str)
void USD_read_geometry(CacheReader *reader,
Object *ob,
blender::bke::GeometrySet &geometry_set,
const USDMeshReadParams params,
const char **err_str)
{
USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, err_str));
if (usd_reader == nullptr) {
return nullptr;
return;
}
return usd_reader->read_mesh(existing_mesh, params, err_str);
return usd_reader->read_geometry(geometry_set, params, err_str);
}
bool USD_mesh_topology_changed(CacheReader *reader,

View File

@ -1,4 +1,4 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later
* Adapted from the Blender Alembic importer implementation. Copyright 2016 Kévin Dietrich.
@ -6,41 +6,134 @@
#include "usd_reader_curve.hh"
#include "BKE_curve.hh"
#include "BKE_mesh.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_object.hh"
#include "BLI_listbase.h"
#include "BLI_index_range.hh"
#include "BLI_math_vector_types.hh"
#include "DNA_curve_types.h"
#include "DNA_curves_types.h"
#include "DNA_object_types.h"
#include "MEM_guardedalloc.h"
#include <pxr/base/vt/array.h>
#include <pxr/base/vt/types.h>
#include <pxr/base/vt/value.h>
#include <pxr/usd/usdGeom/basisCurves.h>
#include <pxr/usd/usdGeom/curves.h>
namespace blender::io::usd {
static inline float3 to_float3(pxr::GfVec3f vec3f)
{
return float3(vec3f.data());
}
static inline int bezier_point_count(int usd_count, bool is_cyclic)
{
return is_cyclic ? (usd_count / 3) : ((usd_count / 3) + 1);
}
static int point_count(int usdCount, CurveType curve_type, bool is_cyclic)
{
if (curve_type == CURVE_TYPE_BEZIER) {
return bezier_point_count(usdCount, is_cyclic);
}
else {
return usdCount;
}
}
/** Return the sum of the values of each element in `usdCounts`. This is used for precomputing the
* total number of points for all curves in some curve primitive. */
static int accumulate_point_count(const pxr::VtIntArray &usdCounts,
CurveType curve_type,
bool is_cyclic)
{
int result = 0;
for (int v : usdCounts) {
result += point_count(v, curve_type, is_cyclic);
}
return result;
}
static void add_bezier_control_point(int cp,
int offset,
MutableSpan<float3> positions,
MutableSpan<float3> handles_left,
MutableSpan<float3> handles_right,
const Span<pxr::GfVec3f> &usdPoints)
{
if (offset == 0) {
deadpin marked this conversation as resolved Outdated

There's a comment in main about /* TODO(makowalski): Beziers are not properly imported as beziers. */ -- Is that still applicable or is that actually ok now? Guessing we need a sample file to check?

There's a comment in `main` about `/* TODO(makowalski): Beziers are not properly imported as beziers. */` -- Is that still applicable or is that actually ok now? Guessing we need a sample file to check?

I don't know what problem this is referring to, so test files would be appreciated.

I don't know what problem this is referring to, so test files would be appreciated.
positions[cp] = to_float3(usdPoints[offset]);
handles_right[cp] = to_float3(usdPoints[offset + 1]);
handles_left[cp] = 2.0f * positions[cp] - handles_right[cp];
}
else if (offset == usdPoints.size() - 1) {
positions[cp] = to_float3(usdPoints[offset]);
handles_left[cp] = to_float3(usdPoints[offset - 1]);
handles_right[cp] = 2.0f * positions[cp] - handles_left[cp];
}
else {