Blender V5.0
usd_writer_curves.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <cstdint>
6#include <numeric>
7
8#include <pxr/usd/usdGeom/basisCurves.h>
9#include <pxr/usd/usdGeom/curves.h>
10#include <pxr/usd/usdGeom/nurbsCurves.h>
11#include <pxr/usd/usdGeom/primvar.h>
12#include <pxr/usd/usdGeom/primvarsAPI.h>
13#include <pxr/usd/usdGeom/tokens.h>
14#include <pxr/usd/usdShade/material.h>
15#include <pxr/usd/usdShade/materialBindingAPI.h>
16
19#include "usd_utils.hh"
20#include "usd_writer_curves.hh"
21
22#include "BLI_array_utils.hh"
24#include "BLI_set.hh"
25#include "BLI_span.hh"
26#include "BLI_virtual_array.hh"
27
29#include "BKE_attribute.hh"
31#include "BKE_curves.hh"
32#include "BKE_lib_id.hh"
33#include "BKE_material.hh"
34#include "BKE_report.hh"
35
36#include "BLT_translation.hh"
37
38#include "DNA_material_types.h"
39
40#include "RNA_access.hh"
41#include "RNA_enum_types.hh"
42
43namespace blender::io::usd {
44
45pxr::UsdGeomBasisCurves USDCurvesWriter::DefineUsdGeomBasisCurves(pxr::VtValue curve_basis,
46 const bool is_cyclic,
47 const bool is_cubic) const
48{
49 pxr::UsdGeomBasisCurves basis_curves = pxr::UsdGeomBasisCurves::Define(
51 /* Not required to set the basis attribute for linear curves
52 * https://graphics.pixar.com/usd/dev/api/class_usd_geom_basis_curves.html#details */
53 if (is_cubic) {
54 basis_curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
55 basis_curves.CreateBasisAttr(curve_basis);
56 }
57 else {
58 basis_curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->linear));
59 }
60
61 if (is_cyclic) {
62 basis_curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->periodic));
63 }
64 else if (curve_basis == pxr::VtValue(pxr::UsdGeomTokens->catmullRom)) {
65 /* In Blender the first and last points are treated as endpoints. The pinned attribute tells
66 * the client that to evaluate or render the curve, it must effectively add 'phantom
67 * points' at the beginning and end of every curve in a batch. These phantom points are
68 * injected to ensure that the interpolated curve begins at P[0] and ends at P[n-1]. */
69 basis_curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->pinned));
70 }
71 else {
72 basis_curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->nonperiodic));
73 }
74
75 return basis_curves;
76}
77
78static void populate_curve_widths(const bke::CurvesGeometry &curves, pxr::VtArray<float> &widths)
79{
80 const VArray<float> radii = curves.radius();
81
82 widths.resize(radii.size());
83 for (const int i : radii.index_range()) {
84 widths[i] = radii[i] * 2.0f;
85 }
86}
87
88static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray<float> &widths,
89 const pxr::VtArray<int> &segments,
90 const pxr::VtIntArray &control_point_counts,
91 const bool is_cyclic,
92 ReportList *reports)
93{
94 if (widths.empty()) {
95 return pxr::TfToken();
96 }
97
98 const size_t accumulated_control_point_count = std::accumulate(
99 control_point_counts.begin(), control_point_counts.end(), 0);
100
101 /* For Blender curves, radii are always stored per point. For linear curves, this should match
102 * with USD's vertex interpolation. For cubic curves, this should match with USD's varying
103 * interpolation. */
104 if (widths.size() == accumulated_control_point_count) {
105 return pxr::UsdGeomTokens->vertex;
106 }
107
108 size_t expectedVaryingSize = std::accumulate(segments.begin(), segments.end(), 0);
109 if (!is_cyclic) {
110 expectedVaryingSize += control_point_counts.size();
111 }
112
113 if (widths.size() == expectedVaryingSize) {
114 return pxr::UsdGeomTokens->varying;
115 }
116
117 BKE_report(reports, RPT_WARNING, "Curve width size not supported for USD interpolation");
118 return pxr::TfToken();
119}
120
122 const Span<float3> positions,
123 pxr::VtArray<pxr::GfVec3f> &verts,
124 pxr::VtIntArray &control_point_counts,
125 pxr::VtArray<int> &segments,
126 const bool is_cyclic,
127 const bool is_cubic)
128{
129 const OffsetIndices points_by_curve = curves.points_by_curve();
130 for (const int i_curve : curves.curves_range()) {
131
132 const IndexRange points = points_by_curve[i_curve];
133 for (const int i_point : points) {
134 verts.push_back(
135 pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
136 }
137
138 const int tot_points = points.size();
139 control_point_counts[i_curve] = tot_points;
140
141 /* For periodic linear curve, segment count = curveVertexCount.
142 * For periodic cubic curve, segment count = curveVertexCount / vstep.
143 * For nonperiodic linear curve, segment count = curveVertexCount - 1.
144 * For nonperiodic cubic curve, segment count = ((curveVertexCount - 4) / vstep) + 1.
145 * This function handles linear and Catmull-Rom curves. For Catmull-Rom, vstep is 1.
146 * https://graphics.pixar.com/usd/dev/api/class_usd_geom_basis_curves.html */
147 if (is_cyclic) {
148 segments[i_curve] = tot_points;
149 }
150 else if (is_cubic) {
151 segments[i_curve] = (tot_points - 4) + 1;
152 }
153 else {
154 segments[i_curve] = tot_points - 1;
155 }
156 }
157}
158
160 pxr::VtArray<pxr::GfVec3f> &verts,
161 pxr::VtIntArray &control_point_counts,
162 pxr::VtArray<float> &widths,
163 pxr::TfToken &interpolation,
164 const bool is_cyclic,
165 const bool is_cubic,
166 ReportList *reports)
167{
168 const int num_curves = curves.curve_num;
169 const Span<float3> positions = curves.positions();
170
171 pxr::VtArray<int> segments(num_curves);
172
174 curves, positions, verts, control_point_counts, segments, is_cyclic, is_cubic);
175
176 populate_curve_widths(curves, widths);
177 interpolation = get_curve_width_interpolation(
178 widths, segments, control_point_counts, is_cyclic, reports);
179}
180
182 const Span<float3> positions,
183 const Span<float3> handles_l,
184 const Span<float3> handles_r,
185 pxr::VtArray<pxr::GfVec3f> &verts,
186 pxr::VtIntArray &control_point_counts,
187 pxr::VtArray<int> &segments,
188 const bool is_cyclic)
189{
190 const int bezier_vstep = 3;
191 const OffsetIndices points_by_curve = curves.points_by_curve();
192
193 for (const int i_curve : curves.curves_range()) {
194
195 const IndexRange points = points_by_curve[i_curve];
196 const int start_point_index = points[0];
197 const int last_point_index = points[points.size() - 1];
198
199 const int start_verts_count = verts.size();
200
201 for (int i_point = start_point_index; i_point < last_point_index; i_point++) {
202
203 /* The order verts in the USD bezier curve representation is [control point 0, right handle
204 * 0, left handle 1, control point 1, right handle 1, left handle 2, control point 2, ...].
205 * The last vert in the array doesn't need a right handle because the curve stops at that
206 * point. */
207 verts.push_back(
208 pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
209
210 const blender::float3 right_handle = handles_r[i_point];
211 verts.push_back(pxr::GfVec3f(right_handle[0], right_handle[1], right_handle[2]));
212
213 const blender::float3 left_handle = handles_l[i_point + 1];
214 verts.push_back(pxr::GfVec3f(left_handle[0], left_handle[1], left_handle[2]));
215 }
216
217 verts.push_back(pxr::GfVec3f(positions[last_point_index][0],
218 positions[last_point_index][1],
219 positions[last_point_index][2]));
220
221 /* For USD periodic bezier curves, since the curve is closed, we need to include
222 * the right handle of the last point and the left handle of the first point.
223 */
224 if (is_cyclic) {
225 const blender::float3 right_handle = handles_r[last_point_index];
226 verts.push_back(pxr::GfVec3f(right_handle[0], right_handle[1], right_handle[2]));
227
228 const blender::float3 left_handle = handles_l[start_point_index];
229 verts.push_back(pxr::GfVec3f(left_handle[0], left_handle[1], left_handle[2]));
230 }
231
232 const int tot_points = verts.size() - start_verts_count;
233 control_point_counts[i_curve] = tot_points;
234
235 if (is_cyclic) {
236 segments[i_curve] = tot_points / bezier_vstep;
237 }
238 else {
239 segments[i_curve] = ((tot_points - 4) / bezier_vstep) + 1;
240 }
241 }
242}
243
245 pxr::VtArray<pxr::GfVec3f> &verts,
246 pxr::VtIntArray &control_point_counts,
247 pxr::VtArray<float> &widths,
248 pxr::TfToken &interpolation,
249 const bool is_cyclic,
250 ReportList *reports)
251{
252 const int num_curves = curves.curve_num;
253
254 const Span<float3> positions = curves.positions();
255 const std::optional<Span<float3>> handles_l = curves.handle_positions_left();
256 const std::optional<Span<float3>> handles_r = curves.handle_positions_right();
257
258 pxr::VtArray<int> segments(num_curves);
259
261 curves, positions, *handles_l, *handles_r, verts, control_point_counts, segments, is_cyclic);
262
263 populate_curve_widths(curves, widths);
264 interpolation = get_curve_width_interpolation(
265 widths, segments, control_point_counts, is_cyclic, reports);
266}
267
269 pxr::VtArray<pxr::GfVec3f> &verts,
270 pxr::VtIntArray &control_point_counts,
271 pxr::VtArray<float> &widths,
272 pxr::VtArray<double> &knots,
273 pxr::VtArray<double> &weights,
274 pxr::VtArray<int> &orders,
275 pxr::TfToken &interpolation,
276 const bool is_cyclic)
277{
278 /* Order and range, when representing a batched NurbsCurve should be authored one value per
279 * curve. */
280 const int num_curves = curves.curve_num;
281 orders.resize(num_curves);
282
283 const Span<float3> positions = curves.positions();
284 const Span<float> custom_knots = curves.nurbs_custom_knots();
285 const std::optional<Span<float>> nurbs_weights = curves.nurbs_weights();
286
287 VArray<int8_t> geom_orders = curves.nurbs_orders();
288 VArray<int8_t> knots_modes = curves.nurbs_knots_modes();
289 const VArray<float> radii = curves.radius();
290
291 const OffsetIndices points_by_curve = curves.points_by_curve();
292 const OffsetIndices custom_knots_by_curve = curves.nurbs_custom_knots_by_curve();
293 for (const int i_curve : curves.curves_range()) {
294 const IndexRange points = points_by_curve[i_curve];
295 const size_t curr_vert_num = verts.size();
296 for (const int i_point : points) {
297 verts.push_back(
298 pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
299 widths.push_back(radii[i_point] * 2.0f);
300 }
301
302 if (nurbs_weights) {
303 for (const int i_point : points) {
304 weights.push_back((*nurbs_weights)[i_point]);
305 }
306 }
307
308 /* Repeat the first degree(order - 1) number of points and weights if curve is cyclic. */
309 if (is_cyclic) {
310 for (const int i_point : points.take_front(geom_orders[i_curve] - 1)) {
311 verts.push_back(
312 pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
313 widths.push_back(radii[i_point] * 2.0f);
314 if (nurbs_weights) {
315 weights.push_back((*nurbs_weights)[i_point]);
316 }
317 }
318 }
319
320 const int tot_blender_points = int(points.size());
321 const int tot_usd_points = int(verts.size() - curr_vert_num);
322 control_point_counts[i_curve] = tot_usd_points;
323
324 const int8_t order = geom_orders[i_curve];
325 orders[i_curve] = int(geom_orders[i_curve]);
326
327 const KnotsMode mode = KnotsMode(knots_modes[i_curve]);
328
329 const int knots_num = bke::curves::nurbs::knots_num(tot_blender_points, order, is_cyclic);
330 Array<float> temp_knots(knots_num);
332 tot_blender_points,
333 order,
334 is_cyclic,
335 custom_knots_by_curve[i_curve],
336 custom_knots,
337 temp_knots);
338
339 /* Knots should be the concatenation of all batched curves.
340 * https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
341 for (int i_knot = 0; i_knot < knots_num; i_knot++) {
342 knots.push_back(double(temp_knots[i_knot]));
343 }
344
345 /* For USD it is required to set specific end knots for periodic/non-periodic curves
346 * https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
347 int zeroth_knot_index = knots.size() - knots_num;
348 if (is_cyclic) {
349 knots[zeroth_knot_index] = knots[zeroth_knot_index + 1] -
350 (knots[knots.size() - 2] - knots[knots.size() - 3]);
351 knots[knots.size() - 1] = knots[knots.size() - 2] +
352 (knots[zeroth_knot_index + 2] - knots[zeroth_knot_index + 1]);
353 }
354 else {
355 knots[zeroth_knot_index] = knots[zeroth_knot_index + 1];
356 knots[knots.size() - 1] = knots[knots.size() - 2];
357 }
358 }
359
360 interpolation = pxr::UsdGeomTokens->vertex;
361}
362
363void USDCurvesWriter::set_writer_attributes_for_nurbs(
364 const pxr::UsdGeomNurbsCurves &usd_nurbs_curves,
365 pxr::VtArray<double> &knots,
366 pxr::VtArray<double> &weights,
367 pxr::VtArray<int> &orders,
368 const pxr::UsdTimeCode time)
369{
370 pxr::UsdAttribute attr_knots = usd_nurbs_curves.CreateKnotsAttr(pxr::VtValue(), true);
371 set_attribute(attr_knots, knots, time, usd_value_writer_);
372 pxr::UsdAttribute attr_weights = usd_nurbs_curves.CreatePointWeightsAttr(pxr::VtValue(), true);
373 set_attribute(attr_weights, weights, time, usd_value_writer_);
374 pxr::UsdAttribute attr_order = usd_nurbs_curves.CreateOrderAttr(pxr::VtValue(), true);
375 set_attribute(attr_order, orders, time, usd_value_writer_);
376}
377
378void USDCurvesWriter::set_writer_attributes(pxr::UsdGeomCurves &usd_curves,
379 pxr::VtArray<pxr::GfVec3f> &verts,
380 pxr::VtIntArray &control_point_counts,
381 pxr::VtArray<float> &widths,
382 const pxr::UsdTimeCode time,
383 const pxr::TfToken interpolation)
384{
385 pxr::UsdAttribute attr_points = usd_curves.CreatePointsAttr(pxr::VtValue(), true);
386 set_attribute(attr_points, verts, time, usd_value_writer_);
387
388 pxr::UsdAttribute attr_vertex_counts = usd_curves.CreateCurveVertexCountsAttr(pxr::VtValue(),
389 true);
390 set_attribute(attr_vertex_counts, control_point_counts, time, usd_value_writer_);
391
392 if (!widths.empty()) {
393 pxr::UsdAttribute attr_widths = usd_curves.CreateWidthsAttr(pxr::VtValue(), true);
394 set_attribute(attr_widths, widths, time, usd_value_writer_);
395
396 usd_curves.SetWidthsInterpolation(interpolation);
397 }
398}
399
400static std::optional<pxr::TfToken> convert_blender_domain_to_usd(
401 const bke::AttrDomain blender_domain, bool is_bezier)
402{
403 switch (blender_domain) {
405 return is_bezier ? pxr::UsdGeomTokens->varying : pxr::UsdGeomTokens->vertex;
407 return pxr::UsdGeomTokens->uniform;
408
409 default:
410 return std::nullopt;
411 }
412}
413
414/* Excluded attributes are those which are handled through native USD concepts
415 * and should not be exported as generic attributes. */
417{
418 static const Set<StringRefNull> excluded_attrs = []() {
420 set.add_new("position");
421 set.add_new("radius");
422 set.add_new("resolution");
423 set.add_new("id");
424 set.add_new("cyclic");
425 set.add_new("curve_type");
426 set.add_new("normal_mode");
427 set.add_new("handle_left");
428 set.add_new("handle_right");
429 set.add_new("handle_type_left");
430 set.add_new("handle_type_right");
431 set.add_new("knots_mode");
432 set.add_new("nurbs_order");
433 set.add_new("nurbs_weight");
434 set.add_new("velocity");
435 return set;
436 }();
437
438 return excluded_attrs.contains(name);
439}
440
441void USDCurvesWriter::write_generic_data(const bke::CurvesGeometry &curves,
442 const bke::AttributeIter &attr,
443 const pxr::UsdGeomCurves &usd_curves)
444{
445 const CurveType curve_type = CurveType(curves.curve_types().first());
446 const bool is_bezier = curve_type == CURVE_TYPE_BEZIER;
447
448 const std::optional<pxr::TfToken> pv_interp = convert_blender_domain_to_usd(attr.domain,
449 is_bezier);
450 const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(attr.data_type);
451
452 if (!pv_interp || !pv_type) {
453 BKE_reportf(this->reports(),
455 "Attribute '%s' (Blender domain %d, type %d) cannot be converted to USD",
456 attr.name.c_str(),
457 int8_t(attr.domain),
458 int(attr.data_type));
459 return;
460 }
461
462 const GVArray attribute = *attr.get();
463 if (attribute.is_empty()) {
464 return;
465 }
466
467 const pxr::UsdTimeCode time = get_export_time_code();
468 const pxr::TfToken pv_name(
470 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
471
472 pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type, *pv_interp);
473
474 copy_blender_attribute_to_primvar(attribute, attr.data_type, time, pv_attr, usd_value_writer_);
475}
476
477void USDCurvesWriter::write_uv_data(const bke::AttributeIter &attr,
478 const pxr::UsdGeomCurves &usd_curves)
479{
480 const VArray<float2> buffer = *attr.get<float2>(bke::AttrDomain::Curve);
481 if (buffer.is_empty()) {
482 return;
483 }
484
485 const pxr::UsdTimeCode time = get_export_time_code();
486 const pxr::TfToken pv_name(
487 make_safe_name(attr.name, usd_export_context_.export_params.allow_unicode));
488 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
489
490 pxr::UsdGeomPrimvar pv_uv = pv_api.CreatePrimvar(
491 pv_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->uniform);
492
494}
495
496void USDCurvesWriter::write_velocities(const bke::CurvesGeometry &curves,
497 const pxr::UsdGeomCurves &usd_curves)
498{
499 const VArraySpan velocity = *curves.attributes().lookup<float3>("velocity",
501 if (velocity.is_empty()) {
502 return;
503 }
504
505 /* Export per-vertex velocity vectors. */
506 Span<pxr::GfVec3f> data = velocity.cast<pxr::GfVec3f>();
507 pxr::VtVec3fArray usd_velocities;
508 usd_velocities.assign(data.begin(), data.end());
509
510 pxr::UsdTimeCode time = get_export_time_code();
511 pxr::UsdAttribute attr_vel = usd_curves.CreateVelocitiesAttr(pxr::VtValue(), true);
512 set_attribute(attr_vel, usd_velocities, time, usd_value_writer_);
513}
514
515void USDCurvesWriter::write_custom_data(const bke::CurvesGeometry &curves,
516 const pxr::UsdGeomCurves &usd_curves)
517{
518 const bke::AttributeAccessor attributes = curves.attributes();
519
520 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
521 /* Skip "internal" Blender properties and attributes dealt with elsewhere. */
522 if (iter.name[0] == '.' || bke::attribute_name_is_anonymous(iter.name) ||
523 is_excluded_attr(iter.name))
524 {
525 return;
526 }
527
528 /* Spline UV data */
529 if (iter.domain == bke::AttrDomain::Curve && iter.data_type == bke::AttrType::Float2) {
530 if (usd_export_context_.export_params.export_uvmaps) {
531 this->write_uv_data(iter, usd_curves);
532 }
533 }
534
535 /* Everything else. */
536 else {
537 this->write_generic_data(curves, iter, usd_curves);
538 }
539 });
540}
541
543{
544 Curves *curves_id;
545 std::unique_ptr<Curves, std::function<void(Curves *)>> converted_curves;
546
547 switch (context.object->type) {
548 case OB_CURVES_LEGACY: {
549 const Curve *legacy_curve = static_cast<Curve *>(context.object->data);
550 converted_curves = std::unique_ptr<Curves, std::function<void(Curves *)>>(
551 bke::curve_legacy_to_curves(*legacy_curve), [](Curves *c) { BKE_id_free(nullptr, c); });
552 curves_id = converted_curves.get();
553 break;
554 }
555 case OB_CURVES:
556 curves_id = static_cast<Curves *>(context.object->data);
557 break;
558 default:
560 return;
561 }
562
563 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
564 if (curves.is_empty()) {
565 return;
566 }
567
568 const std::array<int, CURVE_TYPES_NUM> &curve_type_counts = curves.curve_type_counts();
569 const int number_of_curve_types = std::count_if(curve_type_counts.begin(),
570 curve_type_counts.end(),
571 [](const int count) { return count > 0; });
572 if (number_of_curve_types > 1) {
574 reports(), RPT_WARNING, "Cannot export mixed curve types in the same Curves object");
575 return;
576 }
577
581 "Cannot export mixed cyclic and non-cyclic curves in the same Curves object");
582 return;
583 }
584
585 const pxr::UsdTimeCode time = get_export_time_code();
586 const int8_t curve_type = curves.curve_types()[0];
587
588 if (first_frame_curve_type == -1) {
589 first_frame_curve_type = curve_type;
590 }
591 else if (first_frame_curve_type != curve_type) {
592 const char *first_frame_curve_type_name = nullptr;
594 rna_enum_curves_type_items, int(first_frame_curve_type), &first_frame_curve_type_name);
595
596 const char *current_curve_type_name = nullptr;
598 rna_enum_curves_type_items, int(curve_type), &current_curve_type_name);
599
602 "USD does not support animating curve types. The curve type changes from %s to "
603 "%s on frame %f",
604 IFACE_(first_frame_curve_type_name),
605 IFACE_(current_curve_type_name),
606 time.GetValue());
607 return;
608 }
609
610 const bool is_cyclic = curves.cyclic().first();
611 pxr::VtArray<pxr::GfVec3f> verts;
612 pxr::VtIntArray control_point_counts;
613 pxr::VtArray<float> widths;
614 pxr::TfToken interpolation;
615
616 pxr::UsdGeomBasisCurves usd_basis_curves;
617 pxr::UsdGeomNurbsCurves usd_nurbs_curves;
618 pxr::UsdGeomCurves *usd_curves = nullptr;
619
620 control_point_counts.resize(curves.curves_num());
621 switch (curve_type) {
622 case CURVE_TYPE_POLY:
623 usd_basis_curves = DefineUsdGeomBasisCurves(pxr::VtValue(), is_cyclic, false);
624 usd_curves = &usd_basis_curves;
625
627 curves, verts, control_point_counts, widths, interpolation, is_cyclic, false, reports());
628 break;
630 usd_basis_curves = DefineUsdGeomBasisCurves(
631 pxr::VtValue(pxr::UsdGeomTokens->catmullRom), is_cyclic, true);
632 usd_curves = &usd_basis_curves;
633
635 curves, verts, control_point_counts, widths, interpolation, is_cyclic, true, reports());
636 break;
638 usd_basis_curves = DefineUsdGeomBasisCurves(
639 pxr::VtValue(pxr::UsdGeomTokens->bezier), is_cyclic, true);
640 usd_curves = &usd_basis_curves;
641
643 curves, verts, control_point_counts, widths, interpolation, is_cyclic, reports());
644 break;
645 case CURVE_TYPE_NURBS: {
646 pxr::VtArray<double> knots;
647 pxr::VtArray<double> weights;
648 pxr::VtArray<int> orders;
649
650 usd_nurbs_curves = pxr::UsdGeomNurbsCurves::Define(usd_export_context_.stage,
651 usd_export_context_.usd_path);
652 usd_curves = &usd_nurbs_curves;
653
655 verts,
656 control_point_counts,
657 widths,
658 knots,
659 weights,
660 orders,
661 interpolation,
662 is_cyclic);
663
664 set_writer_attributes_for_nurbs(usd_nurbs_curves, knots, weights, orders, time);
665
666 break;
667 }
668 default:
670 }
671
672 this->set_writer_attributes(
673 *usd_curves, verts, control_point_counts, widths, time, interpolation);
674
675 this->assign_materials(context, *usd_curves);
676
677 /* TODO: We cannot write custom privars for cyclic NURBS curves at the moment. */
678 if (!is_cyclic || (is_cyclic && curve_type != CURVE_TYPE_NURBS)) {
679 this->write_velocities(curves, *usd_curves);
680 this->write_custom_data(curves, *usd_curves);
681 }
682
683 auto prim = usd_curves->GetPrim();
684 write_id_properties(prim, curves_id->id, time);
685
686 this->author_extent(*usd_curves, curves.bounds_min_max(), time);
687}
688
690 const pxr::UsdGeomCurves &usd_curves)
691{
692 if (context.object->totcol == 0) {
693 return;
694 }
695
696 bool curve_material_bound = false;
697 for (int mat_num = 0; mat_num < context.object->totcol; mat_num++) {
698 Material *material = BKE_object_material_get(context.object, mat_num + 1);
699 if (material == nullptr) {
700 continue;
701 }
702
703 pxr::UsdPrim curve_prim = usd_curves.GetPrim();
704 pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(curve_prim);
705 pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
706 api.Bind(usd_material);
707 pxr::UsdShadeMaterialBindingAPI::Apply(curve_prim);
708
709 /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
710 * use the flag from the first non-empty material slot. */
711 usd_curves.CreateDoubleSidedAttr(
712 pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
713
714 curve_material_bound = true;
715 break;
716 }
717
718 if (!curve_material_bound) {
719 /* Blender defaults to double-sided, but USD to single-sided. */
720 usd_curves.CreateDoubleSidedAttr(pxr::VtValue(true));
721 }
722}
723
724} // namespace blender::io::usd
Low-level operations for curves.
void BKE_id_free(Main *bmain, void *idv)
General operations, lookup, etc. for materials.
Material * BKE_object_material_get(Object *ob, short act)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define IFACE_(msgid)
KnotsMode
@ MA_BL_CULL_BACKFACE
@ OB_CURVES_LEGACY
@ OB_CURVES
BMesh const char void * data
constexpr int64_t size() const
constexpr IndexRange take_front(int64_t n) const
bool contains(const Key &key) const
Definition BLI_set.hh:310
void add_new(const Key &key)
Definition BLI_set.hh:233
constexpr const char * c_str() const
IndexRange index_range() const
GAttributeReader get() const
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
const std::array< int, CURVE_TYPES_NUM > & curve_type_counts() const
VArray< float > radius() const
std::optional< Span< float > > nurbs_weights() const
Span< float > nurbs_custom_knots() const
VArray< int8_t > nurbs_knots_modes() const
std::optional< Span< float3 > > handle_positions_left() const
Span< float3 > positions() const
std::optional< Span< float3 > > handle_positions_right() const
std::optional< Bounds< float3 > > bounds_min_max(bool use_radius=true) const
OffsetIndices< int > nurbs_custom_knots_by_curve() const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
VArray< int8_t > nurbs_orders() const
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material) const
void author_extent(const pxr::UsdGeomBoundable &boundable, const pxr::UsdTimeCode time)
pxr::UsdTimeCode get_export_time_code() const
pxr::UsdUtilsSparseValueWriter usd_value_writer_
void write_id_properties(const pxr::UsdPrim &prim, const ID &id, pxr::UsdTimeCode=pxr::UsdTimeCode::Default()) const
const USDExporterContext usd_export_context_
void do_write(HierarchyContext &context) override
void assign_materials(const HierarchyContext &context, const pxr::UsdGeomCurves &usd_curves)
static bool is_cyclic(const Nurb *nu)
static float verts[][3]
int count
BooleanMix booleans_mix_calc(const VArray< bool > &varray, IndexRange range_to_check)
int knots_num(int points_num, int8_t order, bool cyclic)
void load_curve_knots(KnotsMode mode, int points_num, int8_t order, bool cyclic, IndexRange curve_knots, Span< float > custom_knots, MutableSpan< float > knots)
bool attribute_name_is_anonymous(const StringRef name)
Curves * curve_legacy_to_curves(const Curve &curve_legacy)
static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray< float > &widths, const pxr::VtArray< int > &segments, const pxr::VtIntArray &control_point_counts, const bool is_cyclic, ReportList *reports)
void copy_blender_attribute_to_primvar(const GVArray &attribute, const bke::AttrType data_type, const pxr::UsdTimeCode time, const pxr::UsdGeomPrimvar &primvar, pxr::UsdUtilsSparseValueWriter &value_writer)
static bool is_excluded_attr(StringRefNull name)
std::optional< pxr::SdfValueTypeName > convert_blender_type_to_usd(const bke::AttrType blender_type, bool use_color3f_type)
void copy_blender_buffer_to_primvar(const VArray< BlenderT > &buffer, const pxr::UsdTimeCode time, const pxr::UsdGeomPrimvar &primvar, pxr::UsdUtilsSparseValueWriter &value_writer)
static void populate_curve_props_for_bezier(const bke::CurvesGeometry &curves, pxr::VtArray< pxr::GfVec3f > &verts, pxr::VtIntArray &control_point_counts, pxr::VtArray< float > &widths, pxr::TfToken &interpolation, const bool is_cyclic, ReportList *reports)
static std::optional< pxr::TfToken > convert_blender_domain_to_usd(const bke::AttrDomain blender_domain, bool is_bezier)
static void populate_curve_verts(const bke::CurvesGeometry &curves, const Span< float3 > positions, pxr::VtArray< pxr::GfVec3f > &verts, pxr::VtIntArray &control_point_counts, pxr::VtArray< int > &segments, const bool is_cyclic, const bool is_cubic)
static void populate_curve_props(const bke::CurvesGeometry &curves, pxr::VtArray< pxr::GfVec3f > &verts, pxr::VtIntArray &control_point_counts, pxr::VtArray< float > &widths, pxr::TfToken &interpolation, const bool is_cyclic, const bool is_cubic, ReportList *reports)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
static void populate_curve_verts_for_bezier(const bke::CurvesGeometry &curves, const Span< float3 > positions, const Span< float3 > handles_l, const Span< float3 > handles_r, pxr::VtArray< pxr::GfVec3f > &verts, pxr::VtIntArray &control_point_counts, pxr::VtArray< int > &segments, const bool is_cyclic)
void set_attribute(const pxr::UsdAttribute &attr, const USDT value, pxr::UsdTimeCode time, pxr::UsdUtilsSparseValueWriter &value_writer)
static void populate_curve_widths(const bke::CurvesGeometry &curves, pxr::VtArray< float > &widths)
static void populate_curve_props_for_nurbs(const bke::CurvesGeometry &curves, pxr::VtArray< pxr::GfVec3f > &verts, pxr::VtIntArray &control_point_counts, pxr::VtArray< float > &widths, pxr::VtArray< double > &knots, pxr::VtArray< double > &weights, pxr::VtArray< int > &orders, pxr::TfToken &interpolation, const bool is_cyclic)
VecBase< float, 2 > float2
VecBase< float, 3 > float3
const char * name
bool RNA_enum_name_from_value(const EnumPropertyItem *item, int value, const char **r_name)
const EnumPropertyItem rna_enum_curves_type_items[]
Definition rna_curves.cc:24
CurvesGeometry geometry
i
Definition text_draw.cc:230