diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index b7ade02f85e..40eb0cafe7b 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -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; diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 6081a965a01..4bd590b3578 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -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, diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc index 28c5c338be7..ee0769696b8 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.cc +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -8,24 +8,23 @@ #include "abc_reader_curves.h" #include "abc_axis_conversion.h" -#include "abc_reader_transform.h" #include "abc_util.h" #include -#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 preprocessed_offsets) +{ + /* Offsets have an extra element. */ + if (geometry.curve_num != preprocessed_offsets.size() - 1) { + return true; + } + + const Span 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 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 offset_in_alembic; + /* This holds one value for each spline to tell whether it is cyclic. */ + Vector curves_cyclic; + /* This holds one value for each spline which define its order. */ + Vector 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 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(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 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(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 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 radii = + geometry.attributes_for_write().lookup_or_add_for_write_span( + "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 curves_weights = geometry.nurbs_weights_for_write(); + Span 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(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( - 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(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(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(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 diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h index 726da43ec5b..79afc6448a6 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.h +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -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 diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 83efc23dc56..b48d7965c78 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -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 diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h index a0241ab5e63..b329b0af3b6 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.h +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -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 diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc index f412221d7a6..838dabf574f 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.cc +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -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*/, diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h index 6daf7dd5c2d..d35b0295b5b 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.h +++ b/source/blender/io/alembic/intern/abc_reader_object.h @@ -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); diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc index 9d0cbdeba13..a45de4048ad 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.cc +++ b/source/blender/io/alembic/intern/abc_reader_points.cc @@ -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( + 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().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 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 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 positions_writer = + attribute_accessor.lookup_or_add_for_write_span("position", bke::AttrDomain::Point); + MutableSpan point_positions = positions_writer.span; + N3fArraySamplePtr normals = read_points_sample(m_schema, sample_sel, point_positions); + positions_writer.finish(); + + bke::SpanAttributeWriter point_radii_writer = + attribute_accessor.lookup_or_add_for_write_span("radius", bke::AttrDomain::Point); + MutableSpan 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 normals_writer = + attribute_accessor.lookup_or_add_for_write_span("N", bke::AttrDomain::Point); + MutableSpan 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 diff --git a/source/blender/io/alembic/intern/abc_reader_points.h b/source/blender/io/alembic/intern/abc_reader_points.h index 90ccad58148..dd918b8ea63 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.h +++ b/source/blender/io/alembic/intern/abc_reader_points.h @@ -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 diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index a4aa186f2a4..0f99f86d2ff 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -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, diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index b1f18a7e371..b5a9f20b2a1 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -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(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, diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc index 95461efe2ef..dea1dd0084d 100644 --- a/source/blender/io/usd/intern/usd_reader_curve.cc +++ b/source/blender/io/usd/intern/usd_reader_curve.cc @@ -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 #include -#include - #include #include 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 positions, + MutableSpan handles_left, + MutableSpan handles_right, + const Span &usdPoints) +{ + if (offset == 0) { + 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 { + positions[cp] = to_float3(usdPoints[offset]); + handles_left[cp] = to_float3(usdPoints[offset - 1]); + handles_right[cp] = to_float3(usdPoints[offset + 1]); + } +} + +/** Returns true if the number of curves or the number of curve points in each curve differ. */ +static bool curves_topology_changed(const CurvesGeometry &geometry, + const pxr::VtIntArray &usdCounts, + CurveType curve_type, + int expected_total_point_num, + bool is_cyclic) +{ + if (geometry.curve_num != usdCounts.size()) { + return true; + } + if (geometry.point_num != expected_total_point_num) { + return true; + } + + for (const int curve_idx : IndexRange(geometry.curve_num)) { + const int expected_curve_point_num = point_count(usdCounts[curve_idx], curve_type, is_cyclic); + const int current_curve_point_num = geometry.curve_offsets[curve_idx]; + + if (current_curve_point_num != expected_curve_point_num) { + return true; + } + } + + return false; +} + +static CurveType get_curve_type(pxr::TfToken type, pxr::TfToken basis) +{ + if (type == pxr::UsdGeomTokens->cubic) { + if (basis == pxr::UsdGeomTokens->bezier) { + return CURVE_TYPE_BEZIER; + } + if (basis == pxr::UsdGeomTokens->bspline) { + return CURVE_TYPE_NURBS; + } + if (basis == pxr::UsdGeomTokens->catmullRom) { + return CURVE_TYPE_CATMULL_ROM; + } + } + + return CURVE_TYPE_POLY; +} void USDCurvesReader::create_object(Main *bmain, const double /*motionSampleTime*/) { - curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVES_LEGACY); + curve_ = static_cast(BKE_curves_add(bmain, name_.c_str())); - curve_->flag |= CU_3D; - curve_->actvert = CU_ACT_NONE; - curve_->resolu = 2; - - object_ = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, name_.c_str()); + object_ = BKE_object_add_only_object(bmain, OB_CURVES, name_.c_str()); object_->data = curve_; } void USDCurvesReader::read_object_data(Main *bmain, double motionSampleTime) { - Curve *cu = (Curve *)object_->data; + Curves *cu = (Curves *)object_->data; read_curve_sample(cu, motionSampleTime); if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying()) { @@ -50,10 +143,9 @@ void USDCurvesReader::read_object_data(Main *bmain, double motionSampleTime) USDXformReader::read_object_data(bmain, motionSampleTime); } -void USDCurvesReader::read_curve_sample(Curve *cu, const double motionSampleTime) +void USDCurvesReader::read_curve_sample(Curves *cu, const double motionSampleTime) { curve_prim_ = pxr::UsdGeomBasisCurves(prim_); - if (!curve_prim_) { return; } @@ -63,9 +155,7 @@ void USDCurvesReader::read_curve_sample(Curve *cu, const double motionSampleTime pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); pxr::VtIntArray usdCounts; - vertexAttr.Get(&usdCounts, motionSampleTime); - int num_subcurves = usdCounts.size(); pxr::VtVec3fArray usdPoints; pointsAttr.Get(&usdPoints, motionSampleTime); @@ -85,155 +175,134 @@ void USDCurvesReader::read_curve_sample(Curve *cu, const double motionSampleTime pxr::TfToken wrap; wrapAttr.Get(&wrap, motionSampleTime); - pxr::VtVec3fArray usdNormals; - curve_prim_.GetNormalsAttr().Get(&usdNormals, motionSampleTime); + const CurveType curve_type = get_curve_type(type, basis); + const bool is_cyclic = wrap == pxr::UsdGeomTokens->periodic; + const int num_subcurves = usdCounts.size(); + const int num_points = accumulate_point_count(usdCounts, curve_type, is_cyclic); + const int default_resolution = 6; - /* If normals, extrude, else bevel. - * Perhaps to be replaced by Blender/USD Schema. */ - if (!usdNormals.empty()) { - /* Set extrusion to 1.0f. */ - curve_->extrude = 1.0f; - } - else { - /* Set bevel depth to 1.0f. */ - curve_->bevel_radius = 1.0f; + bke::CurvesGeometry &geometry = cu->geometry.wrap(); + if (curves_topology_changed(geometry, usdCounts, curve_type, num_points, is_cyclic)) { + geometry.resize(num_points, num_subcurves); } - size_t idx = 0; - for (size_t i = 0; i < num_subcurves; i++) { - const int num_verts = usdCounts[i]; - Nurb *nu = static_cast(MEM_callocN(sizeof(Nurb), __func__)); + geometry.fill_curve_types(curve_type); + geometry.resolution_for_write().fill(default_resolution); - if (basis == pxr::UsdGeomTokens->bspline) { - nu->flag = CU_SMOOTH; - nu->type = CU_NURBS; - } - else if (basis == pxr::UsdGeomTokens->bezier) { - /* TODO(makowalski): Beziers are not properly imported as beziers. */ - nu->type = CU_POLY; - } - else if (basis.IsEmpty()) { - nu->type = CU_POLY; - } - nu->resolu = cu->resolu; - nu->resolv = cu->resolv; + if (is_cyclic) { + geometry.cyclic_for_write().fill(true); + } - nu->pntsu = num_verts; - nu->pntsv = 1; + if (curve_type == CURVE_TYPE_NURBS) { + const int8_t curve_order = type == pxr::UsdGeomTokens->cubic ? 4 : 2; + geometry.nurbs_orders_for_write().fill(curve_order); + } - if (type == pxr::UsdGeomTokens->cubic) { - nu->orderu = 4; - } - else if (type == pxr::UsdGeomTokens->linear) { - nu->orderu = 2; - } + MutableSpan offsets = geometry.offsets_for_write(); + MutableSpan positions = geometry.positions_for_write(); - if (wrap == pxr::UsdGeomTokens->periodic) { - nu->flagu |= CU_NURB_CYCLIC; - } - else if (wrap == pxr::UsdGeomTokens->pinned) { - nu->flagu |= CU_NURB_ENDPOINT; - } + /* Bezier curves require care in filing out their left/right handles. */ + if (type == pxr::UsdGeomTokens->cubic && basis == pxr::UsdGeomTokens->bezier) { + geometry.handle_types_left_for_write().fill(BEZIER_HANDLE_ALIGN); + geometry.handle_types_right_for_write().fill(BEZIER_HANDLE_ALIGN); - float weight = 1.0f; + MutableSpan handles_right = geometry.handle_positions_right_for_write(); + MutableSpan handles_left = geometry.handle_positions_left_for_write(); + Span points{usdPoints.data(), int64_t(usdPoints.size())}; - nu->bp = static_cast(MEM_callocN(sizeof(BPoint) * nu->pntsu, __func__)); - BPoint *bp = nu->bp; + int usd_point_offset = 0; + int point_offset = 0; + for (const int i : IndexRange(num_subcurves)) { + const int usd_point_count = usdCounts[i]; + const int point_count = bezier_point_count(usd_point_count, is_cyclic); - for (int j = 0; j < nu->pntsu; j++, bp++, idx++) { - bp->vec[0] = float(usdPoints[idx][0]); - bp->vec[1] = float(usdPoints[idx][1]); - bp->vec[2] = float(usdPoints[idx][2]); - bp->vec[3] = weight; - bp->f1 = SELECT; - bp->weight = weight; + offsets[i] = point_offset; - float radius = curve_->offset; - if (idx < usdWidths.size()) { - radius = usdWidths[idx]; + int cp_offset = 0; + for (const int cp : IndexRange(point_count)) { + add_bezier_control_point(cp, + cp_offset, + positions.slice(point_offset, point_count), + handles_left.slice(point_offset, point_count), + handles_right.slice(point_offset, point_count), + points.slice(usd_point_offset, usd_point_count)); + cp_offset += 3; } - bp->radius = radius; + point_offset += point_count; + usd_point_offset += usd_point_count; + } + } + else { + int offset = 0; + for (const int i : IndexRange(num_subcurves)) { + const int num_verts = usdCounts[i]; + offsets[i] = offset; + offset += num_verts; } - BKE_nurb_knot_calc_u(nu); - BKE_nurb_knot_calc_v(nu); + for (const int i_point : geometry.points_range()) { + positions[i_point] = to_float3(usdPoints[i_point]); + } + } - BLI_addtail(BKE_curve_nurbs_get(cu), nu); + if (!usdWidths.empty()) { + bke::SpanAttributeWriter radii = + geometry.attributes_for_write().lookup_or_add_for_write_span( + "radius", bke::AttrDomain::Point); + + pxr::TfToken widths_interp = curve_prim_.GetWidthsInterpolation(); + if (widths_interp == pxr::UsdGeomTokens->constant) { + radii.span.fill(usdWidths[0] / 2); + } + else { + const bool is_bezier_vertex_interp = (type == pxr::UsdGeomTokens->cubic && + basis == pxr::UsdGeomTokens->bezier && + widths_interp == pxr::UsdGeomTokens->vertex); + if (is_bezier_vertex_interp) { + /* Blender does not support 'vertex-varying' interpolation. + * Assign the widths as-if it were 'varying' only. */ + int usd_point_offset = 0; + int point_offset = 0; + for (const int i : IndexRange(num_subcurves)) { + const int usd_point_count = usdCounts[i]; + const int point_count = bezier_point_count(usd_point_count, is_cyclic); + + int cp_offset = 0; + for (const int cp : IndexRange(point_count)) { + radii.span[point_offset + cp] = usdWidths[usd_point_offset + cp_offset] / 2; + cp_offset += 3; + } + + point_offset += point_count; + usd_point_offset += usd_point_count; + } + } + else { + for (const int i_point : geometry.points_range()) { + radii.span[i_point] = usdWidths[i_point] / 2; + } + } + } + + radii.finish(); } } -Mesh *USDCurvesReader::read_mesh(Mesh *existing_mesh, - const USDMeshReadParams params, - const char ** /*err_str*/) +void USDCurvesReader::read_geometry(bke::GeometrySet &geometry_set, + const USDMeshReadParams params, + const char ** /*err_str*/) { if (!curve_prim_) { - return existing_mesh; + return; } - pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); - pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); - pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); - - pxr::VtIntArray usdCounts; - - vertexAttr.Get(&usdCounts, params.motion_sample_time); - int num_subcurves = usdCounts.size(); - - pxr::VtVec3fArray usdPoints; - pointsAttr.Get(&usdPoints, params.motion_sample_time); - - int vertex_idx = 0; - int curve_idx; - Curve *curve = static_cast(object_->data); - - const int curve_count = BLI_listbase_count(&curve->nurb); - bool same_topology = curve_count == num_subcurves; - - if (same_topology) { - Nurb *nurbs = static_cast(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int num_in_usd = usdCounts[curve_idx]; - const int num_in_blender = nurbs->pntsu; - - if (num_in_usd != num_in_blender) { - same_topology = false; - break; - } - } + if (!geometry_set.has_curves()) { + return; } - if (!same_topology) { - BKE_nurbList_free(&curve->nurb); - read_curve_sample(curve, params.motion_sample_time); - } - else { - Nurb *nurbs = static_cast(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int totpoint = usdCounts[curve_idx]; - - if (nurbs->bp) { - BPoint *point = nurbs->bp; - - for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { - point->vec[0] = usdPoints[vertex_idx][0]; - point->vec[1] = usdPoints[vertex_idx][1]; - point->vec[2] = usdPoints[vertex_idx][2]; - } - } - else if (nurbs->bezt) { - BezTriple *bezier = nurbs->bezt; - - for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { - bezier->vec[1][0] = usdPoints[vertex_idx][0]; - bezier->vec[1][1] = usdPoints[vertex_idx][1]; - bezier->vec[1][2] = usdPoints[vertex_idx][2]; - } - } - } - } - - return BKE_mesh_new_nomain_from_curve(object_); + Curves *curves = geometry_set.get_curves_for_write(); + read_curve_sample(curves, params.motion_sample_time); } } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_curve.hh b/source/blender/io/usd/intern/usd_reader_curve.hh index 1a697addbff..47a013df1be 100644 --- a/source/blender/io/usd/intern/usd_reader_curve.hh +++ b/source/blender/io/usd/intern/usd_reader_curve.hh @@ -10,14 +10,14 @@ #include "pxr/usd/usdGeom/basisCurves.h" -struct Curve; +struct Curves; namespace blender::io::usd { class USDCurvesReader : public USDGeomReader { protected: pxr::UsdGeomBasisCurves curve_prim_; - Curve *curve_; + Curves *curve_; public: USDCurvesReader(const pxr::UsdPrim &prim, @@ -35,11 +35,11 @@ class USDCurvesReader : public USDGeomReader { void create_object(Main *bmain, double motionSampleTime) override; void read_object_data(Main *bmain, double motionSampleTime) override; - void read_curve_sample(Curve *cu, double motionSampleTime); + void read_curve_sample(Curves *cu, double motionSampleTime); - Mesh *read_mesh(struct Mesh *existing_mesh, - USDMeshReadParams params, - const char **err_str) override; + void read_geometry(bke::GeometrySet &geometry_set, + USDMeshReadParams params, + const char **err_str) override; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_geom.hh b/source/blender/io/usd/intern/usd_reader_geom.hh index 19fd9b68d0e..a6979c659ac 100644 --- a/source/blender/io/usd/intern/usd_reader_geom.hh +++ b/source/blender/io/usd/intern/usd_reader_geom.hh @@ -8,6 +8,10 @@ struct Mesh; +namespace blender::bke { +struct GeometrySet; +} + namespace blender::io::usd { class USDGeomReader : public USDXformReader { @@ -20,9 +24,9 @@ class USDGeomReader : public USDXformReader { { } - virtual Mesh *read_mesh(struct Mesh *existing_mesh, - USDMeshReadParams params, - const char **err_str) = 0; + virtual void read_geometry(bke::GeometrySet &geometry_set, + USDMeshReadParams params, + const char **err_str) = 0; virtual bool topology_changed(const Mesh * /*existing_mesh*/, double /*motionSampleTime*/) { diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index dd88b5d16b3..f58ed557711 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -12,6 +12,7 @@ #include "BKE_attribute.hh" #include "BKE_customdata.hh" +#include "BKE_geometry_set.hh" #include "BKE_main.hh" #include "BKE_material.h" #include "BKE_mesh.hh" @@ -1122,6 +1123,18 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, return active_mesh; } +void USDMeshReader::read_geometry(bke::GeometrySet &geometry_set, + const USDMeshReadParams params, + const char **err_str) +{ + Mesh *existing_mesh = geometry_set.get_mesh_for_write(); + Mesh *new_mesh = read_mesh(existing_mesh, params, err_str); + + if (new_mesh != existing_mesh) { + geometry_set.replace_mesh(new_mesh); + } +} + std::string USDMeshReader::get_skeleton_path() const { /* Make sure we can apply UsdSkelBindingAPI to the prim. diff --git a/source/blender/io/usd/intern/usd_reader_mesh.hh b/source/blender/io/usd/intern/usd_reader_mesh.hh index badc57c27ea..05dab733edf 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.hh +++ b/source/blender/io/usd/intern/usd_reader_mesh.hh @@ -50,9 +50,9 @@ class USDMeshReader : public USDGeomReader { void create_object(Main *bmain, double motionSampleTime) override; void read_object_data(Main *bmain, double motionSampleTime) override; - struct Mesh *read_mesh(struct Mesh *existing_mesh, - USDMeshReadParams params, - const char **err_str) override; + void read_geometry(bke::GeometrySet &geometry_set, + USDMeshReadParams params, + const char **err_str) override; bool topology_changed(const Mesh *existing_mesh, double motionSampleTime) override; @@ -84,6 +84,10 @@ class USDMeshReader : public USDGeomReader { double motionSampleTime, bool new_mesh); + Mesh *read_mesh(struct Mesh *existing_mesh, + const USDMeshReadParams params, + const char **err_str); + void read_custom_data(const ImportSettings *settings, Mesh *mesh, double motionSampleTime, diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.cc b/source/blender/io/usd/intern/usd_reader_nurbs.cc index 2c4d3b63b66..6e2fea60f95 100644 --- a/source/blender/io/usd/intern/usd_reader_nurbs.cc +++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc @@ -8,6 +8,7 @@ #include "usd_reader_nurbs.hh" #include "BKE_curve.hh" +#include "BKE_geometry_set.hh" #include "BKE_mesh.hh" #include "BKE_object.hh" @@ -168,6 +169,15 @@ void USDNurbsReader::read_curve_sample(Curve *cu, const double motionSampleTime) } } +void USDNurbsReader::read_geometry(bke::GeometrySet &geometry_set, + const USDMeshReadParams params, + const char **err_str) +{ + BLI_assert(geometry_set.has_mesh()); + Mesh *new_mesh = read_mesh(nullptr, params, err_str); + geometry_set.replace_mesh(new_mesh); +} + Mesh *USDNurbsReader::read_mesh(Mesh * /*existing_mesh*/, const USDMeshReadParams params, const char ** /*err_str*/) diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.hh b/source/blender/io/usd/intern/usd_reader_nurbs.hh index 07e5c17b237..17919b09313 100644 --- a/source/blender/io/usd/intern/usd_reader_nurbs.hh +++ b/source/blender/io/usd/intern/usd_reader_nurbs.hh @@ -39,9 +39,12 @@ class USDNurbsReader : public USDGeomReader { void read_curve_sample(Curve *cu, double motionSampleTime); - Mesh *read_mesh(struct Mesh *existing_mesh, - USDMeshReadParams params, - const char **err_str) override; + void read_geometry(bke::GeometrySet &geometry_set, + USDMeshReadParams params, + const char **err_str) override; + + private: + Mesh *read_mesh(struct Mesh *existing_mesh, USDMeshReadParams params, const char **err_str); }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_shape.cc b/source/blender/io/usd/intern/usd_reader_shape.cc index 74af12c74c8..62934c98ac9 100644 --- a/source/blender/io/usd/intern/usd_reader_shape.cc +++ b/source/blender/io/usd/intern/usd_reader_shape.cc @@ -2,6 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BKE_geometry_set.hh" #include "BKE_lib_id.hh" #include "BKE_mesh.hh" #include "BKE_modifier.hh" @@ -161,6 +162,18 @@ Mesh *USDShapeReader::read_mesh(Mesh *existing_mesh, return active_mesh; } +void USDShapeReader::read_geometry(bke::GeometrySet &geometry_set, + USDMeshReadParams params, + const char **err_str) +{ + Mesh *existing_mesh = geometry_set.get_mesh_for_write(); + Mesh *new_mesh = read_mesh(existing_mesh, params, err_str); + + if (new_mesh != existing_mesh) { + geometry_set.replace_mesh(new_mesh); + } +} + Mesh *USDShapeReader::mesh_from_prim(Mesh *existing_mesh, double motionSampleTime, pxr::VtIntArray &face_indices, diff --git a/source/blender/io/usd/intern/usd_reader_shape.hh b/source/blender/io/usd/intern/usd_reader_shape.hh index dfabd921245..ac8548c8489 100644 --- a/source/blender/io/usd/intern/usd_reader_shape.hh +++ b/source/blender/io/usd/intern/usd_reader_shape.hh @@ -41,6 +41,8 @@ class USDShapeReader : public USDGeomReader { pxr::VtIntArray &face_indices, pxr::VtIntArray &face_counts) const; + Mesh *read_mesh(Mesh *existing_mesh, USDMeshReadParams params, const char ** /*err_str*/); + public: USDShapeReader(const pxr::UsdPrim &prim, const USDImportParams &import_params, @@ -48,9 +50,10 @@ class USDShapeReader : public USDGeomReader { void create_object(Main *bmain, double /*motionSampleTime*/) override; void read_object_data(Main *bmain, double motionSampleTime) override; - Mesh *read_mesh(Mesh *existing_mesh, - USDMeshReadParams params, - const char ** /*err_str*/) override; + void read_geometry(bke::GeometrySet & /*geometry_set*/, + USDMeshReadParams /*params*/, + const char ** /*err_str*/) override; + bool is_time_varying(); virtual bool topology_changed(const Mesh * /*existing_mesh*/, diff --git a/source/blender/io/usd/usd.hh b/source/blender/io/usd/usd.hh index ae2fd69287d..409a0b32597 100644 --- a/source/blender/io/usd/usd.hh +++ b/source/blender/io/usd/usd.hh @@ -19,6 +19,10 @@ struct Object; struct ReportList; struct wmJobWorkerStatus; +namespace blender::bke { +struct GeometrySet; +} + namespace blender::io::usd { /** @@ -172,11 +176,11 @@ void USD_free_handle(CacheArchiveHandle *handle); void USD_get_transform(CacheReader *reader, float r_mat[4][4], float time, float scale); /** Either modifies current_mesh in-place or constructs a new mesh. */ -Mesh *USD_read_mesh(CacheReader *reader, - Object *ob, - Mesh *existing_mesh, - USDMeshReadParams params, - const char **err_str); +void USD_read_geometry(CacheReader *reader, + Object *ob, + blender::bke::GeometrySet &geometry_set, + USDMeshReadParams params, + const char **err_str); bool USD_mesh_topology_changed(CacheReader *reader, const Object *ob, diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.cc b/source/blender/modifiers/intern/MOD_meshsequencecache.cc index b707c11bebe..22a59daf24b 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.cc +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.cc @@ -26,6 +26,7 @@ #include "MEM_guardedalloc.h" #include "BKE_cachefile.hh" +#include "BKE_geometry_set.hh" #include "BKE_lib_query.hh" #include "BKE_mesh.hh" @@ -55,8 +56,7 @@ # include "usd.hh" #endif -using blender::float3; -using blender::Span; +using namespace blender; static void init_data(ModifierData *md) { @@ -163,6 +163,91 @@ static Mesh *generate_bounding_box_mesh(const Mesh *org_mesh) #endif +static void modify_geometry_set(ModifierData *md, + const ModifierEvalContext *ctx, + bke::GeometrySet *geometry_set) +{ +#if defined(WITH_USD) || defined(WITH_ALEMBIC) + MeshSeqCacheModifierData *mcmd = reinterpret_cast(md); + + Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); + CacheFile *cache_file = mcmd->cache_file; + const float frame = DEG_get_ctime(ctx->depsgraph); + const float time = BKE_cachefile_time_offset(cache_file, frame, FPS); + const char *err_str = nullptr; + + if (!mcmd->reader || !STREQ(mcmd->reader_object_path, mcmd->object_path)) { + STRNCPY(mcmd->reader_object_path, mcmd->object_path); + BKE_cachefile_reader_open(cache_file, &mcmd->reader, ctx->object, mcmd->object_path); + if (!mcmd->reader) { + BKE_modifier_set_error( + ctx->object, md, "Could not create cache reader for file %s", cache_file->filepath); + return; + } + } + + if (geometry_set->has_mesh()) { + const Mesh *mesh = geometry_set->get_mesh(); + if (can_use_mesh_for_orco_evaluation(mcmd, ctx, mesh, time, &err_str)) { + return; + } + } + + /* Do not process data if using a render procedural, return a box instead for displaying in the + * viewport. */ + if (BKE_cache_file_uses_render_procedural(cache_file, scene)) { + const Mesh *org_mesh = nullptr; + if (geometry_set->has_mesh()) { + org_mesh = geometry_set->get_mesh(); + } + + Mesh *bbox = generate_bounding_box_mesh(org_mesh); + *geometry_set = bke::GeometrySet::from_mesh(bbox, bke::GeometryOwnershipType::Editable); + return; + } + + /* Time (in frames or seconds) between two velocity samples. Automatically computed to + * scale the velocity vectors at render time for generating proper motion blur data. */ + float velocity_scale = mcmd->velocity_scale; + if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_FRAME) { + velocity_scale *= FPS; + } + + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: { +# ifdef WITH_ALEMBIC + ABCReadParams params; + params.time = time; + params.read_flags = mcmd->read_flag; + params.velocity_name = mcmd->cache_file->velocity_name; + params.velocity_scale = velocity_scale; + ABC_read_geometry(mcmd->reader, ctx->object, *geometry_set, ¶ms, &err_str); +# endif + break; + } + case CACHEFILE_TYPE_USD: { +# ifdef WITH_USD + const blender::io::usd::USDMeshReadParams params = blender::io::usd::create_mesh_read_params( + time * FPS, mcmd->read_flag); + blender::io::usd::USD_read_geometry( + mcmd->reader, ctx->object, *geometry_set, params, &err_str); +# endif + break; + } + case CACHE_FILE_TYPE_INVALID: + break; + } + + if (err_str) { + BKE_modifier_set_error(ctx->object, md, "%s", err_str); + } + +#else + UNUSED_VARS(ctx, md, geometry_set); + return; +#endif +} + static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) { #if defined(WITH_USD) || defined(WITH_ALEMBIC) @@ -225,43 +310,10 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh } } - Mesh *result = nullptr; - - switch (cache_file->type) { - case CACHEFILE_TYPE_ALEMBIC: { -# ifdef WITH_ALEMBIC - /* Time (in frames or seconds) between two velocity samples. Automatically computed to - * scale the velocity vectors at render time for generating proper motion blur data. */ - float velocity_scale = mcmd->velocity_scale; - if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_FRAME) { - velocity_scale *= FPS; - } - - ABCReadParams params = {}; - params.time = time; - params.read_flags = mcmd->read_flag; - params.velocity_name = mcmd->cache_file->velocity_name; - params.velocity_scale = velocity_scale; - - result = ABC_read_mesh(mcmd->reader, ctx->object, mesh, ¶ms, &err_str); -# endif - break; - } - case CACHEFILE_TYPE_USD: { -# ifdef WITH_USD - const blender::io::usd::USDMeshReadParams params = blender::io::usd::create_mesh_read_params( - time * FPS, mcmd->read_flag); - result = blender::io::usd::USD_read_mesh(mcmd->reader, ctx->object, mesh, params, &err_str); -# endif - break; - } - case CACHE_FILE_TYPE_INVALID: - break; - } - - if (err_str) { - BKE_modifier_set_error(ctx->object, md, "%s", err_str); - } + bke::GeometrySet geometry_set = bke::GeometrySet::from_mesh( + mesh, bke::GeometryOwnershipType::Editable); + modify_geometry_set(md, ctx, &geometry_set); + Mesh *result = geometry_set.get_component_for_write().release(); if (!ELEM(result, nullptr, mesh) && (mesh != org_mesh)) { BKE_id_free(nullptr, mesh); @@ -443,7 +495,7 @@ ModifierTypeInfo modifierType_MeshSequenceCache = { /*deform_verts_EM*/ nullptr, /*deform_matrices_EM*/ nullptr, /*modify_mesh*/ modify_mesh, - /*modify_geometry_set*/ nullptr, + /*modify_geometry_set*/ modify_geometry_set, /*init_data*/ init_data, /*required_data_mask*/ nullptr,