Blender V5.0
usd_reader_nurbs.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
2 * SPDX-FileCopyrightText: 2023 Blender Authors
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 *
6 * Adapted from the Blender Alembic importer implementation. */
7
8#include "usd_reader_nurbs.hh"
9
10#include "BKE_curves.hh"
11
12#include "BLI_offset_indices.hh"
13#include "BLI_span.hh"
14
15#include "DNA_curves_types.h"
16
17#include <pxr/base/vt/types.h>
18
19#include <pxr/usd/usdGeom/curves.h>
20#include <pxr/usd/usdGeom/primvarsAPI.h>
21
22#include "CLG_log.h"
23static CLG_LogRef LOG = {"io.usd"};
24
25namespace blender::io::usd {
26
27/* Store incoming USD data privately and expose Blender-friendly Spans publicly. */
29 private:
30 pxr::VtArray<pxr::GfVec3f> points_;
31 pxr::VtArray<int> counts_;
32 pxr::VtArray<int> orders_;
33 pxr::VtArray<double> knots_;
34 pxr::VtArray<double> weights_;
35 pxr::VtArray<float> widths_;
36 pxr::VtArray<pxr::GfVec3f> velocities_;
37
38 public:
40 {
41 return Span(points_.cdata(), points_.size()).cast<float3>();
42 }
44 {
45 return Span(counts_.cdata(), counts_.size());
46 }
48 {
49 return Span(orders_.cdata(), orders_.size());
50 }
52 {
53 return Span(knots_.cdata(), knots_.size());
54 }
56 {
57 return Span(weights_.cdata(), weights_.size());
58 }
60 {
61 return Span(widths_.cdata(), widths_.size());
62 }
64 {
65 return Span(velocities_.cdata(), velocities_.size()).cast<float3>();
66 }
67
68 bool load(const pxr::UsdGeomNurbsCurves &curve_prim, const pxr::UsdTimeCode time)
69 {
70 curve_prim.GetCurveVertexCountsAttr().Get(&counts_, time);
71 curve_prim.GetOrderAttr().Get(&orders_, time);
72
73 if (counts_.size() != orders_.size()) {
75 "Curve vertex and order size mismatch for NURBS prim %s",
76 curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
77 return false;
78 }
79
80 if (std::any_of(counts_.cbegin(), counts_.cend(), [](int value) { return value < 0; }) ||
81 std::any_of(orders_.cbegin(), orders_.cend(), [](int value) { return value < 0; }))
82 {
84 "Invalid curve vertex count or order value detected for NURBS prim %s",
85 curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
86 return false;
87 }
88
89 curve_prim.GetPointsAttr().Get(&points_, time);
90 curve_prim.GetKnotsAttr().Get(&knots_, time);
91 curve_prim.GetWidthsAttr().Get(&widths_, time);
92
93 curve_prim.GetPointWeightsAttr().Get(&weights_, time);
94 if (!weights_.empty() && points_.size() != weights_.size()) {
96 "Invalid curve weights count for NURBS prim %s",
97 curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
98
99 /* Only clear, but continue to load other curve data. */
100 weights_.clear();
101 }
102
103 curve_prim.GetVelocitiesAttr().Get(&velocities_, time);
104 if (!velocities_.empty() && points_.size() != velocities_.size()) {
105 CLOG_WARN(&LOG,
106 "Invalid curve velocity count for NURBS prim %s",
107 curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
108
109 /* Only clear, but continue to load other curve data. */
110 velocities_.clear();
111 }
112
113 return true;
114 }
115};
116
123
125 const int order,
126 const bool is_cyclic)
127{
128 /* TODO: We have to convert knot values to float for usage in Blender APIs. Look into making
129 * calculate_multiplicity_sequence a template. */
130 Array<float> blender_knots(usd_knots.size());
131 for (const int knot_i : usd_knots.index_range()) {
132 blender_knots[knot_i] = float(usd_knots[knot_i]);
133 }
134
136 const int head = multiplicity.first();
137 const int tail = multiplicity.last();
138 const Span<int> inner = multiplicity.as_span().slice(1, multiplicity.size() - 2);
139
140 /* If the knot vector starts and ends with full multiplicity knots, then this is classified as
141 * Blender's endpoint mode. */
142 const int degree = order - 1;
143 const bool is_endpoint = is_cyclic ? (tail >= degree) : (head == order && tail >= order);
144
145 /* If all of the inner multiplicities are equal to the degree, then this is a Bezier curve. */
146 if (degree > 1 &&
147 std::all_of(inner.begin(), inner.end(), [degree](int value) { return value == degree; }))
148 {
150 }
151
152 if (is_endpoint) {
154 }
155
156 /* If all of the inner knot values are equally spaced, then this is a regular/uniform curve and
157 * we assume that our normal knot mode will match. Use custom knots otherwise. */
158 const Span<float> inner_values = blender_knots.as_span().drop_front(head).drop_back(tail);
159 if (inner_values.size() > 2) {
160 const float delta = inner_values[1] - inner_values[0];
161 if (delta < 0) {
162 /* Invalid knot vector. Use normal mode. */
164 }
165 for (int i = 2; i < inner.size(); i++) {
166 if (inner_values[i] - inner_values[i - 1] != delta) {
167 /* The knot values are not equally spaced. Use custom knots. */
169 }
170 }
171 }
172
173 /* Nothing matches. Use normal mode. */
175}
176
178 const Span<int> usd_counts,
179 const Span<int> usd_orders,
180 const Span<double> usd_knots)
181{
183 data.blender_offsets.reinitialize(usd_counts.size() + 1);
184 data.usd_offsets.reinitialize(usd_counts.size() + 1);
185 data.usd_knot_offsets.reinitialize(usd_counts.size() + 1);
186 data.is_cyclic.reinitialize(usd_counts.size());
187
188 Span<float3> usd_remaining_points = usd_points;
189 Span<double> usd_remaining_knots = usd_knots;
190
191 for (const int curve_i : usd_counts.index_range()) {
192 const int points_num = usd_counts[curve_i];
193 const int knots_num = points_num + usd_orders[curve_i];
194 const int degree = usd_orders[curve_i] - 1;
195 const Span<double> usd_current_knots = usd_remaining_knots.take_front(knots_num);
196 const Span<float3> usd_current_points = usd_remaining_points.take_front(points_num);
197 if (knots_num < 4 || knots_num != usd_current_knots.size()) {
198 data.is_cyclic[curve_i] = false;
199 }
200 else {
201 data.is_cyclic[curve_i] = usd_current_points.take_front(degree) ==
202 usd_current_points.take_back(degree);
203 }
204
205 int blender_count = usd_counts[curve_i];
206
207 /* Account for any repeated degree(order - 1) number of points from USD cyclic curves which
208 * Blender does not use internally. */
209 if (data.is_cyclic[curve_i]) {
210 blender_count -= degree;
211 }
212
213 data.blender_offsets[curve_i] = blender_count;
214 data.usd_offsets[curve_i] = points_num;
215 data.usd_knot_offsets[curve_i] = knots_num;
216
217 /* Move to next sequence of values. */
218 usd_remaining_points = usd_remaining_points.drop_front(points_num);
219 usd_remaining_knots = usd_remaining_knots.drop_front(knots_num);
220 }
221
225 return data;
226}
227
229static bool curves_topology_changed(const bke::CurvesGeometry &curves, const Span<int> usd_offsets)
230{
231 if (curves.offsets() != usd_offsets) {
232 return true;
233 }
234
235 return false;
236}
237
239 IndexRange usd_points_range)
240{
241 /* Take from the front of USD's range to exclude any duplicates at the end. */
242 return usd_points_range.take_front(blender_points_range.size());
243};
244
246{
247 if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying() ||
248 curve_prim_.GetWidthsAttr().ValueMightBeTimeVarying() ||
249 curve_prim_.GetPointWeightsAttr().ValueMightBeTimeVarying())
250 {
251 return true;
252 }
253
254 pxr::UsdGeomPrimvarsAPI pv_api(curve_prim_);
255 for (const pxr::UsdGeomPrimvar &pv : pv_api.GetPrimvarsWithValues()) {
256 if (pv.ValueMightBeTimeVarying()) {
257 return true;
258 }
259 }
260
261 return false;
262}
263
264void USDNurbsReader::read_curve_sample(Curves *curves_id, const pxr::UsdTimeCode time)
265{
266 USDCurveData usd_data;
267 if (!usd_data.load(curve_prim_, time)) {
268 return;
269 }
270
271 const Span<float3> usd_points = usd_data.points();
272 const Span<int> usd_counts = usd_data.counts();
273 const Span<int> usd_orders = usd_data.orders();
274 const Span<double> usd_knots = usd_data.knots();
275 const Span<double> usd_weights = usd_data.weights();
276 const Span<float3> usd_velocities = usd_data.velocities();
277
278 /* Calculate and set the Curves topology. */
279 CurveData data = calc_curve_offsets(usd_points, usd_counts, usd_orders, usd_knots);
280
281 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
282 if (curves_topology_changed(curves, data.blender_offsets)) {
283 curves.resize(data.blender_offsets.last(), usd_counts.size());
284 curves.offsets_for_write().copy_from(data.blender_offsets);
285 curves.fill_curve_types(CurveType::CURVE_TYPE_NURBS);
286 }
287
288 /* NOTE: USD contains duplicated points for periodic(cyclic) curves. The indices into each curve
289 * will differ from what Blender expects so we need to maintain and use separate offsets for
290 * each. A side effect of this dissonance is that all primvar/attribute loading needs to be
291 * handled in a special manner vs. what might be seen in our other USD readers. */
292 const OffsetIndices blender_points_by_curve = curves.points_by_curve();
293 const OffsetIndices usd_points_by_curve = OffsetIndices<int>(data.usd_offsets,
295 const OffsetIndices usd_knots_by_curve = OffsetIndices<int>(data.usd_knot_offsets,
297
298 /* TODO: We cannot read custom primvars for cyclic curves at the moment. */
299 const bool can_read_primvars = std::all_of(
300 data.is_cyclic.begin(), data.is_cyclic.end(), [](bool item) { return item == false; });
301
302 /* Set all curve data. */
303 MutableSpan<float3> curves_positions = curves.positions_for_write();
304 for (const int curve_i : blender_points_by_curve.index_range()) {
305 const IndexRange blender_points_range = blender_points_by_curve[curve_i];
306 const IndexRange usd_points_range_de_dup = get_usd_points_range_de_dup(
307 blender_points_range, usd_points_by_curve[curve_i]);
308
309 curves_positions.slice(blender_points_range)
310 .copy_from(usd_points.slice(usd_points_range_de_dup));
311 }
312
313 MutableSpan<bool> curves_cyclic = curves.cyclic_for_write();
314 curves_cyclic.copy_from(data.is_cyclic);
315
316 MutableSpan<int8_t> curves_nurbs_orders = curves.nurbs_orders_for_write();
317 for (const int curve_i : blender_points_by_curve.index_range()) {
318 curves_nurbs_orders[curve_i] = int8_t(usd_orders[curve_i]);
319 }
320
321 MutableSpan<int8_t> curves_knots_mode = curves.nurbs_knots_modes_for_write();
322 for (const int curve_i : blender_points_by_curve.index_range()) {
323 const IndexRange usd_knots_range = usd_knots_by_curve[curve_i];
324 curves_knots_mode[curve_i] = determine_knots_mode(
325 usd_knots.slice(usd_knots_range), usd_orders[curve_i], data.is_cyclic[curve_i]);
326 }
327
328 /* Load in the optional weights. */
329 if (!usd_weights.is_empty()) {
330 MutableSpan<float> curves_weights = curves.nurbs_weights_for_write();
331 for (const int curve_i : blender_points_by_curve.index_range()) {
332 const IndexRange blender_points_range = blender_points_by_curve[curve_i];
333 const IndexRange usd_points_range_de_dup = get_usd_points_range_de_dup(
334 blender_points_range, usd_points_by_curve[curve_i]);
335
336 const Span<double> usd_weights_de_dup = usd_weights.slice(usd_points_range_de_dup);
337 int64_t usd_point_i = 0;
338 for (const int point_i : blender_points_range) {
339 curves_weights[point_i] = float(usd_weights_de_dup[usd_point_i]);
340 usd_point_i++;
341 }
342 }
343 }
344
345 /* Load in the optional velocities. */
346 if (!usd_velocities.is_empty()) {
348 bke::SpanAttributeWriter<float3> curves_velocity =
350
351 for (const int curve_i : blender_points_by_curve.index_range()) {
352 const IndexRange blender_points_range = blender_points_by_curve[curve_i];
353 const IndexRange usd_points_range_de_dup = get_usd_points_range_de_dup(
354 blender_points_range, usd_points_by_curve[curve_i]);
355
356 curves_velocity.span.slice(blender_points_range)
357 .copy_from(usd_velocities.slice(usd_points_range_de_dup));
358 }
359
360 curves_velocity.finish();
361 }
362
363 /* Once all of the curves metadata (orders, cyclic, knots_mode) has been set, we can prepare
364 * Blender for any custom knots that need to be loaded. */
365 MutableSpan<float> blender_custom_knots;
366 OffsetIndices<int> blender_knots_by_curve;
367 for (const int curve_i : curves.curves_range()) {
368 if (curves_knots_mode[curve_i] != NURBS_KNOT_MODE_CUSTOM) {
369 continue;
370 }
371
372 /* If this is our first time through, we need to update Blender's topology data to prepare for
373 * the incoming custom knots. */
374 if (blender_custom_knots.is_empty()) {
376 blender_knots_by_curve = curves.nurbs_custom_knots_by_curve();
377 blender_custom_knots = curves.nurbs_custom_knots_for_write();
378 }
379
380 const IndexRange blender_knots_range = blender_knots_by_curve[curve_i];
381 const IndexRange usd_knots_range = usd_knots_by_curve[curve_i];
382 const Span<double> usd_knots_values = usd_knots.slice(usd_knots_range);
383 MutableSpan<float> blender_knots = blender_custom_knots.slice(blender_knots_range);
384 int usd_knot_i = 0;
385 for (float &blender_knot : blender_knots) {
386 blender_knot = usd_knots_values[usd_knot_i] > 0.0 ? float(usd_knots_values[usd_knot_i]) : 0;
387 usd_knot_i++;
388 }
389 }
390
391 /* Curve widths. */
392 const Span<float> usd_widths = usd_data.widths();
393 if (!usd_widths.is_empty()) {
394 MutableSpan<float> radii = curves.radius_for_write();
395
396 const pxr::TfToken widths_interp = curve_prim_.GetWidthsInterpolation();
397 if (widths_interp == pxr::UsdGeomTokens->constant || usd_widths.size() == 1) {
398 radii.fill(usd_widths[0] / 2.0f);
399 }
400 else if (widths_interp == pxr::UsdGeomTokens->varying) {
401 int point_offset = 0;
402 for (const int curve_i : curves.curves_range()) {
403 const float usd_curve_radius = usd_widths[curve_i] / 2.0f;
404 int point_count = usd_counts[curve_i];
405 if (curves_cyclic[curve_i]) {
406 point_count -= usd_orders[curve_i] - 1;
407 }
408 for (const int point : IndexRange(point_count)) {
409 radii[point_offset + point] = usd_curve_radius;
410 }
411
412 point_offset += point_count;
413 }
414 }
415 else if (widths_interp == pxr::UsdGeomTokens->vertex) {
416 for (const int curve_i : curves.curves_range()) {
417 const IndexRange blender_points_range = blender_points_by_curve[curve_i];
418 const IndexRange usd_points_range = usd_points_by_curve[curve_i];
419
420 /* Take from the front of USD's range to exclude any duplicates at the end. */
421 const IndexRange usd_points_range_de_dup = usd_points_range.take_front(
422 blender_points_range.size());
423
424 const Span<float> usd_widths_de_dup = usd_widths.slice(usd_points_range_de_dup);
425 int64_t usd_point_i = 0;
426 for (const int point_i : blender_points_range) {
427 radii[point_i] = usd_widths_de_dup[usd_point_i] / 2.0f;
428 usd_point_i++;
429 }
430 }
431 }
432 }
433
434 if (can_read_primvars) {
435 this->read_custom_data(curves, time);
436 }
437}
438
439} // namespace blender::io::usd
Low-level operations for curves.
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
KnotsMode
@ NURBS_KNOT_MODE_ENDPOINT
@ NURBS_KNOT_MODE_NORMAL
@ NURBS_KNOT_MODE_BEZIER
@ NURBS_KNOT_MODE_ENDPOINT_BEZIER
@ NURBS_KNOT_MODE_CUSTOM
BMesh const char void * data
long long int int64_t
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
Span< T > as_span() const
Definition BLI_array.hh:243
constexpr int64_t size() const
constexpr IndexRange take_front(int64_t n) const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr bool is_empty() const
Definition BLI_span.hh:509
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr Span drop_front(int64_t n) const
Definition BLI_span.hh:171
Span< NewT > constexpr cast() const
Definition BLI_span.hh:418
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr Span take_back(int64_t n) const
Definition BLI_span.hh:204
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T * end() const
Definition BLI_span.hh:224
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr const T * begin() const
Definition BLI_span.hh:220
constexpr Span take_front(int64_t n) const
Definition BLI_span.hh:193
constexpr bool is_empty() const
Definition BLI_span.hh:260
int64_t size() const
const T & last(const int64_t n=0) const
Span< T > as_span() const
const T & first() const
MutableSpan< float3 > positions_for_write()
MutableSpan< float > nurbs_custom_knots_for_write()
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
MutableAttributeAccessor attributes_for_write()
MutableSpan< int8_t > nurbs_knots_modes_for_write()
MutableSpan< int8_t > nurbs_orders_for_write()
MutableSpan< float > radius_for_write()
MutableSpan< float > nurbs_weights_for_write()
void resize(int points_num, int curves_num)
void fill_curve_types(CurveType type)
MutableSpan< int > offsets_for_write()
OffsetIndices< int > nurbs_custom_knots_by_curve() const
MutableSpan< bool > cyclic_for_write()
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType data_type)
void read_custom_data(bke::CurvesGeometry &curves, const pxr::UsdTimeCode time) const
void read_curve_sample(Curves *curves_id, pxr::UsdTimeCode time) override
nullptr float
static bool is_cyclic(const Nurb *nu)
#define LOG(level)
Definition log.h:97
Vector< int > calculate_multiplicity_sequence(Span< float > knots)
static Array< int > calc_curve_offsets(const pxr::VtIntArray &usdCounts, const CurveType curve_type, bool is_cyclic)
static IndexRange get_usd_points_range_de_dup(IndexRange blender_points_range, IndexRange usd_points_range)
static int point_count(int usdCount, CurveType curve_type, bool is_cyclic)
static KnotsMode determine_knots_mode(const Span< double > usd_knots, const int order, const bool is_cyclic)
static bool curves_topology_changed(const bke::CurvesGeometry &curves, const Span< int > usd_offsets)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
VecBase< float, 3 > float3
CurvesGeometry geometry
bool load(const pxr::UsdGeomNurbsCurves &curve_prim, const pxr::UsdTimeCode time)
Span< float3 > points() const
Span< float3 > velocities() const
Span< double > weights() const
i
Definition text_draw.cc:230