Blender V4.3
usd_reader_curve.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later
4 * Adapted from the Blender Alembic importer implementation. Copyright 2016 Kévin Dietrich.
5 * Modifications Copyright 2021 Tangent Animation. All rights reserved. */
6
7#include "usd_reader_curve.hh"
8#include "usd.hh"
10#include "usd_hash_types.hh"
11
12#include "BKE_attribute.hh"
13#include "BKE_curves.hh"
14#include "BKE_geometry_set.hh"
15#include "BKE_object.hh"
16#include "BKE_report.hh"
17
18#include "BLI_index_range.hh"
20
21#include "DNA_curves_types.h"
22#include "DNA_object_types.h"
23
24#include <pxr/base/vt/types.h>
25#include <pxr/usd/usdGeom/basisCurves.h>
26#include <pxr/usd/usdGeom/primvarsAPI.h>
27
28namespace blender::io::usd {
29
30static inline float3 to_float3(pxr::GfVec3f vec3f)
31{
32 return float3(vec3f.data());
33}
34
35static inline int bezier_point_count(int usd_count, bool is_cyclic)
36{
37 return is_cyclic ? (usd_count / 3) : ((usd_count / 3) + 1);
38}
39
40static int point_count(int usdCount, CurveType curve_type, bool is_cyclic)
41{
42 if (curve_type == CURVE_TYPE_BEZIER) {
43 return bezier_point_count(usdCount, is_cyclic);
44 }
45 return usdCount;
46}
47
48static Array<int> calc_curve_offsets(const pxr::VtIntArray &usdCounts,
49 const CurveType curve_type,
50 bool is_cyclic)
51{
52 Array<int> offsets(usdCounts.size() + 1);
53 threading::parallel_for(IndexRange(usdCounts.size()), 4096, [&](const IndexRange range) {
54 for (const int i : range) {
55 offsets[i] = point_count(usdCounts[i], curve_type, is_cyclic);
56 }
57 });
59 return offsets;
60}
61
62static void add_bezier_control_point(int cp,
63 int offset,
64 MutableSpan<float3> positions,
65 MutableSpan<float3> handles_left,
66 MutableSpan<float3> handles_right,
67 const Span<pxr::GfVec3f> usdPoints)
68{
69 if (offset == 0) {
70 positions[cp] = to_float3(usdPoints[offset]);
71 handles_right[cp] = to_float3(usdPoints[offset + 1]);
72 handles_left[cp] = 2.0f * positions[cp] - handles_right[cp];
73 }
74 else if (offset == usdPoints.size() - 1) {
75 positions[cp] = to_float3(usdPoints[offset]);
76 handles_left[cp] = to_float3(usdPoints[offset - 1]);
77 handles_right[cp] = 2.0f * positions[cp] - handles_left[cp];
78 }
79 else {
80 positions[cp] = to_float3(usdPoints[offset]);
81 handles_left[cp] = to_float3(usdPoints[offset - 1]);
82 handles_right[cp] = to_float3(usdPoints[offset + 1]);
83 }
84}
85
87static bool curves_topology_changed(const bke::CurvesGeometry &curves, const Span<int> usd_offsets)
88{
89 if (curves.offsets() != usd_offsets) {
90 return true;
91 }
92
93 return false;
94}
95
96static CurveType get_curve_type(pxr::TfToken type, pxr::TfToken basis)
97{
98 if (type == pxr::UsdGeomTokens->cubic) {
99 if (basis == pxr::UsdGeomTokens->bezier) {
100 return CURVE_TYPE_BEZIER;
101 }
102 if (basis == pxr::UsdGeomTokens->bspline) {
103 return CURVE_TYPE_NURBS;
104 }
105 if (basis == pxr::UsdGeomTokens->catmullRom) {
107 }
108 }
109
110 return CURVE_TYPE_POLY;
111}
112
113static std::optional<bke::AttrDomain> convert_usd_interp_to_blender(const pxr::TfToken usd_domain)
114{
115 static const blender::Map<pxr::TfToken, bke::AttrDomain> domain_map = []() {
117 map.add_new(pxr::UsdGeomTokens->vertex, bke::AttrDomain::Point);
118 map.add_new(pxr::UsdGeomTokens->varying, bke::AttrDomain::Point);
119 map.add_new(pxr::UsdGeomTokens->constant, bke::AttrDomain::Curve);
120 map.add_new(pxr::UsdGeomTokens->uniform, bke::AttrDomain::Curve);
121 return map;
122 }();
123
124 const bke::AttrDomain *value = domain_map.lookup_ptr(usd_domain);
125
126 if (value == nullptr) {
127 return std::nullopt;
128 }
129
130 return *value;
131}
132
133void USDCurvesReader::create_object(Main *bmain, const double /*motionSampleTime*/)
134{
135 curve_ = static_cast<Curves *>(BKE_curves_add(bmain, name_.c_str()));
136
137 object_ = BKE_object_add_only_object(bmain, OB_CURVES, name_.c_str());
138 object_->data = curve_;
139}
140
141void USDCurvesReader::read_object_data(Main *bmain, double motionSampleTime)
142{
143 Curves *cu = (Curves *)object_->data;
144 read_curve_sample(cu, motionSampleTime);
145
146 if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying()) {
147 add_cache_modifier();
148 }
149
150 USDXformReader::read_object_data(bmain, motionSampleTime);
151}
152
153void USDCurvesReader::read_curve_sample(Curves *curves_id, const double motionSampleTime)
154{
155 curve_prim_ = pxr::UsdGeomBasisCurves(prim_);
156 if (!curve_prim_) {
157 return;
158 }
159
160 pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr();
161 pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr();
162 pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr();
163
164 pxr::VtIntArray usdCounts;
165 vertexAttr.Get(&usdCounts, motionSampleTime);
166
167 pxr::VtVec3fArray usdPoints;
168 pointsAttr.Get(&usdPoints, motionSampleTime);
169
170 pxr::VtFloatArray usdWidths;
171 widthsAttr.Get(&usdWidths, motionSampleTime);
172
173 pxr::UsdAttribute basisAttr = curve_prim_.GetBasisAttr();
174 pxr::TfToken basis;
175 basisAttr.Get(&basis, motionSampleTime);
176
177 pxr::UsdAttribute typeAttr = curve_prim_.GetTypeAttr();
178 pxr::TfToken type;
179 typeAttr.Get(&type, motionSampleTime);
180
181 pxr::UsdAttribute wrapAttr = curve_prim_.GetWrapAttr();
182 pxr::TfToken wrap;
183 wrapAttr.Get(&wrap, motionSampleTime);
184
185 const CurveType curve_type = get_curve_type(type, basis);
186 const bool is_cyclic = wrap == pxr::UsdGeomTokens->periodic;
187 const int curves_num = usdCounts.size();
188 const Array<int> new_offsets = calc_curve_offsets(usdCounts, curve_type, is_cyclic);
189
190 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
191 if (curves_topology_changed(curves, new_offsets)) {
192 curves.resize(new_offsets.last(), curves_num);
193 }
194
195 curves.offsets_for_write().copy_from(new_offsets);
196
197 curves.fill_curve_types(curve_type);
198
199 if (is_cyclic) {
200 curves.cyclic_for_write().fill(true);
201 }
202
203 if (curve_type == CURVE_TYPE_NURBS) {
204 const int8_t curve_order = type == pxr::UsdGeomTokens->cubic ? 4 : 2;
205 curves.nurbs_orders_for_write().fill(curve_order);
206 }
207
208 MutableSpan<float3> positions = curves.positions_for_write();
209
210 /* Bezier curves require care in filing out their left/right handles. */
211 if (type == pxr::UsdGeomTokens->cubic && basis == pxr::UsdGeomTokens->bezier) {
212 curves.handle_types_left_for_write().fill(BEZIER_HANDLE_ALIGN);
213 curves.handle_types_right_for_write().fill(BEZIER_HANDLE_ALIGN);
214
215 MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
216 MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
217 Span<pxr::GfVec3f> points{usdPoints.data(), int64_t(usdPoints.size())};
218
219 int usd_point_offset = 0;
220 int point_offset = 0;
221 for (const int i : curves.curves_range()) {
222 const int usd_point_count = usdCounts[i];
223 const int point_count = bezier_point_count(usd_point_count, is_cyclic);
224
225 int cp_offset = 0;
226 for (const int cp : IndexRange(point_count)) {
228 cp_offset,
229 positions.slice(point_offset, point_count),
230 handles_left.slice(point_offset, point_count),
231 handles_right.slice(point_offset, point_count),
232 points.slice(usd_point_offset, usd_point_count));
233 cp_offset += 3;
234 }
235
236 point_offset += point_count;
237 usd_point_offset += usd_point_count;
238 }
239 }
240 else {
241 static_assert(sizeof(pxr::GfVec3f) == sizeof(float3));
242 positions.copy_from(Span(usdPoints.data(), usdPoints.size()).cast<float3>());
243 }
244
245 if (!usdWidths.empty()) {
246 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
247 bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_only_span<float>(
248 "radius", bke::AttrDomain::Point);
249
250 pxr::TfToken widths_interp = curve_prim_.GetWidthsInterpolation();
251 if (widths_interp == pxr::UsdGeomTokens->constant) {
252 radii.span.fill(usdWidths[0] / 2.0f);
253 }
254 else {
255 const bool is_bezier_vertex_interp = (type == pxr::UsdGeomTokens->cubic &&
256 basis == pxr::UsdGeomTokens->bezier &&
257 widths_interp == pxr::UsdGeomTokens->vertex);
258 if (is_bezier_vertex_interp) {
259 /* Blender does not support 'vertex-varying' interpolation.
260 * Assign the widths as-if it were 'varying' only. */
261 int usd_point_offset = 0;
262 int point_offset = 0;
263 for (const int i : curves.curves_range()) {
264 const int usd_point_count = usdCounts[i];
265 const int point_count = bezier_point_count(usd_point_count, is_cyclic);
266
267 int cp_offset = 0;
268 for (const int cp : IndexRange(point_count)) {
269 radii.span[point_offset + cp] = usdWidths[usd_point_offset + cp_offset] / 2.0f;
270 cp_offset += 3;
271 }
272
273 point_offset += point_count;
274 usd_point_offset += usd_point_count;
275 }
276 }
277 else {
278 for (const int i_point : curves.points_range()) {
279 radii.span[i_point] = usdWidths[i_point] / 2.0f;
280 }
281 }
282 }
283
284 radii.finish();
285 }
286
287 read_custom_data(curves, motionSampleTime);
288}
289
290void USDCurvesReader::read_custom_data(bke::CurvesGeometry &curves,
291 const double motionSampleTime) const
292{
293 pxr::UsdGeomPrimvarsAPI pv_api(curve_prim_);
294
295 std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
296 for (const pxr::UsdGeomPrimvar &pv : primvars) {
297 if (!pv.HasValue()) {
298 continue;
299 }
300
301 const pxr::SdfValueTypeName pv_type = pv.GetTypeName();
302 const pxr::TfToken pv_interp = pv.GetInterpolation();
303
304 const std::optional<bke::AttrDomain> domain = convert_usd_interp_to_blender(pv_interp);
305 const std::optional<eCustomDataType> type = convert_usd_type_to_blender(pv_type);
306
307 if (!domain.has_value() || !type.has_value()) {
308 const pxr::TfToken pv_name = pxr::UsdGeomPrimvar::StripPrimvarsName(pv.GetPrimvarName());
309 BKE_reportf(reports(),
311 "Primvar '%s' (interpolation %s, type %s) cannot be converted to Blender",
312 pv_name.GetText(),
313 pv_interp.GetText(),
314 pv_type.GetAsToken().GetText());
315 continue;
316 }
317
318 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
319 copy_primvar_to_blender_attribute(pv, motionSampleTime, *type, *domain, {}, attributes);
320 }
321}
322
323void USDCurvesReader::read_geometry(bke::GeometrySet &geometry_set,
325 const char ** /*r_err_str*/)
326{
327 if (!curve_prim_) {
328 return;
329 }
330
331 if (!geometry_set.has_curves()) {
332 return;
333 }
334
335 Curves *curves = geometry_set.get_curves_for_write();
336 read_curve_sample(curves, params.motion_sample_time);
337}
338
339} // namespace blender::io::usd
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
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
CurveType
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CURVE_TYPE_CATMULL_ROM
@ BEZIER_HANDLE_ALIGN
Object is a sort of wrapper for general info.
@ OB_CURVES
const T & last(const int64_t n=0) const
Definition BLI_array.hh:285
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:484
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:241
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
constexpr void fill(const T &value) const
Definition BLI_span.hh:518
constexpr const T * data() const
Definition BLI_span.hh:216
constexpr int64_t size() const
Definition BLI_span.hh:253
static bool is_cyclic(const Nurb *nu)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static int bezier_point_count(int usd_count, bool is_cyclic)
static Array< int > calc_curve_offsets(const pxr::VtIntArray &usdCounts, const CurveType curve_type, bool is_cyclic)
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)
static int point_count(int usdCount, CurveType curve_type, bool is_cyclic)
static CurveType get_curve_type(pxr::TfToken type, pxr::TfToken basis)
std::optional< eCustomDataType > convert_usd_type_to_blender(const pxr::SdfValueTypeName usd_type)
static bool curves_topology_changed(const bke::CurvesGeometry &curves, const Span< int > usd_offsets)
static std::optional< bke::AttrDomain > convert_usd_interp_to_blender(const pxr::TfToken usd_domain)
static float3 to_float3(pxr::GfVec3f vec3f)
void copy_primvar_to_blender_attribute(const pxr::UsdGeomPrimvar &primvar, const pxr::UsdTimeCode timecode, const eCustomDataType data_type, const bke::AttrDomain domain, const OffsetIndices< int > face_indices, bke::MutableAttributeAccessor attributes)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
VecBase< float, 3 > float3
float wrap(float value, float max, float min)
Definition node_math.h:71
__int64 int64_t
Definition stdint.h:89
signed char int8_t
Definition stdint.h:75
CurvesGeometry geometry