Blender V5.0
abc_writer_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
8
9#include <functional>
10#include <memory>
11
12#include "abc_writer_curves.h"
14
15#include "BLI_array_utils.hh"
16#include "BLI_offset_indices.hh"
17
18#include "DNA_curve_types.h"
19#include "DNA_object_types.h"
20
22#include "BKE_curve_to_mesh.hh"
23#include "BKE_curves.hh"
24#include "BKE_lib_id.hh"
25#include "BKE_mesh.hh"
26#include "BKE_object.hh"
27
28#include "CLG_log.h"
29static CLG_LogRef LOG = {"io.alembic"};
30
31using Alembic::AbcGeom::OCompoundProperty;
32using Alembic::AbcGeom::OCurves;
33using Alembic::AbcGeom::OCurvesSchema;
34using Alembic::AbcGeom::OInt16Property;
35using Alembic::AbcGeom::ON3fGeomParam;
36using Alembic::AbcGeom::OV2fGeomParam;
37
38namespace blender::io::alembic {
39
40const std::string ABC_CURVE_RESOLUTION_U_PROPNAME("blender:resolution");
41
42static inline Imath::V3f to_yup_V3f(float3 v)
43{
44 Imath::V3f p;
45 copy_yup_from_zup(p.getValue(), v);
46 return p;
47}
48
50
52{
53 CLOG_DEBUG(&LOG, "exporting %s", args_.abc_path.c_str());
54 abc_curve_ = OCurves(args_.abc_parent, args_.abc_name, timesample_index_);
55 abc_curve_schema_ = abc_curve_.getSchema();
56
57 /* TODO: Blender supports per-curve resolutions but we're only using the first curve's data
58 * here. Investigate using OInt16ArrayProperty to write out all the data but do so efficiently.
59 * e.g. Write just a single value if all curves share the same resolution etc. */
60
61 int resolution_u = 1;
62 switch (context->object->type) {
63 case OB_CURVES_LEGACY: {
64 Curve *curves_id = static_cast<Curve *>(context->object->data);
65 resolution_u = curves_id->resolu;
66 break;
67 }
68 case OB_CURVES: {
69 Curves *curves_id = static_cast<Curves *>(context->object->data);
70 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
71 resolution_u = curves.resolution().first();
72 break;
73 }
74 }
75
76 OCompoundProperty user_props = abc_curve_schema_.getUserProperties();
77 OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME);
78 user_prop_resolu.set(resolution_u);
79}
80
81Alembic::Abc::OObject ABCCurveWriter::get_alembic_object() const
82{
83 return abc_curve_;
84}
85
86Alembic::Abc::OCompoundProperty ABCCurveWriter::abc_prop_for_custom_props()
87{
88 return abc_schema_prop_for_custom_props(abc_curve_schema_);
89}
90
92{
93 const Curves *curves_id;
94 std::unique_ptr<Curves, std::function<void(Curves *)>> converted_curves;
95
96 switch (context.object->type) {
97 case OB_CURVES_LEGACY: {
98 const Curve *legacy_curve = static_cast<Curve *>(context.object->data);
99 converted_curves = std::unique_ptr<Curves, std::function<void(Curves *)>>(
100 bke::curve_legacy_to_curves(*legacy_curve), [](Curves *c) { BKE_id_free(nullptr, c); });
101 curves_id = converted_curves.get();
102 break;
103 }
104 case OB_CURVES:
105 curves_id = static_cast<Curves *>(context.object->data);
106 break;
107 default:
109 return;
110 }
111
112 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
113 if (curves.is_empty()) {
114 return;
115 }
116
117 /* Alembic only supports 1 curve type / periodicity combination per object. Enforce this here.
118 * See: Alembic source code for OCurves.h as no documentation explicitly exists for this. */
119 const std::array<int, CURVE_TYPES_NUM> &curve_type_counts = curves.curve_type_counts();
120 const int number_of_curve_types = std::count_if(curve_type_counts.begin(),
121 curve_type_counts.end(),
122 [](const int count) { return count > 0; });
123 if (number_of_curve_types > 1) {
124 CLOG_WARN(&LOG, "Cannot export mixed curve types in the same Curves object");
125 return;
126 }
127
129 CLOG_WARN(&LOG, "Cannot export mixed cyclic and non-cyclic curves in the same Curves object");
130 return;
131 }
132
133 const bool is_cyclic = curves.cyclic().first();
134 Alembic::AbcGeom::BasisType curve_basis = Alembic::AbcGeom::kNoBasis;
135 Alembic::AbcGeom::CurveType curve_type = Alembic::AbcGeom::kLinear;
136 Alembic::AbcGeom::CurvePeriodicity periodicity = is_cyclic ? Alembic::AbcGeom::kPeriodic :
137 Alembic::AbcGeom::kNonPeriodic;
138 const CurveType blender_curve_type = CurveType(curves.curve_types().first());
139 switch (blender_curve_type) {
140 case CURVE_TYPE_POLY:
141 curve_basis = Alembic::AbcGeom::kNoBasis;
142 curve_type = Alembic::AbcGeom::kLinear;
143 break;
145 curve_basis = Alembic::AbcGeom::kCatmullromBasis;
146 curve_type = Alembic::AbcGeom::kLinear;
147 break;
149 curve_basis = Alembic::AbcGeom::kBezierBasis;
150 curve_type = Alembic::AbcGeom::kCubic;
151 break;
152 case CURVE_TYPE_NURBS:
153 curve_basis = Alembic::AbcGeom::kBsplineBasis;
154 curve_type = Alembic::AbcGeom::kVariableOrder;
155 break;
156 }
157
158 std::vector<Imath::V3f> verts;
159 std::vector<int32_t> vert_counts;
160 std::vector<float> widths;
161 std::vector<float> weights;
162 std::vector<float> knots;
163 std::vector<uint8_t> orders;
164
165 const Span<float3> positions = curves.positions();
166 const std::optional<Span<float>> nurbs_weights = curves.nurbs_weights();
167 const VArray<int8_t> nurbs_orders = curves.nurbs_orders();
168 const VArray<float> radii = curves.radius();
169
170 vert_counts.resize(curves.curves_num());
171 const OffsetIndices points_by_curve = curves.points_by_curve();
172 const std::optional<Span<float3>> handles_l = curves.handle_positions_left();
173 const std::optional<Span<float3>> handles_r = curves.handle_positions_right();
174 if (blender_curve_type == CURVE_TYPE_BEZIER && handles_l && handles_r) {
175
176 for (const int i_curve : curves.curves_range()) {
177 const IndexRange points = points_by_curve[i_curve];
178 const size_t current_vert_count = verts.size();
179
180 const int start_point_index = points.first();
181 const int last_point_index = points.last();
182
183 /* Vert order in the bezier curve representation is:
184 * [
185 * control point 0(+ width), right handle 0, left handle 1,
186 * control point 1(+ width), right handle 1, left handle 2,
187 * control point 2(+ width), ...
188 * ] */
189 for (const int i_point : points.drop_back(1)) {
190 verts.push_back(to_yup_V3f(positions[i_point]));
191 widths.push_back(radii[i_point] * 2.0f);
192
193 verts.push_back(to_yup_V3f((*handles_r)[i_point]));
194 verts.push_back(to_yup_V3f((*handles_l)[i_point + 1]));
195 }
196
197 /* The last vert in the array doesn't need a right handle because the curve stops
198 * at that point. */
199 verts.push_back(to_yup_V3f(positions[last_point_index]));
200 widths.push_back(radii[last_point_index] * 2.0f);
201
202 /* If the curve is cyclic, include the right handle of the last point and the
203 * left handle of the first point. */
204 if (is_cyclic) {
205 verts.push_back(to_yup_V3f((*handles_r)[last_point_index]));
206 verts.push_back(to_yup_V3f((*handles_l)[start_point_index]));
207 }
208
209 vert_counts[i_curve] = verts.size() - current_vert_count;
210 }
211 }
212 else {
213 verts.resize(curves.points_num());
214 widths.resize(curves.points_num());
215 for (const int i_point : curves.points_range()) {
216 verts[i_point] = to_yup_V3f(positions[i_point]);
217 widths[i_point] = radii[i_point] * 2.0f;
218 }
219
220 if (blender_curve_type == CURVE_TYPE_NURBS) {
221 if (nurbs_weights) {
222 weights.resize(curves.points_num());
223 std::copy_n(nurbs_weights->data(), weights.size(), weights.data());
224 }
225
226 orders.resize(curves.curves_num());
227 for (const int i_curve : curves.curves_range()) {
228 orders[i_curve] = nurbs_orders[i_curve];
229 }
230 }
231
232 offset_indices::copy_group_sizes(points_by_curve, points_by_curve.index_range(), vert_counts);
233 }
234
235 Alembic::AbcGeom::OFloatGeomParam::Sample width_sample;
236 width_sample.setVals(widths);
237
238 OCurvesSchema::Sample sample(verts,
239 vert_counts,
240 curve_type,
241 periodicity,
242 width_sample,
243 OV2fGeomParam::Sample(), /* UVs */
244 ON3fGeomParam::Sample(), /* normals */
245 curve_basis,
246 weights,
247 orders,
248 knots);
249
250 update_bounding_box(context.object);
251 sample.setSelfBounds(bounding_box_);
252 abc_curve_schema_.set(sample);
253}
254
259
260Mesh *ABCCurveMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
261{
262 switch (object_eval->type) {
263 case OB_CURVES_LEGACY: {
264 Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
265 if (mesh_eval != nullptr) {
266 /* Mesh_eval only exists when generative modifiers are in use. */
267 r_needsfree = false;
268 return mesh_eval;
269 }
270
271 r_needsfree = true;
272 return BKE_mesh_new_nomain_from_curve(object_eval);
273 }
274
275 case OB_CURVES:
276 Curves *curves = static_cast<Curves *>(object_eval->data);
277 r_needsfree = true;
278 return bke::curve_to_wire_mesh(curves->geometry.wrap());
279 }
280
281 return nullptr;
282}
283
284} // namespace blender::io::alembic
Low-level operations for curves.
void BKE_id_free(Main *bmain, void *idv)
Mesh * BKE_mesh_new_nomain_from_curve(const Object *ob)
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define CLOG_DEBUG(clg_ref,...)
Definition CLG_log.h:191
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
Object is a sort of wrapper for general info.
@ OB_CURVES_LEGACY
@ OB_CURVES
ATTR_WARN_UNUSED_RESULT const BMVert * v
constexpr int64_t first() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) 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
VArray< int > resolution() const
IndexRange points_range() const
std::optional< Span< float3 > > handle_positions_left() const
Span< float3 > positions() const
std::optional< Span< float3 > > handle_positions_right() const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
VArray< int8_t > nurbs_orders() const
ABCAbstractWriter(const ABCWriterConstructorArgs &args)
Alembic::Abc::OCompoundProperty abc_schema_prop_for_custom_props(T abc_schema)
const ABCWriterConstructorArgs args_
virtual void update_bounding_box(Object *object)
Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree) override
ABCCurveMeshWriter(const ABCWriterConstructorArgs &args)
Alembic::Abc::OObject get_alembic_object() const override
ABCCurveWriter(const ABCWriterConstructorArgs &args)
void create_alembic_objects(const HierarchyContext *context) override
void do_write(HierarchyContext &context) override
Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override
ABCGenericMeshWriter(const ABCWriterConstructorArgs &args)
static bool is_cyclic(const Nurb *nu)
static float verts[][3]
int count
#define LOG(level)
Definition log.h:97
BooleanMix booleans_mix_calc(const VArray< bool > &varray, IndexRange range_to_check)
Curves * curve_legacy_to_curves(const Curve &curve_legacy)
Mesh * curve_to_wire_mesh(const CurvesGeometry &curve, const bke::AttributeFilter &attribute_filter={})
BLI_INLINE void copy_yup_from_zup(float yup[3], const float zup[3])
static Imath::V3f to_yup_V3f(float3 v)
const std::string ABC_CURVE_RESOLUTION_U_PROPNAME
void copy_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
VecBase< float, 3 > float3
short resolu
CurvesGeometry geometry