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

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@
#include "abc_reader_mesh.h"
#include "abc_reader_object.h"
struct Curve;
struct Curves;
#define ABC_CURVE_RESOLUTION_U_PROPNAME "blender:resolution"
@ -28,23 +28,17 @@ class AbcCurveReader final : public AbcObjectReader {
const char **err_str) const override;
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
/**
* \note Alembic only stores data about control points, but the Mesh
* passed from the cache modifier contains the #DispList, which has more data
* than the control points, so to avoid corrupting the #DispList we modify the
* object directly and create a new Mesh from that. Also we might need to
* create new or delete existing NURBS in the curve.
*/
struct Mesh *read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
void read_curve_sample(Curve *cu,
const Alembic::AbcGeom::ICurvesSchema &schema,
const Alembic::Abc::ISampleSelector &sample_selector);
void read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
void read_curves_sample(Curves *curves,
const Alembic::AbcGeom::ICurvesSchema &schema,
const Alembic::Abc::ISampleSelector &sample_selector);
};
} // namespace blender::io::alembic

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,17 +26,12 @@ class AbcPointsReader final : public AbcObjectReader {
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
struct Mesh *read_mesh(struct Mesh *existing_mesh,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
void read_geometry(bke::GeometrySet &geometry_set,
const Alembic::Abc::ISampleSelector &sample_sel,
int read_flag,
const char *velocity_name,
float velocity_scale,
const char **err_str) override;
};
void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema,
const Alembic::AbcGeom::ISampleSelector &selector,
CDStreamConfig &config,
ImportSettings *settings);
} // namespace blender::io::alembic

View File

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

View File

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

View File

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

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

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

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

I don't know what problem this is referring to, so test files would be appreciated.
positions[cp] = to_float3(usdPoints[offset]);
handles_right[cp] = to_float3(usdPoints[offset + 1]);
handles_left[cp] = 2.0f * positions[cp] - handles_right[cp];
}
else if (offset == usdPoints.size() - 1) {
positions[cp] = to_float3(usdPoints[offset]);
handles_left[cp] = to_float3(usdPoints[offset - 1]);
handles_right[cp] = 2.0f * positions[cp] - handles_left[cp];
}
else {
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()) {
deadpin marked this conversation as resolved Outdated

Keep this message generic since USD is here now.

Keep this message generic since USD is here now.
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,