Alembic/USD: Use GeometrySets to import data

This rewrites the Alembic and USD data importers to work with and
output GeometrySets instead of Meshes.

The main motivation for this change is to be able to import properly
point clouds, which are currently imported as Meshes, and curves
data, which suffer from a lot of issues due to limitations of
legacy curves structures (fixed by the new curves data-block) and are
also converted to Meshes. Further, for Curves, it will allow importing
arbitrary attributes.

This patch was primarily meant for Alembic, but changes to USD import
were necessary as they share the same modifier.

For Alembic:
There should be no behavioral changes for Meshes
Curves are imported as the new Curves object type
Points are imported as PointClouds

For USD:
There should be no behavioral changes for Meshes
Curves are imported as the new Curves object type
Note that the current USD importer does not support loading PointClouds,
so this patch does not add support for it.

For both Alembic and USD, knots arrays are not read anymore, as the new
Curves object does not expose the ability to set them. Improvements can
be made in the future if and when example assets are provided.

This fixes at least the following:
#58704: Animated Alembic curves don't update on render
#112308: Curves have offset animations (alembic / USD)
#118261: wrong motion blur from usd in cycles and reverting to the first
frame when disabeling motion blur

Co-authored-by: Jesse Yurkovich <jesse.y@gmail.com>
Pull Request: https://projects.blender.org/blender/blender/pulls/115623
This commit is contained in:
Kévin Dietrich 2024-02-28 03:02:38 +01:00 committed by Jesse Yurkovich
parent 7db790c4b7
commit ea256346a8
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) {
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<Curves *>(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<Nurb *>(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<int> offsets = geometry.offsets_for_write();
MutableSpan<float3> 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<float3> handles_right = geometry.handle_positions_right_for_write();
MutableSpan<float3> handles_left = geometry.handle_positions_left_for_write();
Span<pxr::GfVec3f> points{usdPoints.data(), int64_t(usdPoints.size())};
nu->bp = static_cast<BPoint *>(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<float> radii =
geometry.attributes_for_write().lookup_or_add_for_write_span<float>(
"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<Curve *>(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<Nurb *>(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<Nurb *>(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

View File

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

View File

@ -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*/)
{

View File

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

View File

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

View File

@ -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*/)

View File

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

View File

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

View File

@ -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*/,

View File

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

View File

@ -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<MeshSeqCacheModifierData *>(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, &params, &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, &params, &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<bke::MeshComponent>().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,