Blender V4.5
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,
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,
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,
251{
252 const int num_curves = curves.curve_num;
253
254 const Span<float3> positions = curves.positions();
255 const Span<float3> handles_l = curves.handle_positions_left();
256 const 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<int> &orders,
274 pxr::TfToken &interpolation,
275 const bool is_cyclic)
276{
277 /* Order and range, when representing a batched NurbsCurve should be authored one value per
278 * curve. */
279 const int num_curves = curves.curve_num;
280 orders.resize(num_curves);
281
282 const Span<float3> positions = curves.positions();
283 const Span<float> custom_knots = curves.nurbs_custom_knots();
284
285 VArray<int8_t> geom_orders = curves.nurbs_orders();
286 VArray<int8_t> knots_modes = curves.nurbs_knots_modes();
287
288 const OffsetIndices points_by_curve = curves.points_by_curve();
289 const OffsetIndices custom_knots_by_curve = curves.nurbs_custom_knots_by_curve();
290 for (const int i_curve : curves.curves_range()) {
291 const IndexRange points = points_by_curve[i_curve];
292 for (const int i_point : points) {
293 verts.push_back(
294 pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
295 }
296
297 const int tot_points = points.size();
298 control_point_counts[i_curve] = tot_points;
299
300 const int8_t order = geom_orders[i_curve];
301 orders[i_curve] = int(geom_orders[i_curve]);
302
303 const KnotsMode mode = KnotsMode(knots_modes[i_curve]);
304
305 const int knots_num = bke::curves::nurbs::knots_num(tot_points, order, is_cyclic);
306 Array<float> temp_knots(knots_num);
308 tot_points,
309 order,
310 is_cyclic,
311 custom_knots_by_curve[i_curve],
312 custom_knots,
313 temp_knots);
314
315 /* Knots should be the concatenation of all batched curves.
316 * https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
317 for (int i_knot = 0; i_knot < knots_num; i_knot++) {
318 knots.push_back(double(temp_knots[i_knot]));
319 }
320
321 /* For USD it is required to set specific end knots for periodic/non-periodic curves
322 * https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
323 int zeroth_knot_index = knots.size() - knots_num;
324 if (is_cyclic) {
325 knots[zeroth_knot_index] = knots[zeroth_knot_index + 1] -
326 (knots[knots.size() - 2] - knots[knots.size() - 3]);
327 knots[knots.size() - 1] = knots[knots.size() - 2] +
328 (knots[zeroth_knot_index + 2] - knots[zeroth_knot_index + 1]);
329 }
330 else {
331 knots[zeroth_knot_index] = knots[zeroth_knot_index + 1];
332 knots[knots.size() - 1] = knots[knots.size() - 2];
333 }
334 }
335
336 populate_curve_widths(curves, widths);
337 interpolation = pxr::UsdGeomTokens->vertex;
338}
339
340void USDCurvesWriter::set_writer_attributes_for_nurbs(
341 const pxr::UsdGeomNurbsCurves &usd_nurbs_curves,
342 const pxr::VtArray<double> &knots,
343 const pxr::VtArray<int> &orders,
344 const pxr::UsdTimeCode timecode)
345{
346 pxr::UsdAttribute attr_knots = usd_nurbs_curves.CreateKnotsAttr(pxr::VtValue(), true);
347 usd_value_writer_.SetAttribute(attr_knots, pxr::VtValue(knots), timecode);
348 pxr::UsdAttribute attr_order = usd_nurbs_curves.CreateOrderAttr(pxr::VtValue(), true);
349 usd_value_writer_.SetAttribute(attr_order, pxr::VtValue(orders), timecode);
350}
351
352void USDCurvesWriter::set_writer_attributes(pxr::UsdGeomCurves &usd_curves,
353 pxr::VtArray<pxr::GfVec3f> &verts,
354 pxr::VtIntArray &control_point_counts,
355 pxr::VtArray<float> &widths,
356 const pxr::UsdTimeCode timecode,
357 const pxr::TfToken interpolation)
358{
359 pxr::UsdAttribute attr_points = usd_curves.CreatePointsAttr(pxr::VtValue(), true);
360 set_attribute(attr_points, verts, timecode, usd_value_writer_);
361
362 pxr::UsdAttribute attr_vertex_counts = usd_curves.CreateCurveVertexCountsAttr(pxr::VtValue(),
363 true);
364 set_attribute(attr_vertex_counts, control_point_counts, timecode, usd_value_writer_);
365
366 if (!widths.empty()) {
367 pxr::UsdAttribute attr_widths = usd_curves.CreateWidthsAttr(pxr::VtValue(), true);
368 set_attribute(attr_widths, widths, timecode, usd_value_writer_);
369
370 usd_curves.SetWidthsInterpolation(interpolation);
371 }
372}
373
374static std::optional<pxr::TfToken> convert_blender_domain_to_usd(
375 const bke::AttrDomain blender_domain, bool is_bezier)
376{
377 switch (blender_domain) {
379 return is_bezier ? pxr::UsdGeomTokens->varying : pxr::UsdGeomTokens->vertex;
381 return pxr::UsdGeomTokens->uniform;
382
383 default:
384 return std::nullopt;
385 }
386}
387
388/* Excluded attributes are those which are handled through native USD concepts
389 * and should not be exported as generic attributes. */
391{
392 static const Set<StringRefNull> excluded_attrs = []() {
394 set.add_new("position");
395 set.add_new("radius");
396 set.add_new("resolution");
397 set.add_new("id");
398 set.add_new("cyclic");
399 set.add_new("curve_type");
400 set.add_new("normal_mode");
401 set.add_new("handle_left");
402 set.add_new("handle_right");
403 set.add_new("handle_type_left");
404 set.add_new("handle_type_right");
405 set.add_new("knots_mode");
406 set.add_new("nurbs_order");
407 set.add_new("nurbs_weight");
408 set.add_new("velocity");
409 return set;
410 }();
411
412 return excluded_attrs.contains(name);
413}
414
415void USDCurvesWriter::write_generic_data(const bke::CurvesGeometry &curves,
416 const bke::AttributeIter &attr,
417 const pxr::UsdGeomCurves &usd_curves)
418{
419 const CurveType curve_type = CurveType(curves.curve_types().first());
420 const bool is_bezier = curve_type == CURVE_TYPE_BEZIER;
421
422 const std::optional<pxr::TfToken> pv_interp = convert_blender_domain_to_usd(attr.domain,
423 is_bezier);
424 const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(attr.data_type);
425
426 if (!pv_interp || !pv_type) {
427 BKE_reportf(this->reports(),
429 "Attribute '%s' (Blender domain %d, type %d) cannot be converted to USD",
430 attr.name.c_str(),
431 int8_t(attr.domain),
432 attr.data_type);
433 return;
434 }
435
436 const GVArray attribute = *attr.get();
437 if (attribute.is_empty()) {
438 return;
439 }
440
441 const pxr::UsdTimeCode timecode = get_export_time_code();
442 const pxr::TfToken pv_name(
444 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
445
446 pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type, *pv_interp);
447
449 attribute, attr.data_type, timecode, pv_attr, usd_value_writer_);
450}
451
452void USDCurvesWriter::write_uv_data(const bke::AttributeIter &attr,
453 const pxr::UsdGeomCurves &usd_curves)
454{
455 const VArray<float2> buffer = *attr.get<float2>(bke::AttrDomain::Curve);
456 if (buffer.is_empty()) {
457 return;
458 }
459
460 const pxr::UsdTimeCode timecode = get_export_time_code();
461 const pxr::TfToken pv_name(
462 make_safe_name(attr.name, usd_export_context_.export_params.allow_unicode));
463 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
464
465 pxr::UsdGeomPrimvar pv_uv = pv_api.CreatePrimvar(
466 pv_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->uniform);
467
469}
470
471void USDCurvesWriter::write_velocities(const bke::CurvesGeometry &curves,
472 const pxr::UsdGeomCurves &usd_curves)
473{
474 const VArraySpan velocity = *curves.attributes().lookup<float3>("velocity",
476 if (velocity.is_empty()) {
477 return;
478 }
479
480 /* Export per-vertex velocity vectors. */
481 Span<pxr::GfVec3f> data = velocity.cast<pxr::GfVec3f>();
482 pxr::VtVec3fArray usd_velocities;
483 usd_velocities.assign(data.begin(), data.end());
484
485 pxr::UsdTimeCode timecode = get_export_time_code();
486 pxr::UsdAttribute attr_vel = usd_curves.CreateVelocitiesAttr(pxr::VtValue(), true);
487 set_attribute(attr_vel, usd_velocities, timecode, usd_value_writer_);
488}
489
490void USDCurvesWriter::write_custom_data(const bke::CurvesGeometry &curves,
491 const pxr::UsdGeomCurves &usd_curves)
492{
493 const bke::AttributeAccessor attributes = curves.attributes();
494
495 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
496 /* Skip "internal" Blender properties and attributes dealt with elsewhere. */
497 if (iter.name[0] == '.' || bke::attribute_name_is_anonymous(iter.name) ||
498 is_excluded_attr(iter.name))
499 {
500 return;
501 }
502
503 /* Spline UV data */
504 if (iter.domain == bke::AttrDomain::Curve && iter.data_type == CD_PROP_FLOAT2) {
505 if (usd_export_context_.export_params.export_uvmaps) {
506 this->write_uv_data(iter, usd_curves);
507 }
508 }
509
510 /* Everything else. */
511 else {
512 this->write_generic_data(curves, iter, usd_curves);
513 }
514 });
515}
516
518{
519 Curves *curves_id;
520 std::unique_ptr<Curves, std::function<void(Curves *)>> converted_curves;
521
522 switch (context.object->type) {
523 case OB_CURVES_LEGACY: {
524 const Curve *legacy_curve = static_cast<Curve *>(context.object->data);
525 converted_curves = std::unique_ptr<Curves, std::function<void(Curves *)>>(
526 bke::curve_legacy_to_curves(*legacy_curve), [](Curves *c) { BKE_id_free(nullptr, c); });
527 curves_id = converted_curves.get();
528 break;
529 }
530 case OB_CURVES:
531 curves_id = static_cast<Curves *>(context.object->data);
532 break;
533 default:
535 return;
536 }
537
538 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
539 if (curves.is_empty()) {
540 return;
541 }
542
543 const std::array<int, CURVE_TYPES_NUM> &curve_type_counts = curves.curve_type_counts();
544 const int number_of_curve_types = std::count_if(curve_type_counts.begin(),
545 curve_type_counts.end(),
546 [](const int count) { return count > 0; });
547 if (number_of_curve_types > 1) {
549 reports(), RPT_WARNING, "Cannot export mixed curve types in the same Curves object");
550 return;
551 }
552
556 "Cannot export mixed cyclic and non-cyclic curves in the same Curves object");
557 return;
558 }
559
560 const pxr::UsdTimeCode timecode = get_export_time_code();
561 const int8_t curve_type = curves.curve_types()[0];
562
563 if (first_frame_curve_type == -1) {
564 first_frame_curve_type = curve_type;
565 }
566 else if (first_frame_curve_type != curve_type) {
567 const char *first_frame_curve_type_name = nullptr;
569 rna_enum_curves_type_items, int(first_frame_curve_type), &first_frame_curve_type_name);
570
571 const char *current_curve_type_name = nullptr;
573 rna_enum_curves_type_items, int(curve_type), &current_curve_type_name);
574
577 "USD does not support animating curve types. The curve type changes from %s to "
578 "%s on frame %f",
579 IFACE_(first_frame_curve_type_name),
580 IFACE_(current_curve_type_name),
581 timecode.GetValue());
582 return;
583 }
584
585 const bool is_cyclic = curves.cyclic().first();
586 pxr::VtArray<pxr::GfVec3f> verts;
587 pxr::VtIntArray control_point_counts;
588 pxr::VtArray<float> widths;
589 pxr::TfToken interpolation;
590
591 pxr::UsdGeomBasisCurves usd_basis_curves;
592 pxr::UsdGeomNurbsCurves usd_nurbs_curves;
593 pxr::UsdGeomCurves *usd_curves = nullptr;
594
595 control_point_counts.resize(curves.curves_num());
596 switch (curve_type) {
597 case CURVE_TYPE_POLY:
598 usd_basis_curves = DefineUsdGeomBasisCurves(pxr::VtValue(), is_cyclic, false);
599 usd_curves = &usd_basis_curves;
600
602 curves, verts, control_point_counts, widths, interpolation, is_cyclic, false, reports());
603 break;
605 usd_basis_curves = DefineUsdGeomBasisCurves(
606 pxr::VtValue(pxr::UsdGeomTokens->catmullRom), is_cyclic, true);
607 usd_curves = &usd_basis_curves;
608
610 curves, verts, control_point_counts, widths, interpolation, is_cyclic, true, reports());
611 break;
613 usd_basis_curves = DefineUsdGeomBasisCurves(
614 pxr::VtValue(pxr::UsdGeomTokens->bezier), is_cyclic, true);
615 usd_curves = &usd_basis_curves;
616
618 curves, verts, control_point_counts, widths, interpolation, is_cyclic, reports());
619 break;
620 case CURVE_TYPE_NURBS: {
621 pxr::VtArray<double> knots;
622 pxr::VtArray<int> orders;
623 orders.resize(curves.curves_num());
624
625 usd_nurbs_curves = pxr::UsdGeomNurbsCurves::Define(usd_export_context_.stage,
626 usd_export_context_.usd_path);
627 usd_curves = &usd_nurbs_curves;
628
630 curves, verts, control_point_counts, widths, knots, orders, interpolation, is_cyclic);
631
632 set_writer_attributes_for_nurbs(usd_nurbs_curves, knots, orders, timecode);
633
634 break;
635 }
636 default:
638 }
639
640 this->set_writer_attributes(
641 *usd_curves, verts, control_point_counts, widths, timecode, interpolation);
642
643 this->assign_materials(context, *usd_curves);
644
645 this->write_velocities(curves, *usd_curves);
646 this->write_custom_data(curves, *usd_curves);
647
648 auto prim = usd_curves->GetPrim();
649 write_id_properties(prim, curves_id->id, timecode);
650
651 this->author_extent(*usd_curves, curves.bounds_min_max(), timecode);
652}
653
655 const pxr::UsdGeomCurves &usd_curves)
656{
657 if (context.object->totcol == 0) {
658 return;
659 }
660
661 bool curve_material_bound = false;
662 for (int mat_num = 0; mat_num < context.object->totcol; mat_num++) {
663 Material *material = BKE_object_material_get(context.object, mat_num + 1);
664 if (material == nullptr) {
665 continue;
666 }
667
668 pxr::UsdPrim curve_prim = usd_curves.GetPrim();
669 pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(curve_prim);
670 pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
671 api.Bind(usd_material);
672 pxr::UsdShadeMaterialBindingAPI::Apply(curve_prim);
673
674 /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
675 * use the flag from the first non-empty material slot. */
676 usd_curves.CreateDoubleSidedAttr(
677 pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
678
679 curve_material_bound = true;
680 break;
681 }
682
683 if (!curve_material_bound) {
684 /* Blender defaults to double-sided, but USD to single-sided. */
685 usd_curves.CreateDoubleSidedAttr(pxr::VtValue(true));
686 }
687}
688
689} // 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
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define IFACE_(msgid)
CurveType
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CURVE_TYPE_CATMULL_ROM
KnotsMode
@ CD_PROP_FLOAT2
@ MA_BL_CULL_BACKFACE
@ OB_CURVES_LEGACY
@ OB_CURVES
ReportList * reports
Definition WM_types.hh:1025
BMesh const char void * data
constexpr int64_t size() 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
Span< float > nurbs_custom_knots() const
Span< float3 > handle_positions_left() const
VArray< int8_t > nurbs_knots_modes() const
Span< float3 > positions() const
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 timecode)
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)
static bool is_excluded_attr(StringRefNull name)
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< int > &orders, pxr::TfToken &interpolation, const bool is_cyclic)
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)
void set_attribute(const pxr::UsdAttribute &attr, const USDT value, pxr::UsdTimeCode timecode, pxr::UsdUtilsSparseValueWriter &value_writer)
void copy_blender_attribute_to_primvar(const GVArray &attribute, const eCustomDataType data_type, const pxr::UsdTimeCode timecode, const pxr::UsdGeomPrimvar &primvar, pxr::UsdUtilsSparseValueWriter &value_writer)
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)
static void populate_curve_widths(const bke::CurvesGeometry &curves, pxr::VtArray< float > &widths)
std::optional< pxr::SdfValueTypeName > convert_blender_type_to_usd(const eCustomDataType blender_type, bool use_color3f_type)
void copy_blender_buffer_to_primvar(const VArray< BlenderT > &buffer, const pxr::UsdTimeCode timecode, const pxr::UsdGeomPrimvar &primvar, pxr::UsdUtilsSparseValueWriter &value_writer)
VecBase< float, 2 > float2
VecBase< float, 3 > float3
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:22
CurvesGeometry geometry
i
Definition text_draw.cc:230