Blender V4.3
abc_reader_curves.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "abc_reader_curves.h"
10#include "abc_axis_conversion.h"
11#include "abc_util.h"
12
13#include <cstdio>
14
15#include "DNA_curves_types.h"
16#include "DNA_object_types.h"
17
18#include "BKE_attribute.hh"
19#include "BKE_curves.hh"
20#include "BKE_geometry_set.hh"
21#include "BKE_object.hh"
22
23#include "BLI_vector.hh"
24
25#include "BLT_translation.hh"
26
27using Alembic::Abc::FloatArraySamplePtr;
28using Alembic::Abc::Int32ArraySamplePtr;
29using Alembic::Abc::P3fArraySamplePtr;
30using Alembic::Abc::PropertyHeader;
31using Alembic::Abc::UcharArraySamplePtr;
32
33using Alembic::AbcGeom::CurvePeriodicity;
34using Alembic::AbcGeom::ICompoundProperty;
35using Alembic::AbcGeom::ICurves;
36using Alembic::AbcGeom::ICurvesSchema;
37using Alembic::AbcGeom::IFloatGeomParam;
38using Alembic::AbcGeom::IInt16Property;
39using Alembic::AbcGeom::ISampleSelector;
40using Alembic::AbcGeom::kWrapExisting;
41
42namespace blender::io::alembic {
43static int16_t get_curve_resolution(const ICurvesSchema &schema,
44 const Alembic::Abc::ISampleSelector &sample_sel)
45{
46 ICompoundProperty user_props = schema.getUserProperties();
47 if (!user_props) {
48 return 0;
49 }
50
51 const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME);
52 if (!header || !header->isScalar() || !IInt16Property::matches(*header)) {
53 return 0;
54 }
55
56 IInt16Property resolu(user_props, header->getName());
57 return resolu.getValue(sample_sel);
58}
59
60static int16_t get_curve_order(const Alembic::AbcGeom::CurveType abc_curve_type,
61 const UcharArraySamplePtr orders,
62 const size_t curve_index)
63{
64 switch (abc_curve_type) {
65 case Alembic::AbcGeom::kCubic:
66 return 4;
67 case Alembic::AbcGeom::kVariableOrder:
68 if (orders && orders->size() > curve_index) {
69 return int16_t((*orders)[curve_index]);
70 }
72 case Alembic::AbcGeom::kLinear:
73 default:
74 return 2;
75 }
76}
77
78static int8_t get_knot_mode(const Alembic::AbcGeom::CurveType abc_curve_type)
79{
80 if (abc_curve_type == Alembic::AbcGeom::kCubic) {
82 }
83
85}
86
87static int get_curve_overlap(const P3fArraySamplePtr positions,
88 const int idx,
89 const int num_verts,
90 const int16_t order)
91{
92 /* Check the number of points which overlap, we don't have overlapping points in Blender, but
93 * other software do use them to indicate that a curve is actually cyclic. Usually the number of
94 * overlapping points is equal to the order/degree of the curve.
95 */
96
97 const int start = idx;
98 const int end = idx + num_verts;
99 int overlap = 0;
100
101 const int safe_order = order <= num_verts ? order : num_verts;
102 for (int j = start, k = end - safe_order; j < (start + safe_order); j++, k++) {
103 const Imath::V3f &p1 = (*positions)[j];
104 const Imath::V3f &p2 = (*positions)[k];
105
106 if (p1 != p2) {
107 break;
108 }
109
110 overlap++;
111 }
112
113 /* TODO: Special case, need to figure out how it coincides with knots. */
114 if (overlap == 0 && num_verts > 2 && (*positions)[start] == (*positions)[end - 1]) {
115 overlap = 1;
116 }
117
118 return overlap;
119}
120
121static CurveType get_curve_type(const Alembic::AbcGeom::BasisType basis)
122{
123 switch (basis) {
124 case Alembic::AbcGeom::kNoBasis:
125 return CURVE_TYPE_POLY;
126 case Alembic::AbcGeom::kBezierBasis:
127 return CURVE_TYPE_BEZIER;
128 case Alembic::AbcGeom::kBsplineBasis:
129 return CURVE_TYPE_NURBS;
130 case Alembic::AbcGeom::kCatmullromBasis:
132 case Alembic::AbcGeom::kHermiteBasis:
133 case Alembic::AbcGeom::kPowerBasis:
134 /* Those types are unknown to Blender, use a default poly type. */
135 return CURVE_TYPE_POLY;
136 }
137 return CURVE_TYPE_POLY;
138}
139
140static inline int bezier_point_count(int alembic_count, bool is_cyclic)
141{
142 return is_cyclic ? (alembic_count / 3) : ((alembic_count / 3) + 1);
143}
144
145static inline float3 to_zup_float3(Imath::V3f v)
146{
147 float3 p;
148 copy_zup_from_yup(p, v.getValue());
149 return p;
150}
151
153 Span<int> preprocessed_offsets)
154{
155 if (curves.offsets() != preprocessed_offsets) {
156 return true;
157 }
158 return false;
159}
160
161/* Preprocessed data to help and simplify converting curve data from Alembic to Blender.
162 * As some operations may require to look up the Alembic sample multiple times, we just
163 * do it once and cache the results in this.
164 */
166 /* This holds one value for each spline. This will be used to lookup the data at the right
167 * indices, and will also be used to set #CurveGeometry.offsets. */
169 /* This holds one value for each spline, and tells where in the Alembic curve sample the spline
170 * actually starts, accounting for duplicate points indicating cyclicity. */
172 /* This holds one value for each spline to tell whether it is cyclic. */
174 /* This holds one value for each spline which define its order. */
176
177 /* True if any values of `curves_overlaps` is true. If so, we will need to copy the
178 * `curves_overlaps` to an attribute on the Blender curves. */
179 bool do_cyclic = false;
180
181 /* Only one curve type for the whole objects. */
184
185 /* Store the pointers during preprocess so we do not have to look up the sample twice. */
186 P3fArraySamplePtr positions = nullptr;
187 FloatArraySamplePtr weights = nullptr;
188 FloatArraySamplePtr radii = nullptr;
189};
190
191/* Compute topological information about the curves. We do this step mainly to properly account
192 * for curves overlaps which imply different offsets between Blender and Alembic, but also to
193 * validate the data and cache some values. */
194static std::optional<PreprocessedSampleData> preprocess_sample(StringRefNull iobject_name,
195 const ICurvesSchema &schema,
196 const ISampleSelector sample_sel)
197{
198
199 ICurvesSchema::Sample smp;
200 try {
201 smp = schema.getValue(sample_sel);
202 }
203 catch (Alembic::Util::Exception &ex) {
204 printf("Alembic: error reading curve sample for '%s/%s' at time %f: %s\n",
205 iobject_name.c_str(),
206 schema.getName().c_str(),
207 sample_sel.getRequestedTime(),
208 ex.what());
209 return {};
210 }
211
212 /* NOTE: although Alembic can store knots, we do not read them as the functionality is not
213 * exposed by the Blender's Curves API yet. */
214 const Int32ArraySamplePtr per_curve_vertices_count = smp.getCurvesNumVertices();
215 const P3fArraySamplePtr positions = smp.getPositions();
216 const FloatArraySamplePtr weights = smp.getPositionWeights();
217 const CurvePeriodicity periodicity = smp.getWrap();
218 const UcharArraySamplePtr orders = smp.getOrders();
219
220 if (positions->size() == 0) {
221 return {};
222 }
223
224 const IFloatGeomParam widths_param = schema.getWidthsParam();
225 FloatArraySamplePtr radii;
226 if (widths_param.valid()) {
227 IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel);
228 radii = wsample.getVals();
229 }
230
231 const int curve_count = per_curve_vertices_count->size();
232
234 /* Add 1 as these store offsets with the actual value being `offset[i + 1] - offset[i]`. */
235 data.offset_in_blender.resize(curve_count + 1);
236 data.offset_in_alembic.resize(curve_count + 1);
237 data.curves_cyclic.resize(curve_count);
238 data.curve_type = get_curve_type(smp.getBasis());
239 data.knot_mode = get_knot_mode(smp.getType());
240 data.do_cyclic = periodicity == Alembic::AbcGeom::kPeriodic;
241
242 /* If #kVariableOrder is set then we must have order data. If not, this sample is suspect.
243 * Interpret the data as linear as a fallback. See #126324 for one such example.
244 * See also: Alembic source code in `ICurves.h`, #ICurvesSchema::Sample::valid() */
245 if (smp.getType() == Alembic::AbcGeom::kVariableOrder && !orders) {
246 data.curve_type = CURVE_TYPE_POLY;
247 data.knot_mode = NURBS_KNOT_MODE_NORMAL;
248 data.do_cyclic = false;
249 }
250
251 if (data.curve_type == CURVE_TYPE_NURBS) {
252 data.curves_orders.resize(curve_count);
253 }
254
255 /* Compute topological information. */
256
257 int blender_offset = 0;
258 int alembic_offset = 0;
259 for (size_t i = 0; i < curve_count; i++) {
260 const int vertices_count = (*per_curve_vertices_count)[i];
261
262 const int curve_order = get_curve_order(smp.getType(), orders, i);
263
264 data.offset_in_blender[i] = blender_offset;
265 data.offset_in_alembic[i] = alembic_offset;
266 data.curves_cyclic[i] = data.do_cyclic;
267
268 if (data.curve_type == CURVE_TYPE_NURBS) {
269 data.curves_orders[i] = curve_order;
270 }
271
272 /* Some software writes repeated vertices to indicate periodicity but Blender
273 * should skip these if present. */
274 const int overlap = data.do_cyclic ?
276 positions, alembic_offset, vertices_count, curve_order) :
277 0;
278
279 if (data.curve_type == CURVE_TYPE_BEZIER) {
280 blender_offset += bezier_point_count(vertices_count, data.do_cyclic);
281 }
282 else {
283 blender_offset += (overlap >= vertices_count) ? vertices_count : (vertices_count - overlap);
284 }
285
286 alembic_offset += vertices_count;
287 }
288 data.offset_in_blender[curve_count] = blender_offset;
289 data.offset_in_alembic[curve_count] = alembic_offset;
290
291 /* Store relevant pointers. */
292
293 data.positions = positions;
294
295 if (weights && weights->size() > 1) {
296 data.weights = weights;
297 }
298
299 if (radii && radii->size() > 1) {
300 data.radii = radii;
301 }
302
303 return data;
304}
305
306AbcCurveReader::AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
307 : AbcObjectReader(object, settings)
308{
309 ICurves abc_curves(object, kWrapExisting);
310 m_curves_schema = abc_curves.getSchema();
311
313}
314
316{
317 return m_curves_schema.valid();
318}
319
321 const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
322 const Object *const ob,
323 const char **r_err_str) const
324{
325 if (!Alembic::AbcGeom::ICurves::matches(alembic_header)) {
326 *r_err_str = RPT_(
327 "Object type mismatch, Alembic object path pointed to Curves when importing, but not "
328 "anymore.");
329 return false;
330 }
331
332 if (ob->type != OB_CURVES) {
333 *r_err_str = RPT_("Object type mismatch, Alembic object path points to Curves.");
334 return false;
335 }
336
337 return true;
338}
339
340void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
341{
342 Curves *curves = static_cast<Curves *>(BKE_curves_add(bmain, m_data_name.c_str()));
343
345 m_object->data = curves;
346
347 read_curves_sample(curves, m_curves_schema, sample_sel);
348
351 }
352}
353
354static void add_bezier_control_point(int cp,
355 int offset,
356 const Span<Imath::V3f> alembic_positions,
357 MutableSpan<float3> positions,
358 MutableSpan<float3> handles_left,
359 MutableSpan<float3> handles_right)
360{
361 if (offset == 0) {
362 positions[cp] = to_zup_float3(alembic_positions[offset]);
363 handles_right[cp] = to_zup_float3(alembic_positions[offset + 1]);
364 handles_left[cp] = 2.0f * positions[cp] - handles_right[cp];
365 }
366 else if (offset == alembic_positions.size() - 1) {
367 positions[cp] = to_zup_float3(alembic_positions[offset]);
368 handles_left[cp] = to_zup_float3(alembic_positions[offset - 1]);
369 handles_right[cp] = 2.0f * positions[cp] - handles_left[cp];
370 }
371 else {
372 positions[cp] = to_zup_float3(alembic_positions[offset]);
373 handles_left[cp] = to_zup_float3(alembic_positions[offset - 1]);
374 handles_right[cp] = to_zup_float3(alembic_positions[offset + 1]);
375 }
376}
377
379 const ICurvesSchema &schema,
380 const ISampleSelector &sample_sel)
381{
382 std::optional<PreprocessedSampleData> opt_preprocess = preprocess_sample(
383 m_iobject.getFullName(), schema, sample_sel);
384 if (!opt_preprocess) {
385 return;
386 }
387
388 const PreprocessedSampleData &data = opt_preprocess.value();
389
390 const int point_count = data.offset_in_blender.last();
391 const int curve_count = data.offset_in_blender.size() - 1;
392
393 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
394
395 if (curves_topology_changed(curves, data.offset_in_blender)) {
396 curves.resize(point_count, curve_count);
397 curves.offsets_for_write().copy_from(data.offset_in_blender);
398 }
399
400 curves.fill_curve_types(data.curve_type);
401
402 if (data.curve_type != CURVE_TYPE_POLY) {
403 int16_t curve_resolution = get_curve_resolution(schema, sample_sel);
404 if (curve_resolution > 0) {
405 curves.resolution_for_write().fill(curve_resolution);
406 }
407 }
408
409 MutableSpan<float3> curves_positions = curves.positions_for_write();
410 Span<Imath::V3f> alembic_points{&(*data.positions)[0], int64_t((*data.positions).size())};
411
412 if (data.curve_type == CURVE_TYPE_BEZIER) {
413 curves.handle_types_left_for_write().fill(BEZIER_HANDLE_ALIGN);
414 curves.handle_types_right_for_write().fill(BEZIER_HANDLE_ALIGN);
415
416 MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
417 MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
418
419 int point_offset = 0;
420 for (const int i_curve : curves.curves_range()) {
421 const int alembic_point_offset = data.offset_in_alembic[i_curve];
422 const int alembic_point_count = data.offset_in_alembic[i_curve + 1] - alembic_point_offset;
423 const int cp_count = data.offset_in_blender[i_curve + 1] - data.offset_in_blender[i_curve];
424
425 int cp_offset = 0;
426 for (const int cp : IndexRange(cp_count)) {
428 cp_offset,
429 alembic_points.slice(alembic_point_offset, alembic_point_count),
430 curves_positions.slice(point_offset, point_count),
431 handles_left.slice(point_offset, point_count),
432 handles_right.slice(point_offset, point_count));
433 cp_offset += 3;
434 }
435
436 point_offset += cp_count;
437 }
438 }
439 else {
440 for (const int i_curve : curves.curves_range()) {
441 int position_offset = data.offset_in_alembic[i_curve];
442 for (const int i_point : curves.points_by_curve()[i_curve]) {
443 curves_positions[i_point] = to_zup_float3(alembic_points[position_offset++]);
444 }
445 }
446 }
447
448 if (data.do_cyclic) {
449 curves.cyclic_for_write().copy_from(data.curves_cyclic);
450 }
451
452 if (data.radii) {
454 curves.attributes_for_write().lookup_or_add_for_write_span<float>("radius",
456
457 Alembic::Abc::FloatArraySample alembic_widths = *data.radii;
458 for (const int i_point : curves.points_range()) {
459 radii.span[i_point] = alembic_widths[i_point] / 2.0f;
460 }
461
462 radii.finish();
463 }
464
465 if (data.curve_type == CURVE_TYPE_NURBS) {
466 curves.nurbs_orders_for_write().copy_from(data.curves_orders);
467 curves.nurbs_knots_modes_for_write().fill(data.knot_mode);
468
469 if (data.weights) {
470 MutableSpan<float> curves_weights = curves.nurbs_weights_for_write();
471 Span<float> data_weights_span = {data.weights->get(), int64_t(data.weights->size())};
472 for (const int i_curve : curves.curves_range()) {
473 const int alembic_offset = data.offset_in_alembic[i_curve];
474 const IndexRange points = curves.points_by_curve()[i_curve];
475 curves_weights.slice(points).copy_from(
476 data_weights_span.slice(alembic_offset, points.size()));
477 }
478 }
479 }
480}
481
483 const Alembic::Abc::ISampleSelector &sample_sel,
484 int /*read_flag*/,
485 const char * /*velocity_name*/,
486 const float /*velocity_scale*/,
487 const char ** /*r_err_str*/)
488{
489 Curves *curves = geometry_set.get_curves_for_write();
490
491 read_curves_sample(curves, m_curves_schema, sample_sel);
492}
493
494} // namespace blender::io::alembic
void * BKE_curves_add(struct Main *bmain, const char *name)
Low-level operations for curves.
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
#define ATTR_FALLTHROUGH
#define RPT_(msgid)
CurveType
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CURVE_TYPE_CATMULL_ROM
@ BEZIER_HANDLE_ALIGN
@ NURBS_KNOT_MODE_ENDPOINT
@ NURBS_KNOT_MODE_NORMAL
Object is a sort of wrapper for general info.
@ OB_CURVES
ATTR_WARN_UNUSED_RESULT const BMVert * v
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:726
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr const char * c_str() const
const T & last(const int64_t n=0) const
void resize(const int64_t new_size)
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, float velocity_scale, const char **r_err_str) override
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, const char **r_err_str) const override
AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
void read_curves_sample(Curves *curves_id, const Alembic::AbcGeom::ICurvesSchema &schema, const Alembic::Abc::ISampleSelector &sample_selector)
#define printf
static bool is_cyclic(const Nurb *nu)
static float3 to_zup_float3(Imath::V3f v)
static int16_t get_curve_resolution(const ICurvesSchema &schema, const Alembic::Abc::ISampleSelector &sample_sel)
void get_min_max_time(const Alembic::AbcGeom::IObject &object, const Schema &schema, chrono_t &min, chrono_t &max)
Definition abc_util.h:82
static bool curves_topology_changed(const bke::CurvesGeometry &curves, Span< int > preprocessed_offsets)
BLI_INLINE void copy_zup_from_yup(float zup[3], const float yup[3])
static int8_t get_knot_mode(const Alembic::AbcGeom::CurveType abc_curve_type)
static CurveType get_curve_type(const Alembic::AbcGeom::BasisType basis)
const std::string ABC_CURVE_RESOLUTION_U_PROPNAME
static int16_t get_curve_order(const Alembic::AbcGeom::CurveType abc_curve_type, const UcharArraySamplePtr orders, const size_t curve_index)
static std::optional< PreprocessedSampleData > preprocess_sample(StringRefNull iobject_name, const ICurvesSchema &schema, const ISampleSelector sample_sel)
bool has_animations(Alembic::AbcGeom::IPolyMeshSchema &schema, ImportSettings *settings)
static void add_bezier_control_point(int cp, int offset, const Span< Imath::V3f > alembic_positions, MutableSpan< float3 > positions, MutableSpan< float3 > handles_left, MutableSpan< float3 > handles_right)
static int bezier_point_count(int alembic_count, bool is_cyclic)
static int get_curve_overlap(const P3fArraySamplePtr positions, const int idx, const int num_verts, const int16_t order)
signed short int16_t
Definition stdint.h:76
__int64 int64_t
Definition stdint.h:89
signed char int8_t
Definition stdint.h:75
CurvesGeometry geometry