Blender V5.0
curves_pen.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9
10#include "BKE_attribute.hh"
11#include "BKE_context.hh"
12#include "BKE_curves.hh"
13#include "BKE_curves_utils.hh"
14#include "BKE_deform.hh"
15#include "BKE_grease_pencil.hh"
16#include "BKE_material.hh"
17#include "BKE_report.hh"
18
19#include "BLI_array_utils.hh"
20
21#include "BLT_translation.hh"
22
23#include "WM_api.hh"
24#include "WM_types.hh"
25
26#include "RNA_access.hh"
27#include "RNA_define.hh"
28#include "RNA_enum_types.hh"
29
30#include "DEG_depsgraph.hh"
31
32#include "DNA_material_types.h"
33
34#include "ED_curves.hh"
35#include "ED_grease_pencil.hh"
36#include "ED_screen.hh"
37#include "ED_view3d.hh"
38
39#include "UI_resources.hh"
40
41namespace blender::ed::curves {
42
43namespace pen_tool {
44
45enum class PenModal : int8_t {
46 /* Move the handles of the adjacent control point. */
48 /* Move the entire point even if only the handles are selected. */
50 /* Snap the handles to multiples of 45 degrees. */
52};
53
55 {BEZIER_HANDLE_AUTO, "AUTO", 0, "Auto", ""},
56 {BEZIER_HANDLE_VECTOR, "VECTOR", 0, "Vector", ""},
57 {0, nullptr, 0, nullptr, nullptr},
58};
59
60/* Used to scale the default select distance. */
61constexpr float selection_distance_factor = 0.9f;
62constexpr float selection_distance_factor_edge = 0.5f;
63
64/* Used when creating a single curve from nothing. */
65constexpr float default_handle_px_distance = 16.0f;
66
67/* Total number of curve handle types. */
68constexpr int CURVE_HANDLE_TYPES_NUM = 4;
69
70/* Edges are prioritized less than all other types. */
71constexpr float selection_edge_priority_factor = 0.1f;
72/* Points will overwrite edges to allow control point to be selected easier. */
74
78
79bool ClosestElement::is_closer(const float new_distance_squared,
80 const ElementMode new_element_mode,
81 const float threshold_distance) const
82{
83 const float threshold_distance_sq = threshold_distance * threshold_distance;
84
85 if (new_distance_squared > threshold_distance_sq) {
86 return false;
87 }
88
89 float old_priority = 1.0f;
90 float new_priority = 1.0f;
91
92 if (this->element_mode == ElementMode::Edge) {
93 if (new_element_mode != ElementMode::Edge) {
94 old_priority = selection_edge_priority_factor;
95
96 /* Overwrite edges with points if the point is within the overwrite distance. */
97 if (new_distance_squared <
99 {
100 return true;
101 }
102 }
103 }
104 else {
105 if (new_element_mode == ElementMode::Edge) {
106 new_priority = selection_edge_priority_factor;
107
108 /* Overwrite edges with points if the point is within the overwrite distance. */
109 if (this->distance_squared <
111 {
112 return false;
113 }
114 }
115 }
116
117 if (new_distance_squared * old_priority < this->distance_squared * new_priority) {
118 return true;
119 }
120
121 return false;
122}
123
124/* Will check if the point is closer than the existing element. */
127 const IndexMask &editable_curves,
128 const float4x4 &layer_to_object,
129 const int drawing_index,
130 const float2 &mouse_co,
131 ClosestElement &r_closest_element)
132{
133 const Span<float3> positions = curves.positions();
134 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
135
136 editable_curves.foreach_index([&](const int curve_i) {
137 const IndexRange points = points_by_curve[curve_i];
138 for (const int point_i : points) {
139 const float2 pos_proj = ptd.layer_to_screen(layer_to_object, positions[point_i]);
140 const float distance_squared = math::distance_squared(pos_proj, mouse_co);
141
142 /* Save the closest point. */
143 if (r_closest_element.is_closer(
144 distance_squared, ElementMode::Point, ptd.threshold_distance))
145 {
146 r_closest_element.curve_index = curve_i;
147 r_closest_element.point_index = point_i;
148 r_closest_element.element_mode = ElementMode::Point;
149 r_closest_element.distance_squared = distance_squared;
150 r_closest_element.drawing_index = drawing_index;
151 }
152 }
153 });
154}
155
156/* Will check if the handle is closer than the existing element. */
159 const IndexMask &bezier_points,
160 const float4x4 &layer_to_object,
161 const int drawing_index,
162 const float2 &mouse_co,
163 ClosestElement &r_closest_element)
164{
165 const Array<int> point_to_curve_map = curves.point_to_curve_map();
166 const Span<float3> handle_left = *curves.handle_positions_left();
167 const Span<float3> handle_right = *curves.handle_positions_right();
168
169 bezier_points.foreach_index([&](const int point_i) {
170 const float2 pos_proj = ptd.layer_to_screen(layer_to_object, handle_left[point_i]);
171 const float distance_squared = math::distance_squared(pos_proj, mouse_co);
172
173 /* Save the closest point. */
174 if (r_closest_element.is_closer(
175 distance_squared, ElementMode::HandleLeft, ptd.threshold_distance))
176 {
177 r_closest_element.curve_index = point_to_curve_map[point_i];
178 r_closest_element.point_index = point_i;
179 r_closest_element.element_mode = ElementMode::HandleLeft;
180 r_closest_element.distance_squared = distance_squared;
181 r_closest_element.drawing_index = drawing_index;
182 }
183 });
184
185 bezier_points.foreach_index([&](const int point_i) {
186 const float2 pos_proj = ptd.layer_to_screen(layer_to_object, handle_right[point_i]);
187 const float distance_squared = math::distance_squared(pos_proj, mouse_co);
188
189 /* Save the closest point. */
190 if (r_closest_element.is_closer(
191 distance_squared, ElementMode::HandleRight, ptd.threshold_distance))
192 {
193 r_closest_element.curve_index = point_to_curve_map[point_i];
194 r_closest_element.point_index = point_i;
195 r_closest_element.element_mode = ElementMode::HandleRight;
196 r_closest_element.distance_squared = distance_squared;
197 r_closest_element.drawing_index = drawing_index;
198 }
199 });
200}
201
203 const float2 &pos_2,
204 const float2 &pos,
205 float &r_local_t)
206{
207 const float2 dif_m = pos - pos_1;
208 const float2 dif_l = pos_2 - pos_1;
209 const float d = math::dot(dif_m, dif_l);
210 const float l2 = math::dot(dif_l, dif_l);
211 const float t = math::clamp(d / l2, 0.0f, 1.0f);
212 r_local_t = t;
213 return dif_l * t + pos_1;
214}
215
216/* Will check if the edge point is closer than the existing element. */
219 const IndexMask &editable_curves,
220 const float4x4 &layer_to_object,
221 const int drawing_index,
222 const float2 &mouse_co,
223 ClosestElement &r_closest_element)
224{
225 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
226 const OffsetIndices<int> evaluated_points_by_curve = curves.evaluated_points_by_curve();
227 const Span<float3> positions = curves.positions();
228 const Span<float3> evaluated_positions = curves.evaluated_positions();
229 const VArray<bool> cyclic = curves.cyclic();
230 const VArray<int8_t> types = curves.curve_types();
231
232 editable_curves.foreach_index([&](const int curve_i) {
233 const IndexRange src_points = points_by_curve[curve_i];
234 const IndexRange eval_points = evaluated_points_by_curve[curve_i];
235
236 for (const int src_i : src_points.index_range().drop_back(cyclic[curve_i] ? 0 : 1)) {
237 if (types[curve_i] != CURVE_TYPE_BEZIER) {
238 const int src_i_1 = src_i + src_points.first();
239 const int src_i_2 = (src_i + 1) % src_points.size() + src_points.first();
240 const float2 pos_1_proj = ptd.layer_to_screen(layer_to_object, positions[src_i_1]);
241 const float2 pos_2_proj = ptd.layer_to_screen(layer_to_object, positions[src_i_2]);
242 float local_t;
243 const float2 closest_pos = line_segment_closest_point(
244 pos_1_proj, pos_2_proj, mouse_co, local_t);
245
246 const float distance_squared = math::distance_squared(closest_pos, mouse_co);
247 const float t = local_t;
248
249 /* Save the closest point. */
250 if (r_closest_element.is_closer(
251 distance_squared, ElementMode::Edge, ptd.threshold_distance_edge))
252 {
253 r_closest_element.point_index = src_points.first() + src_i;
254 r_closest_element.edge_t = t;
255 r_closest_element.element_mode = ElementMode::Edge;
256 r_closest_element.curve_index = curve_i;
257 r_closest_element.distance_squared = distance_squared;
258 r_closest_element.drawing_index = drawing_index;
259 }
260 }
261 else {
262 const Span<int> offsets = curves.bezier_evaluated_offsets_for_curve(curve_i);
263 const IndexRange eval_range = IndexRange::from_begin_end_inclusive(offsets[src_i],
264 offsets[src_i + 1])
265 .shift(eval_points.first());
266 const int point_num = eval_range.size() - 1;
267
268 for (const int eval_i : IndexRange(point_num)) {
269 const int eval_point_i_1 = eval_range.first() + eval_i;
270 const int eval_point_i_2 = (eval_range.first() + eval_i + 1 - eval_points.first()) %
271 eval_points.size() +
272 eval_points.first();
273 const float2 pos_1_proj = ptd.layer_to_screen(layer_to_object,
274 evaluated_positions[eval_point_i_1]);
275 const float2 pos_2_proj = ptd.layer_to_screen(layer_to_object,
276 evaluated_positions[eval_point_i_2]);
277 float local_t;
278 const float2 closest_pos = line_segment_closest_point(
279 pos_1_proj, pos_2_proj, mouse_co, local_t);
280
281 const float distance_squared = math::distance_squared(closest_pos, mouse_co);
282 const float t = (eval_i + local_t) / float(point_num);
283
284 /* Save the closest point. */
285 if (r_closest_element.is_closer(
286 distance_squared, ElementMode::Edge, ptd.threshold_distance_edge))
287 {
288 r_closest_element.point_index = src_points.first() + src_i;
289 r_closest_element.element_mode = ElementMode::Edge;
290 r_closest_element.edge_t = t;
291 r_closest_element.curve_index = curve_i;
292 r_closest_element.distance_squared = distance_squared;
293 r_closest_element.drawing_index = drawing_index;
294 }
295 }
296 }
297 }
298 });
299}
300
302{
303 ClosestElement closest_element;
304 closest_element.element_mode = ElementMode::None;
305
306 for (const int curves_index : ptd.curves_range()) {
307 const bke::CurvesGeometry &curves = ptd.get_curves(curves_index);
308 const float4x4 layer_to_object = ptd.layer_to_object_per_curves[curves_index];
309
310 IndexMaskMemory memory;
311 const IndexMask bezier_points = ptd.visible_bezier_handle_points(curves_index, memory);
312 const IndexMask editable_curves = ptd.editable_curves(curves_index, memory);
313
315 ptd, curves, editable_curves, layer_to_object, curves_index, mouse_co, closest_element);
317 ptd, curves, bezier_points, layer_to_object, curves_index, mouse_co, closest_element);
319 ptd, curves, editable_curves, layer_to_object, curves_index, mouse_co, closest_element);
320 }
321 return closest_element;
322}
323
325{
327 status.opmodal(IFACE_("Snap Angle"), op->type, int(PenModal::SnapAngle));
328 status.opmodal(IFACE_("Move Current Handle"), op->type, int(PenModal::MoveHandle));
329 status.opmodal(IFACE_("Move Entire Point"), op->type, int(PenModal::MoveEntire));
330}
331
332/* Snaps to the closest diagonal, horizontal or vertical. */
334{
335 using namespace math;
336 const float sin225 = sin(AngleRadian::from_degree(22.5f));
337 return sign(p) * length(p) * normalize(sign(normalize(abs(p)) - sin225) + 1.0f);
338}
339
340static void move_segment(const PenToolOperation &ptd,
342 const float4x4 &layer_to_world)
343{
344 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
345 MutableSpan<float3> positions = curves.positions_for_write();
346 MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
347 MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
348 MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
349 MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
350
351 const int curve_i = ptd.closest_element.curve_index;
352 const IndexRange points = points_by_curve[curve_i];
353 const int point_i1 = ptd.closest_element.point_index;
354 const int point_i2 = (ptd.closest_element.point_index + 1 - points.first()) % points.size() +
355 points.first();
356
357 const float3 depth_point = positions[point_i1];
358 const float3 Pm = ptd.screen_to_layer(layer_to_world, ptd.mouse_co, depth_point);
359 const float3 P0 = positions[point_i1];
360 const float3 P3 = positions[point_i2];
361 const float3 p1 = handles_right[point_i1];
362 const float3 p2 = handles_left[point_i2];
363 const float3 k2 = p1 - p2;
364
365 const float t = ptd.closest_element.edge_t;
366 const float t_sq = t * t;
367 const float t_cu = t_sq * t;
368 const float one_minus_t = 1.0f - t;
369 const float one_minus_t_sq = one_minus_t * one_minus_t;
370 const float one_minus_t_cu = one_minus_t_sq * one_minus_t;
371
391
392 const float denom = 3.0f * one_minus_t * t;
393 if (denom == 0.0f) {
394 return;
395 }
396
397 const float3 P1 = (Pm - one_minus_t_cu * P0 - t_cu * P3) / denom + k2 * t;
398 const float3 P2 = P1 - k2;
399
400 handles_right[point_i1] = P1;
401 handles_left[point_i2] = P2;
402 handle_types_right[point_i1] = BEZIER_HANDLE_FREE;
403 handle_types_left[point_i2] = BEZIER_HANDLE_FREE;
404
405 /* Only change `Align`, Keep `Vector` and `Auto` the same. */
406 if (handle_types_left[point_i1] == BEZIER_HANDLE_ALIGN) {
407 handle_types_left[point_i1] = BEZIER_HANDLE_FREE;
408 }
409 if (handle_types_right[point_i2] == BEZIER_HANDLE_ALIGN) {
410 handle_types_right[point_i2] = BEZIER_HANDLE_FREE;
411 }
412
413 curves.calculate_bezier_auto_handles();
414}
415
418 const IndexMask &selection,
419 const float4x4 &layer_to_world,
420 const float4x4 &layer_to_object)
421{
422 if (selection.is_empty()) {
423 return false;
424 }
425
426 MutableSpan<float3> positions = curves.positions_for_write();
427 const bke::AttributeAccessor attributes = curves.attributes();
428 const Array<int> point_to_curve_map = curves.point_to_curve_map();
429
430 MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
431 MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
432 MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
433 MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
434
435 const VArray<bool> left_selected = *attributes.lookup_or_default<bool>(
436 ".selection_handle_left", bke::AttrDomain::Point, true);
437 const VArray<bool> right_selected = *attributes.lookup_or_default<bool>(
438 ".selection_handle_right", bke::AttrDomain::Point, true);
439
440 selection.foreach_index(GrainSize(2048), [&](const int64_t point_i) {
441 const float3 depth_point = positions[point_i];
442 float2 offset = ptd.xy - ptd.prev_xy;
443
444 if ((ptd.move_point && !ptd.point_added &&
445 !(left_selected[point_i] || right_selected[point_i])) ||
446 ptd.move_entire)
447 {
448 const float2 pos = ptd.layer_to_screen(layer_to_object, positions[point_i]);
449 const float2 pos_left = ptd.layer_to_screen(layer_to_object, handles_left[point_i]);
450 const float2 pos_right = ptd.layer_to_screen(layer_to_object, handles_right[point_i]);
451 positions[point_i] = ptd.screen_to_layer(layer_to_world, pos + offset, depth_point);
452 handles_left[point_i] = ptd.screen_to_layer(layer_to_world, pos_left + offset, depth_point);
453 handles_right[point_i] = ptd.screen_to_layer(
454 layer_to_world, pos_right + offset, depth_point);
455 return;
456 }
457
458 const bool is_left = !right_selected[point_i];
459 if (ptd.move_handle) {
460 if (is_left) {
461 const float2 pos_left = ptd.layer_to_screen(layer_to_object, handles_left[point_i]);
462 handles_left[point_i] = ptd.screen_to_layer(
463 layer_to_world, pos_left + offset, depth_point);
464 }
465 else {
466 const float2 pos_right = ptd.layer_to_screen(layer_to_object, handles_right[point_i]);
467 handles_right[point_i] = ptd.screen_to_layer(
468 layer_to_world, pos_right + offset, depth_point);
469 }
470 handle_types_left[point_i] = BEZIER_HANDLE_FREE;
471 handle_types_right[point_i] = BEZIER_HANDLE_FREE;
472 return;
473 }
474
475 const float2 center_point = ptd.layer_to_screen(layer_to_object, depth_point);
476 offset = ptd.mouse_co - ptd.center_of_mass_co;
477
478 if (ptd.snap_angle) {
479 offset = snap_8_angles(offset);
480 }
481
482 /* Set both handles to be `Aligned` if this point is newly added or is
483 * no longer control freely. */
484 if (ptd.point_added || ptd.handle_moved) {
485 handle_types_left[point_i] = BEZIER_HANDLE_ALIGN;
486 handle_types_right[point_i] = BEZIER_HANDLE_ALIGN;
487 }
488
489 if (is_left) {
490 if (handle_types_right[point_i] == BEZIER_HANDLE_AUTO) {
491 handle_types_right[point_i] = BEZIER_HANDLE_ALIGN;
492 }
493 handle_types_left[point_i] = handle_types_right[point_i];
494 if (handle_types_right[point_i] == BEZIER_HANDLE_VECTOR) {
495 handle_types_left[point_i] = BEZIER_HANDLE_FREE;
496 }
497
498 if (ptd.point_added) {
499 handles_left[point_i] = ptd.project(center_point + offset);
500 }
501 else {
502 handles_left[point_i] = ptd.screen_to_layer(
503 layer_to_world, center_point + offset, depth_point);
504 }
505
506 if (handle_types_right[point_i] == BEZIER_HANDLE_ALIGN) {
507 handles_right[point_i] = 2.0f * depth_point - handles_left[point_i];
508 }
509 }
510 else {
511 if (handle_types_left[point_i] == BEZIER_HANDLE_AUTO) {
512 handle_types_left[point_i] = BEZIER_HANDLE_ALIGN;
513 }
514 handle_types_right[point_i] = handle_types_left[point_i];
515 if (handle_types_left[point_i] == BEZIER_HANDLE_VECTOR) {
516 handle_types_right[point_i] = BEZIER_HANDLE_FREE;
517 }
518
519 if (ptd.point_added) {
520 handles_right[point_i] = ptd.project(center_point + offset);
521 }
522 else {
523 handles_right[point_i] = ptd.screen_to_layer(
524 layer_to_world, center_point + offset, depth_point);
525 }
526
527 if (handle_types_left[point_i] == BEZIER_HANDLE_ALIGN) {
528 handles_left[point_i] = 2.0f * depth_point - handles_right[point_i];
529 }
530 }
531 });
532
533 curves.calculate_bezier_auto_handles();
534
535 return true;
536}
537
538static std::optional<bke::CurvesGeometry> extrude_curves(const PenToolOperation &ptd,
539 const bke::CurvesGeometry &src,
540 const float4x4 &layer_to_object,
541 const IndexMask editable_curves)
542{
543 const bke::AttributeAccessor src_attributes = src.attributes();
544 const OffsetIndices<int> points_by_curve = src.points_by_curve();
545 const VArray<bool> &src_cyclic = src.cyclic();
546 const VArray<int8_t> types = src.curve_types();
547 const int old_points_num = src.points_num();
548
549 const VArray<bool> point_selection = *src_attributes.lookup_or_default<bool>(
550 ".selection", bke::AttrDomain::Point, true);
551 const VArray<bool> left_selected = *src_attributes.lookup_or_default<bool>(
552 ".selection_handle_left", bke::AttrDomain::Point, true);
553 const VArray<bool> right_selected = *src_attributes.lookup_or_default<bool>(
554 ".selection_handle_right", bke::AttrDomain::Point, true);
555
556 Vector<int> dst_to_src_points(old_points_num);
557 array_utils::fill_index_range(dst_to_src_points.as_mutable_span());
558
559 Vector<bool> dst_selected_start(old_points_num, false);
560 Vector<bool> dst_selected_center(old_points_num, false);
561 Vector<bool> dst_selected_end(old_points_num, false);
562
563 Array<int> dst_curve_counts(src.curves_num());
565 points_by_curve, src.curves_range(), dst_curve_counts.as_mutable_span());
566
567 /* Point offset keeps track of the points inserted. */
568 int point_offset = 0;
569 editable_curves.foreach_index([&](const int curve_index) {
570 const IndexRange curve_points = points_by_curve[curve_index];
571 /* Skip cyclic curves unless they only have one point. */
572 if (src_cyclic[curve_index] && curve_points.size() != 1) {
573 return;
574 }
575 const bool is_bezier = types[curve_index] == CURVE_TYPE_BEZIER;
576
577 bool first_selected = point_selection[curve_points.first()];
578 if (is_bezier) {
579 first_selected |= left_selected[curve_points.first()];
580 first_selected |= right_selected[curve_points.first()];
581 }
582
583 bool last_selected = point_selection[curve_points.last()];
584 if (is_bezier) {
585 last_selected |= left_selected[curve_points.last()];
586 last_selected |= right_selected[curve_points.last()];
587 }
588
589 if (first_selected) {
590 if (curve_points.size() != 1) {
591 /* Start-point extruded, we insert a new point at the beginning of the curve. */
592 dst_to_src_points.insert(curve_points.first() + point_offset, curve_points.first());
593 dst_selected_start.insert(curve_points.first() + point_offset, true);
594 dst_selected_center.insert(curve_points.first() + point_offset, !is_bezier);
595 dst_selected_end.insert(curve_points.first() + point_offset, false);
596 dst_curve_counts[curve_index]++;
597 point_offset++;
598 }
599 }
600
601 if (last_selected) {
602 /* End-point extruded, we insert a new point at the end of the curve. */
603 dst_to_src_points.insert(curve_points.last() + point_offset + 1, curve_points.last());
604 dst_selected_end.insert(curve_points.last() + point_offset + 1, true);
605 dst_selected_center.insert(curve_points.last() + point_offset + 1, !is_bezier);
606 dst_selected_start.insert(curve_points.last() + point_offset + 1, false);
607 dst_curve_counts[curve_index]++;
608 point_offset++;
609 }
610 });
611
612 if (point_offset == 0) {
613 return std::nullopt;
614 }
615
616 bke::CurvesGeometry dst(dst_to_src_points.size(), src.curves_num());
618
619 /* Setup curve offsets, based on the number of points in each curve. */
620 MutableSpan<int> new_curve_offsets = dst.offsets_for_write();
621 array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
623
625
626 /* Selection attribute. */
630 dst, bke::AttrDomain::Point, bke::AttrType::Bool, ".selection_handle_left");
632 dst, bke::AttrDomain::Point, bke::AttrType::Bool, ".selection_handle_right");
633 selection_left.span.copy_from(dst_selected_start.as_span());
634 selection.span.copy_from(dst_selected_center.as_span());
635 selection_right.span.copy_from(dst_selected_end.as_span());
636 selection_left.finish();
637 selection.finish();
638 selection_right.finish();
639
641 src_attributes, bke::AttrDomain::Curve, bke::AttrDomain::Curve, {}, dst_attributes);
642
643 bke::gather_attributes(src_attributes,
647 {".selection", ".selection_handle_left", ".selection_handle_right"}),
648 dst_to_src_points,
649 dst_attributes);
650
651 Span<float3> src_positions = src.positions();
652 MutableSpan<float3> dst_positions = dst.positions_for_write();
653 MutableSpan<bool> dst_cyclic = dst.cyclic_for_write();
654 const Array<int> dst_point_to_curve_map = dst.point_to_curve_map();
655 MutableSpan<int8_t> handle_types_left = dst.handle_types_left_for_write();
656 MutableSpan<int8_t> handle_types_right = dst.handle_types_right_for_write();
658 for (const int i : dst_to_src_points.index_range()) {
659 if (!(dst_selected_end[i] || dst_selected_start[i])) {
660 continue;
661 }
662 const float3 depth_point = src_positions[dst_to_src_points[i]];
663 const float2 pos = ptd.layer_to_screen(layer_to_object, depth_point) - ptd.center_of_mass_co +
664 ptd.mouse_co;
665 dst_positions[i] = ptd.project(pos);
666 handle_types_left[i] = ptd.extrude_handle;
667 handle_types_right[i] = ptd.extrude_handle;
668 radius[i] = ptd.radius;
669 dst_cyclic[dst_point_to_curve_map[i]] = false;
670 }
671
672 dst.update_curve_types();
674 if (src.nurbs_has_custom_knots()) {
675 IndexMaskMemory memory;
676 const VArray<int8_t> curve_types = src.curve_types();
677 const VArray<int8_t> knot_modes = dst.nurbs_knots_modes();
678 const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
679 const IndexMask include_curves = IndexMask::from_predicate(
680 src.curves_range(), GrainSize(512), memory, [&](const int64_t curve_index) {
681 return curve_types[curve_index] == CURVE_TYPE_NURBS &&
682 knot_modes[curve_index] == NURBS_KNOT_MODE_CUSTOM &&
683 points_by_curve[curve_index].size() == dst_points_by_curve[curve_index].size();
684 });
686 include_curves.complement(dst.curves_range(), memory),
689 dst);
690 bke::curves::nurbs::gather_custom_knots(src, include_curves, 0, dst);
691 }
692 return dst;
693}
694
696{
697 const bke::AttributeAccessor src_attributes = src.attributes();
698 const OffsetIndices<int> points_by_curve = src.points_by_curve();
699 const int old_points_num = src.points_num();
700 const int src_point_index = ptd.closest_element.point_index;
701 const int dst_point_index = src_point_index + 1;
702 const int curve_index = ptd.closest_element.curve_index;
703 const IndexRange points = points_by_curve[curve_index];
704 const int src_point_index_2 = (src_point_index + 1 - points.first()) % points.size() +
705 points.first();
706 const int dst_point_index_2 = (dst_point_index - points.first() + 1) % (points.size() + 1) +
707 points.first();
708
709 Vector<int> dst_to_src_points(old_points_num);
711
712 Array<int> dst_curve_counts(src.curves_num());
714 points_by_curve, src.curves_range(), dst_curve_counts.as_mutable_span());
715
716 dst_to_src_points.insert(src_point_index + 1, src_point_index);
717 dst_curve_counts[curve_index]++;
718
719 bke::CurvesGeometry dst(dst_to_src_points.size(), src.curves_num());
721
722 /* Setup curve offsets, based on the number of points in each curve. */
723 MutableSpan<int> new_curve_offsets = dst.offsets_for_write();
724 array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
726
728
729 /* Selection attribute. */
730 for (const StringRef selection_attribute_name :
732 {
734 dst, bke::AttrDomain::Point, bke::AttrType::Bool, selection_attribute_name);
735 ed::curves::fill_selection_false(selection_writer.span);
736 ed::curves::fill_selection_true(selection_writer.span,
737 IndexRange::from_single(dst_point_index));
738 selection_writer.finish();
739 }
740
742 src_attributes, bke::AttrDomain::Curve, bke::AttrDomain::Curve, {}, dst_attributes);
743 bke::gather_attributes(src_attributes,
747 {".selection", ".selection_handle_left", ".selection_handle_right"}),
748 dst_to_src_points,
749 dst_attributes);
750
751 Span<float3> src_positions = src.positions();
752 MutableSpan<float3> dst_positions = dst.positions_for_write();
753 MutableSpan<int8_t> handle_types_left = dst.handle_types_left_for_write();
754 MutableSpan<int8_t> handle_types_right = dst.handle_types_right_for_write();
755 const Span<float3> src_handles_left = *src.handle_positions_left();
756 const Span<float3> src_handles_right = *src.handle_positions_right();
759 handle_types_left[dst_point_index] = BEZIER_HANDLE_ALIGN;
760 handle_types_right[dst_point_index] = BEZIER_HANDLE_ALIGN;
761
763 src_positions[src_point_index],
764 src_handles_right[src_point_index],
765 src_handles_left[src_point_index_2],
766 src_positions[src_point_index_2],
768
769 dst_positions[dst_point_index] = inserted_point.position;
770 dst_handles_left[dst_point_index] = inserted_point.left_handle;
771 dst_handles_right[dst_point_index] = inserted_point.right_handle;
772 dst_handles_right[dst_point_index - 1] = inserted_point.handle_prev;
773 dst_handles_left[dst_point_index_2] = inserted_point.handle_next;
774 handle_types_right[dst_point_index - 1] = BEZIER_HANDLE_FREE;
775 handle_types_left[dst_point_index_2] = BEZIER_HANDLE_FREE;
776
777 dst.update_curve_types();
779 if (src.nurbs_has_custom_knots()) {
780 IndexMaskMemory memory;
781 const VArray<int8_t> curve_types = src.curve_types();
782 const VArray<int8_t> knot_modes = dst.nurbs_knots_modes();
783 const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
784 const IndexMask include_curves = IndexMask::from_predicate(
785 src.curves_range(), GrainSize(512), memory, [&](const int64_t curve_index) {
786 return curve_types[curve_index] == CURVE_TYPE_NURBS &&
787 knot_modes[curve_index] == NURBS_KNOT_MODE_CUSTOM &&
788 points_by_curve[curve_index].size() == dst_points_by_curve[curve_index].size();
789 });
791 include_curves.complement(dst.curves_range(), memory),
794 dst);
795 bke::curves::nurbs::gather_custom_knots(src, include_curves, 0, dst);
796 }
797
798 src = std::move(dst);
799}
800
803 const float4x4 &layer_to_world)
804{
805 const float3 depth_point = ptd.project(ptd.mouse_co);
806
808 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
809
810 Set<std::string> curve_attributes_to_skip;
811
812 curves.positions_for_write().last() = depth_point;
813 curves.curve_types_for_write().last() = CURVE_TYPE_BEZIER;
814 curve_attributes_to_skip.add("curve_type");
815 curves.handle_types_left_for_write().last() = ptd.extrude_handle;
816 curves.handle_types_right_for_write().last() = ptd.extrude_handle;
817 curves.update_curve_types();
818 curves.resolution_for_write().last() = 12;
819 curve_attributes_to_skip.add("resolution");
820
821 const int material_index = ptd.vc.obact->actcol - 1;
822 if (material_index != -1) {
823 bke::SpanAttributeWriter<int> material_indexes = attributes.lookup_or_add_for_write_span<int>(
824 "material_index",
827 material_indexes.span.last() = material_index;
828 material_indexes.finish();
829 curve_attributes_to_skip.add("material_index");
830 }
831
832 MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
833 MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
834 handles_left.last() = ptd.screen_to_layer(
835 layer_to_world, ptd.mouse_co - float2(default_handle_px_distance / 2.0f, 0.0f), depth_point);
836 handles_right.last() = ptd.screen_to_layer(
837 layer_to_world, ptd.mouse_co + float2(default_handle_px_distance / 2.0f, 0.0f), depth_point);
838 curves.radius_for_write().last() = ptd.radius;
839
840 for (const StringRef selection_attribute_name :
842 {
844 curves, bke::AttrDomain::Point, bke::AttrType::Bool, selection_attribute_name);
845
847 IndexRange::from_single(curves.points_range().last()));
848 selection.finish();
849 }
850
851 /* Initialize the rest of the attributes with default values. */
853 attributes,
856 "radius",
857 "handle_left",
858 "handle_right",
859 "handle_type_left",
860 "handle_type_right",
861 ".selection",
862 ".selection_handle_left",
863 ".selection_handle_right"}),
864 curves.points_range().take_back(1));
867 bke::attribute_filter_from_skip_ref(curve_attributes_to_skip),
868 curves.curves_range().take_back(1));
869}
870
873 const IndexRange points,
874 const bool clear_selection)
875{
876 bool changed = false;
877
878 for (const StringRef selection_attribute_name :
880 {
882 curves, bke::AttrDomain::Point, bke::AttrType::Bool, selection_attribute_name);
883
884 const bool last_selected = ed::curves::has_anything_selected(
885 selection_writer.span.slice(IndexRange::from_single(points.last())));
886 const bool first_selected = ed::curves::has_anything_selected(
887 selection_writer.span.slice(IndexRange::from_single(points.first())));
888
889 /* Close the curve by selecting the other end point. */
890 if ((ptd.closest_element.point_index == points.first() && last_selected) ||
891 (ptd.closest_element.point_index == points.last() && first_selected))
892 {
893 curves.cyclic_for_write()[ptd.closest_element.curve_index] = true;
894 curves.calculate_bezier_auto_handles();
895 changed = true;
896 }
897
898 if (clear_selection) {
899 ed::curves::fill_selection_false(selection_writer.span);
900 }
901
902 if (ptd.select_point) {
903 if ((selection_attribute_name == ".selection" &&
905 (selection_attribute_name == ".selection_handle_left" &&
907 (selection_attribute_name == ".selection_handle_right" &&
909 {
910
911 ed::curves::fill_selection_true(selection_writer.span,
913 changed = true;
914 }
915 }
916
917 selection_writer.finish();
918 }
919
920 return changed;
921}
922
923static float2 calculate_center_of_mass(const PenToolOperation &ptd, const bool ends_only)
924{
925 float2 pos = float2(0.0f, 0.0f);
926 int num = 0;
927
928 for (const int curves_index : ptd.curves_range()) {
929 const bke::CurvesGeometry &curves = ptd.get_curves(curves_index);
930 const float4x4 &layer_to_object = ptd.layer_to_object_per_curves[curves_index];
931 const Span<float3> positions = curves.positions();
932 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
933 const Array<int> point_to_curve_map = curves.point_to_curve_map();
934 const VArray<bool> &cyclic = curves.cyclic();
935
936 IndexMaskMemory memory;
937 const IndexMask selection = ptd.all_selected_points(curves_index, memory);
938
939 selection.foreach_index([&](const int64_t point_i) {
940 if (ends_only) {
941 const int curve_i = point_to_curve_map[point_i];
942 const IndexRange points = points_by_curve[curve_i];
943
944 /* Skip cyclic curves unless they only have one point. */
945 if (cyclic[curve_i] && points.size() != 1) {
946 return;
947 }
948
949 if (point_i != points.first() && point_i != points.last()) {
950 return;
951 }
952 }
953 pos += ptd.layer_to_screen(layer_to_object, positions[point_i]);
954 num++;
955 });
956 }
957
958 if (num == 0) {
959 return pos;
960 }
961 return pos / num;
962}
963
964static void invoke_curves(PenToolOperation &ptd, bContext *C, wmOperator *op, const wmEvent *event)
965{
968
969 std::atomic<bool> add_single = ptd.extrude_point;
970 std::atomic<bool> changed = false;
971 std::atomic<bool> point_added = false;
972 std::atomic<bool> point_removed = false;
973
974 threading::parallel_for(ptd.curves_range(), 1, [&](const IndexRange curves_range) {
975 for (const int curves_index : curves_range) {
976 bke::CurvesGeometry &curves = ptd.get_curves(curves_index);
977
978 if (curves.is_empty()) {
979 continue;
980 }
981
982 if (ptd.closest_element.element_mode == ElementMode::Edge) {
983 add_single.store(false, std::memory_order_relaxed);
984 if (ptd.insert_point && ptd.closest_element.drawing_index == curves_index) {
985 insert_point_to_curve(ptd, curves);
986 ptd.tag_curve_changed(curves_index);
987 changed.store(true, std::memory_order_relaxed);
988 }
989 continue;
990 }
991
992 if (ptd.closest_element.element_mode == ElementMode::None) {
993 if (ptd.extrude_point) {
994 IndexMaskMemory memory;
995 const IndexMask editable_curves = ptd.editable_curves(curves_index, memory);
996 const float4x4 &layer_to_object = ptd.layer_to_object_per_curves[curves_index];
997
998 if (std::optional<bke::CurvesGeometry> result = extrude_curves(
999 ptd, curves, layer_to_object, editable_curves))
1000 {
1001 curves = std::move(*result);
1002 }
1003 else {
1004 for (const StringRef selection_attribute_name :
1005 ed::curves::get_curves_selection_attribute_names(curves))
1006 {
1007 bke::GSpanAttributeWriter selection_writer = ed::curves::ensure_selection_attribute(
1008 curves, bke::AttrDomain::Point, bke::AttrType::Bool, selection_attribute_name);
1009 ed::curves::fill_selection_false(selection_writer.span);
1010 selection_writer.finish();
1011 }
1012 continue;
1013 }
1014
1015 add_single.store(false, std::memory_order_relaxed);
1016 point_added.store(true, std::memory_order_relaxed);
1017 ptd.tag_curve_changed(curves_index);
1018
1019 changed.store(true, std::memory_order_relaxed);
1020 continue;
1021 }
1022
1023 continue;
1024 }
1025
1026 if (curves_index != ptd.closest_element.drawing_index) {
1027 if (event->val != KM_DBL_CLICK && !ptd.delete_point) {
1028 for (const StringRef selection_attribute_name :
1029 ed::curves::get_curves_selection_attribute_names(curves))
1030 {
1031 bke::GSpanAttributeWriter selection_writer = ed::curves::ensure_selection_attribute(
1032 curves, bke::AttrDomain::Point, bke::AttrType::Bool, selection_attribute_name);
1033 ed::curves::fill_selection_false(selection_writer.span);
1034 selection_writer.finish();
1035 }
1036 }
1037
1038 continue;
1039 }
1040
1041 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
1042 const IndexRange points = points_by_curve[ptd.closest_element.curve_index];
1043
1044 if (event->val == KM_DBL_CLICK && ptd.cycle_handle_type) {
1045 const int8_t handle_type = curves.handle_types_right()[ptd.closest_element.point_index];
1046 /* Cycle to the next type. */
1047 const int8_t new_handle_type = (handle_type + 1) % CURVE_HANDLE_TYPES_NUM;
1048
1049 curves.handle_types_left_for_write()[ptd.closest_element.point_index] = new_handle_type;
1050 curves.handle_types_right_for_write()[ptd.closest_element.point_index] = new_handle_type;
1051 curves.calculate_bezier_auto_handles();
1052 ptd.tag_curve_changed(curves_index);
1053 add_single.store(false, std::memory_order_relaxed);
1054 }
1055
1056 if (ptd.delete_point) {
1057 curves.remove_points(IndexRange::from_single(ptd.closest_element.point_index), {});
1058 add_single.store(false, std::memory_order_relaxed);
1059 point_removed.store(true, std::memory_order_relaxed);
1060 ptd.tag_curve_changed(curves_index);
1061 continue;
1062 }
1063
1064 const bool clear_selection = event->val != KM_DBL_CLICK && !ptd.delete_point;
1065 if (close_curve_and_select(ptd, curves, points, clear_selection)) {
1066 ptd.tag_curve_changed(curves_index);
1067 add_single.store(false, std::memory_order_relaxed);
1068 }
1069
1070 changed.store(true, std::memory_order_relaxed);
1071 }
1072 });
1073
1074 if (add_single) {
1075 if (ptd.can_create_new_curve(op)) {
1076 const int curves_index = *ptd.active_drawing_index;
1077
1078 const float4x4 &layer_to_world = ptd.layer_to_world_per_curves[curves_index];
1079 bke::CurvesGeometry &curves = ptd.get_curves(curves_index);
1080
1081 add_single_point_and_curve(ptd, curves, layer_to_world);
1082 ptd.single_point_attributes(curves, curves_index);
1083 ptd.tag_curve_changed(curves_index);
1084
1085 changed.store(true, std::memory_order_relaxed);
1086 point_added = true;
1087 }
1088 }
1089
1090 ptd.point_added = point_added;
1091 ptd.point_removed = point_removed;
1092
1094 if (changed) {
1095 ptd.update_view(C);
1096 }
1097}
1098
1100 const int handle_display,
1101 IndexMaskMemory &memory)
1102{
1103 if (handle_display == CURVE_HANDLE_NONE) {
1104 return IndexMask(0);
1105 }
1106 else if (handle_display == CURVE_HANDLE_ALL) {
1107 return curves.points_range();
1108 }
1109 /* else handle_display == CURVE_HANDLE_SELECTED */
1110
1111 if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
1112 return IndexMask(0);
1113 }
1114
1115 const Array<int> point_to_curve_map = curves.point_to_curve_map();
1116 const VArray<int8_t> types = curves.curve_types();
1117
1118 const VArray<bool> selected_point = *curves.attributes().lookup_or_default<bool>(
1119 ".selection", bke::AttrDomain::Point, true);
1120 const VArray<bool> selected_left = *curves.attributes().lookup_or_default<bool>(
1121 ".selection_handle_left", bke::AttrDomain::Point, true);
1122 const VArray<bool> selected_right = *curves.attributes().lookup_or_default<bool>(
1123 ".selection_handle_right", bke::AttrDomain::Point, true);
1124
1125 const IndexMask selected_points = IndexMask::from_predicate(
1126 curves.points_range(), GrainSize(4096), memory, [&](const int64_t point_i) {
1127 const bool is_selected = selected_point[point_i] || selected_left[point_i] ||
1128 selected_right[point_i];
1129 const bool is_bezier = types[point_to_curve_map[point_i]] == CURVE_TYPE_BEZIER;
1130 return is_selected && is_bezier;
1131 });
1132
1133 return selected_points;
1134}
1135
1137 const float3 &point) const
1138{
1140 vc.region, math::transform_point(layer_to_object, point), projection);
1141}
1142
1144 const float2 &screen_co,
1145 const float3 &depth_point_layer) const
1146{
1147 const float3 depth_point = math::transform_point(layer_to_world, depth_point_layer);
1148 float3 proj_point;
1149 ED_view3d_win_to_3d(vc.v3d, vc.region, depth_point, screen_co, proj_point);
1150 return math::transform_point(math::invert(layer_to_world), proj_point);
1151}
1152
1154{
1155 /* If in tools region, wait till we get to the main (3D-space)
1156 * region before allowing drawing to take place. */
1158
1159 wmWindow *win = CTX_wm_window(C);
1160 /* Set cursor to indicate modal. */
1162
1164
1165 this->vc = vc;
1166 this->projection = ED_view3d_ob_project_mat_get(this->vc.rv3d, this->vc.obact);
1167
1168 /* Distance threshold for mouse clicks to affect the spline or its points */
1169 this->mouse_co = float2(event->mval);
1172
1173 this->extrude_point = RNA_boolean_get(op->ptr, "extrude_point");
1174 this->delete_point = RNA_boolean_get(op->ptr, "delete_point");
1175 this->insert_point = RNA_boolean_get(op->ptr, "insert_point");
1176 this->move_seg = RNA_boolean_get(op->ptr, "move_segment");
1177 this->select_point = RNA_boolean_get(op->ptr, "select_point");
1178 this->move_point = RNA_boolean_get(op->ptr, "move_point");
1179 this->cycle_handle_type = RNA_boolean_get(op->ptr, "cycle_handle_type");
1180 this->extrude_handle = RNA_enum_get(op->ptr, "extrude_handle");
1181 this->radius = RNA_float_get(op->ptr, "radius");
1182
1183 this->move_entire = false;
1184 this->snap_angle = false;
1185
1186 this->handle_moved = false;
1187
1188 if (!(ELEM(event->type, LEFTMOUSE) && ELEM(event->val, KM_PRESS, KM_DBL_CLICK))) {
1190 }
1191
1192 if (std::optional<wmOperatorStatus> result = this->initialize(C, op, event)) {
1193 return *result;
1194 }
1195
1196 /* Add a modal handler for this operator. */
1198
1199 invoke_curves(*this, C, op, event);
1200
1202}
1203
1205{
1206 this->mouse_co = float2(event->mval);
1207 this->xy = float2(event->xy);
1208 this->prev_xy = float2(event->prev_xy);
1209
1210 if (event->type == EVENT_NONE) {
1212 }
1213
1214 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
1215 return OPERATOR_FINISHED;
1216 }
1217 if (this->point_removed) {
1218 return OPERATOR_FINISHED;
1219 }
1220
1221 if (event->type == EVT_MODAL_MAP) {
1222 if (event->val == int(PenModal::MoveEntire)) {
1223 this->move_entire = !this->move_entire;
1224 }
1225 else if (event->val == int(PenModal::SnapAngle)) {
1226 this->snap_angle = !this->snap_angle;
1227 }
1228 else if (event->val == int(PenModal::MoveHandle)) {
1229 this->move_handle = !this->move_handle;
1230
1231 /* Record if handle has every been moved. */
1232 if (this->move_handle) {
1233 this->handle_moved = true;
1234 }
1235 }
1236 }
1237
1238 std::atomic<bool> changed = false;
1239 this->center_of_mass_co = calculate_center_of_mass(*this, false);
1240
1241 if (this->move_seg && this->closest_element.element_mode == ElementMode::Edge) {
1242 const int curves_index = this->closest_element.drawing_index;
1243 const float4x4 &layer_to_world = this->layer_to_world_per_curves[curves_index];
1244 bke::CurvesGeometry &curves = this->get_curves(curves_index);
1245
1246 move_segment(*this, curves, layer_to_world);
1247 this->tag_curve_changed(curves_index);
1248 changed.store(true, std::memory_order_relaxed);
1249 }
1250 else {
1252 for (const int curves_index : curves_range) {
1253 bke::CurvesGeometry &curves = this->get_curves(curves_index);
1254 const float4x4 &layer_to_object = this->layer_to_object_per_curves[curves_index];
1255 const float4x4 &layer_to_world = this->layer_to_world_per_curves[curves_index];
1256
1257 IndexMaskMemory memory;
1258 const IndexMask selection = this->all_selected_points(curves_index, memory);
1259
1260 if (move_handles_in_curve(*this, curves, selection, layer_to_world, layer_to_object)) {
1261 changed.store(true, std::memory_order_relaxed);
1262 this->tag_curve_changed(curves_index);
1263 }
1264 }
1265 });
1266 }
1267
1269 if (changed) {
1270 this->update_view(C);
1271 }
1272
1273 /* Still running... */
1275}
1276
1278 public:
1280
1281 float3 project(const float2 &screen_co) const
1282 {
1283 const float4x4 &layer_to_world = this->layer_to_world_per_curves[*this->active_drawing_index];
1284 return this->screen_to_layer(layer_to_world, screen_co, float3(0.0f));
1285 }
1286
1287 IndexMask all_selected_points(const int curves_index, IndexMaskMemory &memory) const
1288 {
1289 const Curves *curves_id = this->all_curves[curves_index];
1290 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
1291 return retrieve_all_selected_points(curves, this->vc.v3d->overlay.handle_display, memory);
1292 }
1293
1294 IndexMask visible_bezier_handle_points(const int curves_index, IndexMaskMemory &memory) const
1295 {
1296 const Curves *curves_id = this->all_curves[curves_index];
1297 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
1299 curves, this->vc.v3d->overlay.handle_display, memory);
1300 }
1301
1302 IndexMask editable_curves(const int curves_index, IndexMaskMemory & /*memory*/) const
1303 {
1304 const Curves *curves_id = this->all_curves[curves_index];
1305 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
1306 return curves.curves_range();
1307 }
1308
1309 void tag_curve_changed(const int curves_index) const
1310 {
1311 Curves *curves_id = this->all_curves[curves_index];
1312 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
1313 curves.tag_topology_changed();
1314 }
1315
1316 bke::CurvesGeometry &get_curves(const int curves_index) const
1317 {
1318 Curves *curves_id = this->all_curves[curves_index];
1319 return curves_id->geometry.wrap();
1320 }
1321
1323 {
1324 return this->all_curves.index_range();
1325 }
1326
1327 void single_point_attributes(bke::CurvesGeometry & /*curves*/, const int /*curves_index*/) const
1328 {
1329 return;
1330 }
1331
1333 {
1334 if (this->active_drawing_index == std::nullopt) {
1335 BKE_report(op->reports, RPT_ERROR, "No active Curves Object");
1336 return false;
1337 }
1338
1339 return true;
1340 }
1341
1343 {
1344 for (Curves *curves_id : this->all_curves) {
1345 DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
1346 WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
1347 }
1348 ED_region_tag_redraw(this->vc.region);
1349 }
1350
1351 std::optional<wmOperatorStatus> initialize(bContext *C,
1352 wmOperator * /*op*/,
1353 const wmEvent * /*event*/)
1354 {
1355 this->active_drawing_index = std::nullopt;
1356 VectorSet<Curves *> unique_curves;
1357
1358 const Main &bmain = *CTX_data_main(C);
1359
1360 Object *object = CTX_data_active_object(C);
1361 if (object && object_has_editable_curves(bmain, *object)) {
1362 unique_curves.add_new(static_cast<Curves *>(object->data));
1363 this->layer_to_world_per_curves.append(object->object_to_world());
1364 this->active_drawing_index = 0;
1365 }
1366
1367 CTX_DATA_BEGIN (C, Object *, object, selected_objects) {
1368 if (object_has_editable_curves(bmain, *object)) {
1369 if (unique_curves.add(static_cast<Curves *>(object->data))) {
1370 this->layer_to_world_per_curves.append(object->object_to_world());
1371 }
1372 }
1373 }
1375
1376 for (Curves *curves_id : unique_curves) {
1377 this->all_curves.append(curves_id);
1378 }
1379
1380 this->layer_to_object_per_curves.append_n_times(float4x4::identity(), this->all_curves.size());
1381
1382 return std::nullopt;
1383 }
1384};
1385
1386/* Exit and free memory. */
1388{
1389 CurvesPenToolOperation *ptd = static_cast<CurvesPenToolOperation *>(op->customdata);
1390
1391 /* Clear status message area. */
1392 ED_workspace_status_text(C, nullptr);
1393
1395
1396 ptd->update_view(C);
1397
1398 MEM_delete(ptd);
1399 /* Clear pointer. */
1400 op->customdata = nullptr;
1401}
1402
1403/* Invoke handler: Initialize the operator. */
1405{
1406 /* Allocate new data. */
1407 CurvesPenToolOperation *ptd_pointer = MEM_new<CurvesPenToolOperation>(__func__);
1408 op->customdata = ptd_pointer;
1409 CurvesPenToolOperation &ptd = *ptd_pointer;
1410
1411 const wmOperatorStatus result = ptd.invoke(C, op, event);
1413 curves_pen_exit(C, op);
1414 }
1415 return result;
1416}
1417
1418/* Modal handler: Events handling during interactive part. */
1420{
1421 CurvesPenToolOperation &ptd = *reinterpret_cast<CurvesPenToolOperation *>(op->customdata);
1422
1423 const wmOperatorStatus result = ptd.modal(C, op, event);
1425 curves_pen_exit(C, op);
1426 }
1427 return result;
1428}
1429
1431{
1433
1434 RNA_def_boolean(ot->srna,
1435 "extrude_point",
1436 false,
1437 "Extrude Point",
1438 "Add a point connected to the last selected point");
1439 RNA_def_enum(ot->srna,
1440 "extrude_handle",
1443 "Extrude Handle Type",
1444 "Type of the extruded handle");
1445 RNA_def_boolean(ot->srna, "delete_point", false, "Delete Point", "Delete an existing point");
1447 ot->srna, "insert_point", false, "Insert Point", "Insert Point into a curve segment");
1448 RNA_def_boolean(ot->srna, "move_segment", false, "Move Segment", "Delete an existing point");
1450 ot->srna, "select_point", false, "Select Point", "Select a point or its handles");
1451 RNA_def_boolean(ot->srna, "move_point", false, "Move Point", "Move a point or its handles");
1452 RNA_def_boolean(ot->srna,
1453 "cycle_handle_type",
1454 false,
1455 "Cycle Handle Type",
1456 "Cycle between all four handle types");
1457 RNA_def_float_distance(ot->srna, "radius", 0.01f, 0.0f, FLT_MAX, "Radius", "", 0.0f, 10.0f);
1458}
1459
1461{
1462 using namespace blender::ed::curves::pen_tool;
1463 static const EnumPropertyItem modal_items[] = {
1465 "MOVE_HANDLE",
1466 0,
1467 "Move Current Handle",
1468 "Move the current handle of the control point freely"},
1470 "MOVE_ENTIRE",
1471 0,
1472 "Move Entire Point",
1473 "Move the entire point using its handles"},
1474 {int(PenModal::SnapAngle),
1475 "SNAP_ANGLE",
1476 0,
1477 "Snap Angle",
1478 "Snap the handle angle to 45 degrees"},
1479 {0, nullptr, 0, nullptr, nullptr},
1480 };
1481
1482 wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Pen Tool Modal Map");
1483
1484 /* This function is called for each space-type and both Grease Pencil and Curves, only needs to
1485 * add map once. */
1486 if (keymap && keymap->modal_items) {
1487 return keymap;
1488 }
1489
1490 keymap = WM_modalkeymap_ensure(keyconf, "Pen Tool Modal Map", modal_items);
1491
1492 return keymap;
1493}
1494
1495} // namespace pen_tool
1496
1498{
1499 /* Identifiers. */
1500 ot->name = "Curves Pen";
1501 ot->idname = "CURVES_OT_pen";
1502 ot->description = "Construct and edit Bézier curves";
1503
1504 /* Callbacks. */
1507
1508 /* Flags. */
1509 ot->flag = OPTYPE_UNDO;
1510
1511 /* Properties. */
1513}
1514
1519
1521{
1522 wmKeyMap *keymap = pen_tool::ensure_keymap(keyconf);
1523 WM_modalkeymap_assign(keymap, "CURVES_OT_pen");
1524}
1525
1526} // namespace blender::ed::curves
#define CTX_DATA_BEGIN(C, Type, instance, member)
wmWindow * CTX_wm_window(const bContext *C)
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Main * CTX_data_main(const bContext *C)
#define CTX_DATA_END
Low-level operations for curves.
Low-level operations for curves.
support for deformation groups and hooks.
void BKE_defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
Definition deform.cc:73
Low-level operations for grease pencil.
General operations, lookup, etc. for materials.
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
ATTR_WARN_UNUSED_RESULT const size_t num
#define ELEM(...)
#define IFACE_(msgid)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1074
@ BEZIER_HANDLE_FREE
@ BEZIER_HANDLE_ALIGN
@ BEZIER_HANDLE_VECTOR
@ BEZIER_HANDLE_AUTO
@ NURBS_KNOT_MODE_ENDPOINT
@ NURBS_KNOT_MODE_NORMAL
@ NURBS_KNOT_MODE_CUSTOM
@ CURVE_HANDLE_NONE
@ CURVE_HANDLE_ALL
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
@ OP_IS_MODAL_CURSOR_REGION
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:1024
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
blender::float2 ED_view3d_project_float_v2_m4(const ARegion *region, const float co[3], const blender::float4x4 &mat)
float ED_view3d_select_dist_px()
void ED_view3d_win_to_3d(const View3D *v3d, const ARegion *region, const float depth_pt[3], const float mval[2], float r_out[3])
ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph)
blender::float4x4 ED_view3d_ob_project_mat_get(const RegionView3D *rv3d, const Object *ob)
#define C
Definition RandGen.cpp:29
#define NC_GEOM
Definition WM_types.hh:393
#define ND_DATA
Definition WM_types.hh:509
@ KM_PRESS
Definition WM_types.hh:311
@ KM_DBL_CLICK
Definition WM_types.hh:314
@ KM_RELEASE
Definition WM_types.hh:312
@ OPTYPE_UNDO
Definition WM_types.hh:182
long long int int64_t
void initialize()
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
Span< T > as_span() const
Definition BLI_array.hh:243
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
void copy_from(GSpan values)
GMutableSpan slice(const int64_t start, int64_t size) const
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
constexpr int64_t first() const
constexpr IndexRange shift(int64_t n) const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
static constexpr IndexRange from_single(const int64_t index)
constexpr IndexRange index_range() const
static constexpr IndexRange from_begin_end_inclusive(const int64_t begin, const int64_t last)
constexpr MutableSpan drop_back(const int64_t n) const
Definition BLI_span.hh:618
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
bool add(const Key &key)
Definition BLI_set.hh:248
static VArray from_single(T value, const int64_t size)
bool add(const Key &key)
void add_new(const Key &key)
int64_t size() const
void append(const T &value)
void insert(const int64_t insert_index, const T &value)
IndexRange index_range() const
MutableSpan< T > as_mutable_span()
Span< T > as_span() const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, AttrType data_type, const void *default_value=nullptr) const
MutableSpan< float3 > positions_for_write()
Array< int > point_to_curve_map() const
OffsetIndices< int > points_by_curve() const
MutableSpan< int8_t > handle_types_right_for_write()
IndexRange curves_range() const
MutableSpan< float3 > handle_positions_left_for_write()
MutableAttributeAccessor attributes_for_write()
MutableSpan< float3 > handle_positions_right_for_write()
VArray< int8_t > nurbs_knots_modes() const
std::optional< Span< float3 > > handle_positions_left() const
Span< float3 > positions() const
MutableSpan< float > radius_for_write()
std::optional< Span< float3 > > handle_positions_right() const
AttributeAccessor attributes() const
MutableSpan< int > offsets_for_write()
bool nurbs_has_custom_knots() const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
MutableSpan< bool > cyclic_for_write()
MutableSpan< int8_t > handle_types_left_for_write()
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, AttrType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
void single_point_attributes(bke::CurvesGeometry &, const int) const
float3 project(const float2 &screen_co) const
std::optional< wmOperatorStatus > initialize(bContext *C, wmOperator *, const wmEvent *)
IndexMask editable_curves(const int curves_index, IndexMaskMemory &) const
bke::CurvesGeometry & get_curves(const int curves_index) const
IndexMask all_selected_points(const int curves_index, IndexMaskMemory &memory) const
IndexMask visible_bezier_handle_points(const int curves_index, IndexMaskMemory &memory) const
void tag_curve_changed(const int curves_index) const
virtual void tag_curve_changed(int curves_index) const =0
virtual IndexRange curves_range() const =0
virtual IndexMask editable_curves(int curves_index, IndexMaskMemory &memory) const =0
wmOperatorStatus invoke(bContext *C, wmOperator *op, const wmEvent *event)
virtual IndexMask visible_bezier_handle_points(int curves_index, IndexMaskMemory &memory) const =0
virtual IndexMask all_selected_points(int curves_index, IndexMaskMemory &memory) const =0
virtual bke::CurvesGeometry & get_curves(int curves_index) const =0
virtual float3 project(const float2 &screen_co) const =0
virtual void update_view(bContext *C) const =0
float2 layer_to_screen(const float4x4 &layer_to_object, const float3 &point) const
float3 screen_to_layer(const float4x4 &layer_to_world, const float2 &screen_co, const float3 &depth_point_layer) const
wmOperatorStatus modal(bContext *C, wmOperator *op, const wmEvent *event)
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void foreach_index(Fn &&fn) const
static float is_left(const float2 &p0, const float2 &p1, const float2 &p2)
static void move_segment(const ViewContext *vc, MoveSegmentData *seg_data, const wmEvent *event)
uint pos
#define sin
VecBase< float, D > normalize(VecOp< float, D >) RET
constexpr T sign(T) RET
#define abs
VecBase< float, 3 > float3
static char ** types
Definition makesdna.cc:71
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size=4096)
void fill_index_range(MutableSpan< T > span, const T start=0)
Insertion insert(const float3 &point_prev, const float3 &handle_prev, const float3 &handle_next, const float3 &point_next, float parameter)
void gather_custom_knots(const bke::CurvesGeometry &src, const IndexMask &src_curves, int dst_curve_offset, bke::CurvesGeometry &dst)
void update_custom_knot_modes(const IndexMask &mask, const KnotsMode mode_for_regular, const KnotsMode mode_for_cyclic, bke::CurvesGeometry &curves)
auto attribute_filter_from_skip_ref(const Span< StringRef > skip)
void fill_attribute_range_default(MutableAttributeAccessor dst_attributes, AttrDomain domain, const AttributeFilter &attribute_filter, IndexRange range)
void copy_attributes(const AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, MutableAttributeAccessor dst_attributes)
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
constexpr float default_handle_px_distance
Definition curves_pen.cc:65
static void move_segment(const PenToolOperation &ptd, bke::CurvesGeometry &curves, const float4x4 &layer_to_world)
static void add_single_point_and_curve(const PenToolOperation &ptd, bke::CurvesGeometry &curves, const float4x4 &layer_to_world)
void pen_tool_common_props(wmOperatorType *ot)
static void pen_status_indicators(bContext *C, wmOperator *op)
constexpr float selection_point_overwrite_edge_distance_factor
Definition curves_pen.cc:73
static float2 snap_8_angles(const float2 &p)
static bool close_curve_and_select(const PenToolOperation &ptd, bke::CurvesGeometry &curves, const IndexRange points, const bool clear_selection)
constexpr int CURVE_HANDLE_TYPES_NUM
Definition curves_pen.cc:68
static void curves_pen_exit(bContext *C, wmOperator *op)
static bool move_handles_in_curve(const PenToolOperation &ptd, bke::CurvesGeometry &curves, const IndexMask &selection, const float4x4 &layer_to_world, const float4x4 &layer_to_object)
static const EnumPropertyItem prop_handle_types[]
Definition curves_pen.cc:54
static std::optional< bke::CurvesGeometry > extrude_curves(const PenToolOperation &ptd, const bke::CurvesGeometry &src, const float4x4 &layer_to_object, const IndexMask editable_curves)
static ClosestElement find_closest_element(const PenToolOperation &ptd, const float2 &mouse_co)
static float2 line_segment_closest_point(const float2 &pos_1, const float2 &pos_2, const float2 &pos, float &r_local_t)
static void pen_find_closest_handle(const PenToolOperation &ptd, const bke::CurvesGeometry &curves, const IndexMask &bezier_points, const float4x4 &layer_to_object, const int drawing_index, const float2 &mouse_co, ClosestElement &r_closest_element)
static void pen_find_closest_point(const PenToolOperation &ptd, const bke::CurvesGeometry &curves, const IndexMask &editable_curves, const float4x4 &layer_to_object, const int drawing_index, const float2 &mouse_co, ClosestElement &r_closest_element)
static wmOperatorStatus curves_pen_invoke(bContext *C, wmOperator *op, const wmEvent *event)
constexpr float selection_point_overwrite_edge_distance_factor_sq
Definition curves_pen.cc:75
static IndexMask retrieve_visible_bezier_handle_points(const bke::CurvesGeometry &curves, const int handle_display, IndexMaskMemory &memory)
static void invoke_curves(PenToolOperation &ptd, bContext *C, wmOperator *op, const wmEvent *event)
static float2 calculate_center_of_mass(const PenToolOperation &ptd, const bool ends_only)
constexpr float selection_distance_factor
Definition curves_pen.cc:61
static void insert_point_to_curve(const PenToolOperation &ptd, bke::CurvesGeometry &src)
constexpr float selection_distance_factor_edge
Definition curves_pen.cc:62
static wmOperatorStatus curves_pen_modal(bContext *C, wmOperator *op, const wmEvent *event)
constexpr float selection_edge_priority_factor
Definition curves_pen.cc:71
wmKeyMap * ensure_keymap(wmKeyConfig *keyconf)
static void pen_find_closest_edge_point(const PenToolOperation &ptd, const bke::CurvesGeometry &curves, const IndexMask &editable_curves, const float4x4 &layer_to_object, const int drawing_index, const float2 &mouse_co, ClosestElement &r_closest_element)
static bool has_anything_selected(const Span< Curves * > curves_ids)
IndexMask retrieve_all_selected_points(const bke::CurvesGeometry &curves, const int handle_display, IndexMaskMemory &memory)
void fill_selection_false(GMutableSpan selection)
bool object_has_editable_curves(const Main &bmain, const Object &object)
Definition curves_ops.cc:77
void ED_curves_pentool_modal_keymap(wmKeyConfig *keyconf)
void fill_selection_true(GMutableSpan selection)
Span< StringRef > get_curves_selection_attribute_names(const bke::CurvesGeometry &curves)
void ED_operatortypes_curves_pen()
static void CURVES_OT_pen(wmOperatorType *ot)
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, bke::AttrType create_type, StringRef attribute_name)
void add_single_curve(bke::CurvesGeometry &curves, const bool at_end)
T clamp(const T &a, const T &min, const T &max)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
CartesianBasis invert(const CartesianBasis &basis)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
void copy_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
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:93
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
const int status
float RNA_float_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_float_distance(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
#define FLT_MAX
Definition stdcycles.h:14
ListBase vertex_group_names
CurvesGeometry geometry
RegionView3D * rv3d
Definition ED_view3d.hh:80
wmWindow * win
Definition ED_view3d.hh:79
Object * obact
Definition ED_view3d.hh:75
bool is_closer(const float new_distance_squared, const ElementMode new_element_mode, const float threshold_distance) const
Definition curves_pen.cc:79
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int xy[2]
Definition WM_types.hh:761
int mval[2]
Definition WM_types.hh:763
int prev_xy[2]
Definition WM_types.hh:820
const void * modal_items
struct ReportList * reports
struct wmOperatorType * type
struct PointerRNA * ptr
i
Definition text_draw.cc:230
void WM_cursor_modal_set(wmWindow *win, int val)
void WM_cursor_modal_restore(wmWindow *win)
@ WM_CURSOR_CROSS
Definition wm_cursors.hh:26
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
@ EVT_MODAL_MAP
@ EVENT_NONE
@ LEFTMOUSE
wmOperatorType * ot
Definition wm_files.cc:4237
wmKeyMap * WM_modalkeymap_ensure(wmKeyConfig *keyconf, const char *idname, const EnumPropertyItem *items)
Definition wm_keymap.cc:932
void WM_modalkeymap_assign(wmKeyMap *km, const char *opname)
wmKeyMap * WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
Definition wm_keymap.cc:959
void WM_operator_properties_mouse_select(wmOperatorType *ot)
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))