Blender V4.3
curves_extrude.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BKE_attribute.hh"
6#include "BKE_curves_utils.hh"
7
8#include "WM_api.hh"
9#include "WM_types.hh"
10
11#include "ED_curves.hh"
12
13#include "DEG_depsgraph.hh"
14
15namespace blender::ed::curves {
16
22static Span<int> compress_intervals(const Span<IndexRange> curve_interval_ranges,
23 MutableSpan<int> intervals)
24{
25 const int *src = intervals.data();
26 /* Skip the first curve, as all the data stays in the same place. */
27 int *dst = intervals.data() + curve_interval_ranges[0].size();
28
29 for (const int curve : IndexRange(1, curve_interval_ranges.size() - 1)) {
30 const IndexRange range = curve_interval_ranges[curve];
31 const int width = range.size() - 1;
32 std::copy_n(src + range.first() + 1, width, dst);
33 dst += width;
34 }
35 (*dst) = src[curve_interval_ranges[curve_interval_ranges.size() - 1].last() + 1];
36 return {intervals.data(), dst - intervals.data() + 1};
37}
38
44static bool handle_range(const int curve_index,
45 const int interval_offset,
46 const Span<int> offsets,
47 int &current_interval,
48 IndexRange &range,
49 MutableSpan<int> curve_intervals,
50 MutableSpan<bool> is_first_selected)
51{
52 const int first_elem = offsets[curve_index];
53 const int last_elem = offsets[curve_index + 1] - 1;
54
55 if (current_interval == 0) {
56 is_first_selected[curve_index] = range.first() == first_elem && range.size() == 1;
57 if (!is_first_selected[curve_index]) {
58 current_interval++;
59 }
60 }
61 curve_intervals[interval_offset + current_interval] = range.first();
62 current_interval++;
63
64 bool inside_curve = last_elem >= range.last();
65 if (inside_curve) {
66 curve_intervals[interval_offset + current_interval] = range.last();
67 }
68 else {
69 curve_intervals[interval_offset + current_interval] = last_elem;
70 range = IndexRange(last_elem + 1, range.last() - last_elem);
71 }
72 current_interval++;
73 return inside_curve;
74}
75
80static void calc_curve_offset(const int curve_index,
81 int &interval_offset,
82 const Span<int> offsets,
83 MutableSpan<int> new_offsets,
84 MutableSpan<IndexRange> curve_interval_ranges)
85{
86 const int points_in_curve = (offsets[curve_index + 1] - offsets[curve_index] +
87 curve_interval_ranges[curve_index].size() - 1);
88 new_offsets[curve_index + 1] = new_offsets[curve_index] + points_in_curve;
89 interval_offset += curve_interval_ranges[curve_index].size() + 1;
90}
91
92static void finish_curve(int &curve_index,
93 int &interval_offset,
94 int last_interval,
95 int last_elem,
96 const Span<int> offsets,
97 MutableSpan<int> new_offsets,
98 MutableSpan<int> curve_intervals,
99 MutableSpan<IndexRange> curve_interval_ranges,
100 MutableSpan<bool> is_first_selected)
101{
102 if (curve_intervals[interval_offset + last_interval] != last_elem ||
103 curve_intervals[interval_offset + last_interval - 1] !=
104 curve_intervals[interval_offset + last_interval])
105 {
106 /* Append last element of the current curve if it is not extruded or extruded together with
107 * preceding points. */
108 last_interval++;
109 curve_intervals[interval_offset + last_interval] = last_elem;
110 }
111 else if (is_first_selected[curve_index] && last_interval == 1) {
112 /* Extrusion from one point. */
113 curve_intervals[interval_offset + last_interval + 1] =
114 curve_intervals[interval_offset + last_interval];
115 is_first_selected[curve_index] = false;
116 last_interval++;
117 }
118 curve_interval_ranges[curve_index] = IndexRange(interval_offset, last_interval);
119 calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
120 curve_index++;
121}
122
123static void finish_curve_or_full_copy(int &curve_index,
124 int &interval_offset,
125 int current_interval,
126 const std::optional<IndexRange> prev_range,
127 const Span<int> offsets,
128 MutableSpan<int> new_offsets,
129 MutableSpan<int> curve_intervals,
130 MutableSpan<IndexRange> curve_interval_ranges,
131 MutableSpan<bool> is_first_selected)
132{
133 const int last = offsets[curve_index + 1] - 1;
134
135 if (prev_range.has_value() && prev_range.value().last() >= offsets[curve_index]) {
136 finish_curve(curve_index,
137 interval_offset,
138 current_interval - 1,
139 last,
140 offsets,
141 new_offsets,
142 curve_intervals,
143 curve_interval_ranges,
144 is_first_selected);
145 }
146 else {
147 /* Copy full curve if previous selected point was not on this curve. */
148 const int first = offsets[curve_index];
149 curve_interval_ranges[curve_index] = IndexRange(interval_offset, 1);
150 is_first_selected[curve_index] = false;
151 curve_intervals[interval_offset] = first;
152 curve_intervals[interval_offset + 1] = last;
153 calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
154 curve_index++;
155 }
156}
157
158static void calc_curves_extrusion(const IndexMask &selection,
159 const Span<int> offsets,
160 MutableSpan<int> new_offsets,
161 MutableSpan<int> curve_intervals,
162 MutableSpan<IndexRange> curve_interval_ranges,
163 MutableSpan<bool> is_first_selected)
164{
165 std::optional<IndexRange> prev_range;
166 int current_interval = 0;
167
168 int curve_index = 0;
169 int interval_offset = 0;
170 curve_intervals[interval_offset] = offsets[0];
171 new_offsets[0] = offsets[0];
172
173 selection.foreach_range([&](const IndexRange range) {
174 /* Beginning of the range outside current curve. */
175 if (range.first() > offsets[curve_index + 1] - 1) {
176 do {
177 finish_curve_or_full_copy(curve_index,
178 interval_offset,
179 current_interval,
180 prev_range,
181 offsets,
182 new_offsets,
183 curve_intervals,
184 curve_interval_ranges,
185 is_first_selected);
186 } while (range.first() > offsets[curve_index + 1] - 1);
187 current_interval = 0;
188 curve_intervals[interval_offset] = offsets[curve_index];
189 }
190
191 IndexRange range_to_handle = range;
192 while (!handle_range(curve_index,
193 interval_offset,
194 offsets,
195 current_interval,
196 range_to_handle,
197 curve_intervals,
198 is_first_selected))
199 {
200 finish_curve(curve_index,
201 interval_offset,
202 current_interval - 1,
203 offsets[curve_index + 1] - 1,
204 offsets,
205 new_offsets,
206 curve_intervals,
207 curve_interval_ranges,
208 is_first_selected);
209 current_interval = 0;
210 curve_intervals[interval_offset] = offsets[curve_index];
211 }
212 prev_range = range;
213 });
214
215 do {
216 finish_curve_or_full_copy(curve_index,
217 interval_offset,
218 current_interval,
219 prev_range,
220 offsets,
221 new_offsets,
222 curve_intervals,
223 curve_interval_ranges,
224 is_first_selected);
225 prev_range.reset();
226 } while (curve_index < offsets.size() - 1);
227}
228
229static void extrude_curves(Curves &curves_id)
230{
231 const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain);
232 if (selection_domain != bke::AttrDomain::Point) {
233 return;
234 }
235
236 IndexMaskMemory memory;
237 const IndexMask extruded_points = retrieve_selected_points(curves_id, memory);
238 if (extruded_points.is_empty()) {
239 return;
240 }
241
242 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
243 const Span<int> old_offsets = curves.offsets();
244
245 bke::CurvesGeometry new_curves = bke::curves::copy_only_curve_domain(curves);
246
247 const int curves_num = curves.curves_num();
248 const int curve_intervals_size = extruded_points.size() * 2 + curves_num * 2;
249
250 MutableSpan<int> new_offsets = new_curves.offsets_for_write();
251
252 /* Buffer for intervals of all curves. Beginning and end of a curve can be determined only by
253 * #curve_interval_ranges. For ex. [0, 3, 4, 4, 4] indicates one copy interval for first curve
254 * [0, 3] and two for second [4, 4][4, 4]. The first curve will be copied as is without changes,
255 * in the second one (consisting only one point - 4) first point will be duplicated (extruded).
256 */
257 Array<int> curve_intervals(curve_intervals_size);
258
259 /* Points to intervals for each curve in the curve_intervals array.
260 * For example above value would be [{0, 1}, {2, 2}] */
261 Array<IndexRange> curve_interval_ranges(curves_num);
262
263 /* Per curve boolean indicating if first interval in a curve is selected.
264 * Other can be calculated as in a curve two adjacent intervals can not have same selection
265 * state. */
266 Array<bool> is_first_selected(curves_num);
267
268 calc_curves_extrusion(extruded_points,
269 old_offsets,
270 new_offsets,
271 curve_intervals,
272 curve_interval_ranges,
273 is_first_selected);
274
275 new_curves.resize(new_offsets.last(), new_curves.curves_num());
276
277 const bke::AttributeAccessor src_attributes = curves.attributes();
278
279 std::array<GVArraySpan, 3> src_selection;
280 std::array<bke::GSpanAttributeWriter, 3> dst_selections;
281
282 const Span<StringRef> selection_attr_names = get_curves_selection_attribute_names(curves);
283 for (const int selection_i : selection_attr_names.index_range()) {
284 const StringRef selection_name = selection_attr_names[selection_i];
285
286 GVArray src_selection_array = *src_attributes.lookup(selection_name, bke::AttrDomain::Point);
287 if (!src_selection_array) {
288 src_selection_array = VArray<bool>::ForSingle(true, curves.points_num());
289 }
290
291 src_selection[selection_i] = src_selection_array;
292 dst_selections[selection_i] = ensure_selection_attribute(
293 new_curves,
294 bke::AttrDomain::Point,
295 src_selection_array.type().is<bool>() ? CD_PROP_BOOL : CD_PROP_FLOAT,
296 selection_name);
297 }
298
299 threading::parallel_for(curves.curves_range(), 256, [&](IndexRange curves_range) {
300 for (const int curve : curves_range) {
301 const int first_index = curve_interval_ranges[curve].start();
302 const int first_value = curve_intervals[first_index];
303 bool is_selected = is_first_selected[curve];
304
305 for (const int i : curve_interval_ranges[curve]) {
306 const int dest_index = new_offsets[curve] + curve_intervals[i] - first_value + i -
307 first_index;
308 const int size = curve_intervals[i + 1] - curve_intervals[i] + 1;
309
310 for (const int selection_i : selection_attr_names.index_range()) {
311 GMutableSpan dst_span = dst_selections[selection_i].span.slice(
312 IndexRange(dest_index, size));
313 if (is_selected) {
314 src_selection[selection_i].type().copy_assign_n(
315 src_selection[selection_i].slice(IndexRange(curve_intervals[i], size)).data(),
316 dst_span.data(),
317 size);
318 }
319 else {
320 fill_selection(dst_span, false);
321 }
322 }
323
324 is_selected = !is_selected;
325 }
326 }
327 });
328
329 for (const int selection_i : selection_attr_names.index_range()) {
330 dst_selections[selection_i].finish();
331 }
332
333 const Span<int> intervals = compress_intervals(curve_interval_ranges, curve_intervals);
334
335 bke::MutableAttributeAccessor dst_attributes = new_curves.attributes_for_write();
336
337 for (auto &attribute : bke::retrieve_attributes_for_transfer(
338 src_attributes,
339 dst_attributes,
341 bke::attribute_filter_from_skip_ref(
342 {".selection", ".selection_handle_left", ".selection_handle_right"})))
343 {
344 const CPPType &type = attribute.src.type();
345 threading::parallel_for(IndexRange(intervals.size() - 1), 512, [&](IndexRange range) {
346 for (const int i : range) {
347 const int first = intervals[i];
348 const int size = intervals[i + 1] - first + 1;
349 const int dest_index = intervals[i] + i;
350 type.copy_assign_n(attribute.src.slice(IndexRange(first, size)).data(),
351 attribute.dst.span.slice(IndexRange(dest_index, size)).data(),
352 size);
353 }
354 });
355 attribute.dst.finish();
356 }
357 curves_id.geometry.wrap() = std::move(new_curves);
359}
360
362{
363 for (Curves *curves_id : get_unique_editable_curves(*C)) {
364 extrude_curves(*curves_id);
365 }
366 return OPERATOR_FINISHED;
367}
368
370{
371 ot->name = "Extrude";
372 ot->description = "Extrude selected control point(s)";
373 ot->idname = "CURVES_OT_extrude";
374
377
379}
380
381} // namespace blender::ed::curves
@ ATTR_DOMAIN_MASK_POINT
Low-level operations for curves.
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
@ CD_PROP_FLOAT
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
bool is() const
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr T * data() const
Definition BLI_span.hh:540
constexpr T & first() const
Definition BLI_span.hh:680
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:690
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:326
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
GAttributeReader lookup(const StringRef attribute_id) const
void resize(int points_num, int curves_num)
MutableSpan< int > offsets_for_write()
IndexRange range
static void extrude_curves(Curves &curves_id)
static int curves_extrude_exec(bContext *C, wmOperator *)
VectorSet< Curves * > get_unique_editable_curves(const bContext &C)
Definition curves_ops.cc:96
static void calc_curves_extrusion(const IndexMask &selection, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< int > curve_intervals, MutableSpan< IndexRange > curve_interval_ranges, MutableSpan< bool > is_first_selected)
static void finish_curve_or_full_copy(int &curve_index, int &interval_offset, int current_interval, const std::optional< IndexRange > prev_range, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< int > curve_intervals, MutableSpan< IndexRange > curve_interval_ranges, MutableSpan< bool > is_first_selected)
static Span< int > compress_intervals(const Span< IndexRange > curve_interval_ranges, MutableSpan< int > intervals)
bool editable_curves_in_edit_mode_poll(bContext *C)
static void finish_curve(int &curve_index, int &interval_offset, int last_interval, int last_elem, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< int > curve_intervals, MutableSpan< IndexRange > curve_interval_ranges, MutableSpan< bool > is_first_selected)
Span< StringRef > get_curves_selection_attribute_names(const bke::CurvesGeometry &curves)
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, eCustomDataType create_type, StringRef attribute_name)
static void calc_curve_offset(const int curve_index, int &interval_offset, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< IndexRange > curve_interval_ranges)
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
void CURVES_OT_extrude(wmOperatorType *ot)
static bool handle_range(const int curve_index, const int interval_offset, const Span< int > offsets, int &current_interval, IndexRange &range, MutableSpan< int > curve_intervals, MutableSpan< bool > is_first_selected)
CurvesGeometry geometry
char selection_domain
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
wmOperatorType * ot
Definition wm_files.cc:4125