Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format1
-rw-r--r--release/datafiles/icons/ops.curve.draw.datbin2564 -> 2402 bytes
-rw-r--r--release/datafiles/icons/ops.curve.pen.datbin0 -> 3374 bytes
m---------release/datafiles/locale0
m---------release/scripts/addons0
-rw-r--r--release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py1
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py42
-rw-r--r--release/scripts/startup/bl_ui/space_toolsystem_toolbar.py17
-rw-r--r--source/blender/editors/curve/CMakeLists.txt1
-rw-r--r--source/blender/editors/curve/curve_intern.h25
-rw-r--r--source/blender/editors/curve/curve_ops.c3
-rw-r--r--source/blender/editors/curve/editcurve.c142
-rw-r--r--source/blender/editors/curve/editcurve_pen.c1877
-rw-r--r--source/blender/editors/curve/editcurve_query.c29
-rw-r--r--source/blender/editors/datafiles/CMakeLists.txt1
-rw-r--r--source/blender/editors/include/ED_curve.h7
m---------source/tools0
17 files changed, 2075 insertions, 71 deletions
diff --git a/.clang-format b/.clang-format
index 41f828787b2..44c8cb90b15 100644
--- a/.clang-format
+++ b/.clang-format
@@ -205,6 +205,7 @@ ForEachMacros:
- FOREACH_SCENE_COLLECTION_BEGIN
- FOREACH_SCENE_OBJECT_BEGIN
- FOREACH_SELECTED_BASE_BEGIN
+ - FOREACH_SELECTED_BEZT_BEGIN
- FOREACH_SELECTED_EDITABLE_OBJECT_BEGIN
- FOREACH_SELECTED_OBJECT_BEGIN
- FOREACH_TRANS_DATA_CONTAINER
diff --git a/release/datafiles/icons/ops.curve.draw.dat b/release/datafiles/icons/ops.curve.draw.dat
index cf2c8e31bcb..da4c7424756 100644
--- a/release/datafiles/icons/ops.curve.draw.dat
+++ b/release/datafiles/icons/ops.curve.draw.dat
Binary files differ
diff --git a/release/datafiles/icons/ops.curve.pen.dat b/release/datafiles/icons/ops.curve.pen.dat
new file mode 100644
index 00000000000..1007f7ea604
--- /dev/null
+++ b/release/datafiles/icons/ops.curve.pen.dat
Binary files differ
diff --git a/release/datafiles/locale b/release/datafiles/locale
-Subproject 2a5095eed3028e91624d27ca93e4c65f572b809
+Subproject 716dc02ec30c0810513f7b4adc4ae865ae50c4e
diff --git a/release/scripts/addons b/release/scripts/addons
-Subproject 67f1fbca1482d9d9362a4001332e785c3fd5d23
+Subproject 787ea78f7fa6f0373d80ba1247768402df93f8a
diff --git a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
index 938849d8e6e..6389efba049 100644
--- a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
+++ b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
@@ -100,6 +100,7 @@ _km_hierarchy = [
('Paint Stroke Modal', 'EMPTY', 'WINDOW', []),
('Sculpt Expand Modal', 'EMPTY', 'WINDOW', []),
('Paint Curve', 'EMPTY', 'WINDOW', []),
+ ('Curve Pen Modal Map', 'EMPTY', 'WINDOW', []),
('Object Non-modal', 'EMPTY', 'WINDOW', []), # mode change
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index b626f6a237a..c9a378a101f 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -6158,6 +6158,25 @@ def km_sculpt_expand_modal(_params):
return keymap
+def km_curve_pen_modal_map(_params):
+ items = []
+ keymap = (
+ "Curve Pen Modal Map",
+ {"space_type": 'EMPTY', "region_type": 'WINDOW', "modal": True},
+ {"items": items},
+ )
+
+ items.extend([
+ ("FREE_ALIGN_TOGGLE", {"type": 'LEFT_SHIFT', "value": 'ANY', "any": True}, None),
+ ("MOVE_ADJACENT", {"type": 'LEFT_CTRL', "value": 'ANY', "any": True}, None),
+ ("MOVE_ENTIRE", {"type": 'SPACE', "value": 'ANY', "any": True}, None),
+ ("LOCK_ANGLE", {"type": 'LEFT_ALT', "value": 'ANY', "any": True}, None),
+ ("LINK_HANDLES", {"type": 'RIGHT_CTRL', "value": 'PRESS', "any": True}, None),
+ ])
+
+ return keymap
+
+
# Fallback for gizmos that don't have custom a custom key-map.
def km_generic_gizmo(_params):
keymap = (
@@ -7078,6 +7097,27 @@ def km_3d_view_tool_edit_curve_draw(params):
)
+def km_3d_view_tool_edit_curve_pen(params):
+ return (
+ "3D View Tool: Edit Curve, Curve Pen",
+ {"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
+ {"items": [
+ ("curve.pen", {"type": params.tool_mouse, "value": 'PRESS'},
+ {"properties": [
+ ("extrude_point", True),
+ ("move_segment", True),
+ ("select_point", True),
+ ("move_point", True),
+ ("close_spline_method", "ON_CLICK"),
+ ]}),
+ ("curve.pen", {"type": params.tool_mouse, "value": 'PRESS', "ctrl": True},
+ {"properties": [("insert_point", True), ("delete_point", True)]}),
+ ("curve.pen", {"type": params.tool_mouse, "value": 'DOUBLE_CLICK'},
+ {"properties": [("toggle_vector", True), ("cycle_handle_type", True),]}),
+ ]},
+ )
+
+
def km_3d_view_tool_edit_curve_tilt(params):
return (
"3D View Tool: Edit Curve, Tilt",
@@ -7882,6 +7922,7 @@ def generate_keymaps(params=None):
km_view3d_dolly_modal(params),
km_paint_stroke_modal(params),
km_sculpt_expand_modal(params),
+ km_curve_pen_modal_map(params),
# Gizmos.
km_generic_gizmo(params),
@@ -7960,6 +8001,7 @@ def generate_keymaps(params=None):
km_3d_view_tool_edit_mesh_rip_region(params),
km_3d_view_tool_edit_mesh_rip_edge(params),
km_3d_view_tool_edit_curve_draw(params),
+ km_3d_view_tool_edit_curve_pen(params),
km_3d_view_tool_edit_curve_radius(params),
km_3d_view_tool_edit_curve_tilt(params),
km_3d_view_tool_edit_curve_randomize(params),
diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
index aec3ec63a0c..fc621c5b51e 100644
--- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
@@ -1202,6 +1202,22 @@ class _defs_edit_curve:
)
@ToolDef.from_fn
+ def pen():
+ def draw_settings(_context, layout, tool):
+ props = tool.operator_properties("curve.pen")
+ layout.prop(props, "close_spline")
+ layout.prop(props, "extrude_handle")
+ return dict(
+ idname="builtin.pen",
+ label="Curve Pen",
+ cursor='CROSSHAIR',
+ icon="ops.curve.pen",
+ widget=None,
+ keymap=(),
+ draw_settings=draw_settings,
+ )
+
+ @ToolDef.from_fn
def tilt():
return dict(
idname="builtin.tilt",
@@ -2881,6 +2897,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
*_tools_default,
None,
_defs_edit_curve.draw,
+ _defs_edit_curve.pen,
(
_defs_edit_curve.extrude,
_defs_edit_curve.extrude_cursor,
diff --git a/source/blender/editors/curve/CMakeLists.txt b/source/blender/editors/curve/CMakeLists.txt
index 397725f770c..b21ae305dbe 100644
--- a/source/blender/editors/curve/CMakeLists.txt
+++ b/source/blender/editors/curve/CMakeLists.txt
@@ -25,6 +25,7 @@ set(SRC
editcurve_paint.c
editcurve_query.c
editcurve_select.c
+ editcurve_pen.c
editcurve_undo.c
editfont.c
editfont_undo.c
diff --git a/source/blender/editors/curve/curve_intern.h b/source/blender/editors/curve/curve_intern.h
index 8bfabe23ea1..09a0dbc541f 100644
--- a/source/blender/editors/curve/curve_intern.h
+++ b/source/blender/editors/curve/curve_intern.h
@@ -131,6 +131,16 @@ void CURVE_OT_match_texture_space(struct wmOperatorType *ot);
struct GHash *ED_curve_keyindex_hash_duplicate(struct GHash *keyindex);
void ED_curve_keyindex_update_nurb(struct EditNurb *editnurb, struct Nurb *nu, struct Nurb *newnu);
+/* exported for editcurve_pen.c */
+int ed_editcurve_addvert(Curve *cu, EditNurb *editnurb, View3D *v3d, const float location_init[3]);
+bool curve_toggle_cyclic(View3D *v3d, ListBase *editnurb, int direction);
+void ed_dissolve_bez_segment(BezTriple *bezt_prev,
+ BezTriple *bezt_next,
+ const Nurb *nu,
+ const Curve *cu,
+ const int span_len,
+ const int span_step[2]);
+
/* helper functions */
void ed_editnurb_translate_flag(struct ListBase *editnurb,
uint8_t flag,
@@ -189,8 +199,23 @@ bool ED_curve_pick_vert(struct ViewContext *vc,
struct BPoint **r_bp,
short *r_handle,
struct Base **r_base);
+/**
+ * \param sel_dist_mul: A multiplier on the default select distance.
+ */
+bool ED_curve_pick_vert_ex(struct ViewContext *vc,
+ short sel,
+ float dist_px,
+ struct Nurb **r_nurb,
+ struct BezTriple **r_bezt,
+ struct BPoint **r_bp,
+ short *r_handle,
+ struct Base **r_base);
void ED_curve_nurb_vert_selected_find(
Curve *cu, View3D *v3d, Nurb **r_nu, BezTriple **r_bezt, BPoint **r_bp);
/* editcurve_paint.c */
void CURVE_OT_draw(struct wmOperatorType *ot);
+
+/* editcurve_pen.c */
+void CURVE_OT_pen(struct wmOperatorType *ot);
+struct wmKeyMap *curve_pen_modal_keymap(struct wmKeyConfig *keyconf);
diff --git a/source/blender/editors/curve/curve_ops.c b/source/blender/editors/curve/curve_ops.c
index 850b0cce955..2417947a25e 100644
--- a/source/blender/editors/curve/curve_ops.c
+++ b/source/blender/editors/curve/curve_ops.c
@@ -111,6 +111,7 @@ void ED_operatortypes_curve(void)
WM_operatortype_append(CURVE_OT_spin);
WM_operatortype_append(CURVE_OT_vertex_add);
WM_operatortype_append(CURVE_OT_draw);
+ WM_operatortype_append(CURVE_OT_pen);
WM_operatortype_append(CURVE_OT_extrude);
WM_operatortype_append(CURVE_OT_cyclic_toggle);
@@ -150,4 +151,6 @@ void ED_keymap_curve(wmKeyConfig *keyconf)
/* only set in editmode curve, by space_view3d listener */
keymap = WM_keymap_ensure(keyconf, "Curve", 0, 0);
keymap->poll = ED_operator_editsurfcurve;
+
+ curve_pen_modal_keymap(keyconf);
}
diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c
index 38c14391273..c7c2468a6e4 100644
--- a/source/blender/editors/curve/editcurve.c
+++ b/source/blender/editors/curve/editcurve.c
@@ -4727,6 +4727,14 @@ bool ED_curve_editnurb_select_pick(bContext *C,
const int mval[2],
const struct SelectPick_Params *params)
{
+ return ED_curve_editnurb_select_pick_ex(C, mval, 1.0f, params);
+}
+
+bool ED_curve_editnurb_select_pick_ex(bContext *C,
+ const int mval[2],
+ const float sel_dist_mul,
+ const struct SelectPick_Params *params)
+{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc;
Nurb *nu;
@@ -4740,7 +4748,8 @@ bool ED_curve_editnurb_select_pick(bContext *C,
ED_view3d_viewcontext_init(C, &vc, depsgraph);
copy_v2_v2_int(vc.mval, mval);
- bool found = ED_curve_pick_vert(&vc, 1, &nu, &bezt, &bp, &hand, &basact);
+ bool found = ED_curve_pick_vert_ex(
+ &vc, 1, sel_dist_mul * ED_view3d_select_dist_px(), &nu, &bezt, &bp, &hand, &basact);
if (params->sel_op == SEL_OP_SET) {
if ((found && params->select_passthrough) &&
@@ -5344,10 +5353,7 @@ static bool ed_editcurve_extrude(Curve *cu, EditNurb *editnurb, View3D *v3d)
/** \name Add Vertex Operator
* \{ */
-static int ed_editcurve_addvert(Curve *cu,
- EditNurb *editnurb,
- View3D *v3d,
- const float location_init[3])
+int ed_editcurve_addvert(Curve *cu, EditNurb *editnurb, View3D *v3d, const float location_init[3])
{
float center[3];
float temp[3];
@@ -5719,7 +5725,7 @@ void CURVE_OT_extrude(wmOperatorType *ot)
/** \name Make Cyclic Operator
* \{ */
-static bool curve_toggle_cyclic(View3D *v3d, ListBase *editnurb, int direction)
+bool curve_toggle_cyclic(View3D *v3d, ListBase *editnurb, int direction)
{
BezTriple *bezt;
BPoint *bp;
@@ -6502,6 +6508,70 @@ static bool test_bezt_is_sel_any(const void *bezt_v, void *user_data)
return BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt);
}
+void ed_dissolve_bez_segment(BezTriple *bezt_prev,
+ BezTriple *bezt_next,
+ const Nurb *nu,
+ const Curve *cu,
+ const int span_len,
+ const int span_step[2])
+{
+ int i_span_edge_len = span_len + 1;
+ const int dims = 3;
+
+ const int points_len = ((cu->resolu - 1) * i_span_edge_len) + 1;
+ float *points = MEM_mallocN(points_len * dims * sizeof(float), __func__);
+ float *points_stride = points;
+ const int points_stride_len = (cu->resolu - 1);
+
+ for (int segment = 0; segment < i_span_edge_len; segment++) {
+ BezTriple *bezt_a = &nu->bezt[mod_i((span_step[0] + segment) - 1, nu->pntsu)];
+ BezTriple *bezt_b = &nu->bezt[mod_i((span_step[0] + segment), nu->pntsu)];
+
+ for (int axis = 0; axis < dims; axis++) {
+ BKE_curve_forward_diff_bezier(bezt_a->vec[1][axis],
+ bezt_a->vec[2][axis],
+ bezt_b->vec[0][axis],
+ bezt_b->vec[1][axis],
+ points_stride + axis,
+ points_stride_len,
+ dims * sizeof(float));
+ }
+
+ points_stride += dims * points_stride_len;
+ }
+
+ BLI_assert(points_stride + dims == points + (points_len * dims));
+
+ float tan_l[3], tan_r[3], error_sq_dummy;
+ uint error_index_dummy;
+
+ sub_v3_v3v3(tan_l, bezt_prev->vec[1], bezt_prev->vec[2]);
+ normalize_v3(tan_l);
+ sub_v3_v3v3(tan_r, bezt_next->vec[0], bezt_next->vec[1]);
+ normalize_v3(tan_r);
+
+ curve_fit_cubic_to_points_single_fl(points,
+ points_len,
+ NULL,
+ dims,
+ FLT_EPSILON,
+ tan_l,
+ tan_r,
+ bezt_prev->vec[2],
+ bezt_next->vec[0],
+ &error_sq_dummy,
+ &error_index_dummy);
+
+ if (!ELEM(bezt_prev->h2, HD_FREE, HD_ALIGN)) {
+ bezt_prev->h2 = (bezt_prev->h2 == HD_VECT) ? HD_FREE : HD_ALIGN;
+ }
+ if (!ELEM(bezt_next->h1, HD_FREE, HD_ALIGN)) {
+ bezt_next->h1 = (bezt_next->h1 == HD_VECT) ? HD_FREE : HD_ALIGN;
+ }
+
+ MEM_freeN(points);
+}
+
static int curve_dissolve_exec(bContext *C, wmOperator *UNUSED(op))
{
Main *bmain = CTX_data_main(C);
@@ -6523,8 +6593,8 @@ static int curve_dissolve_exec(bContext *C, wmOperator *UNUSED(op))
LISTBASE_FOREACH (Nurb *, nu, editnurb) {
if ((nu->type == CU_BEZIER) && (nu->pntsu > 2)) {
- uint span_step[2] = {nu->pntsu, nu->pntsu};
- uint span_len;
+ int span_step[2] = {nu->pntsu, nu->pntsu};
+ int span_len;
while (BLI_array_iter_span(nu->bezt,
nu->pntsu,
@@ -6537,61 +6607,7 @@ static int curve_dissolve_exec(bContext *C, wmOperator *UNUSED(op))
BezTriple *bezt_prev = &nu->bezt[mod_i(span_step[0] - 1, nu->pntsu)];
BezTriple *bezt_next = &nu->bezt[mod_i(span_step[1] + 1, nu->pntsu)];
- int i_span_edge_len = span_len + 1;
- const uint dims = 3;
-
- const uint points_len = ((cu->resolu - 1) * i_span_edge_len) + 1;
- float *points = MEM_mallocN(points_len * dims * sizeof(float), __func__);
- float *points_stride = points;
- const int points_stride_len = (cu->resolu - 1);
-
- for (int segment = 0; segment < i_span_edge_len; segment++) {
- BezTriple *bezt_a = &nu->bezt[mod_i((span_step[0] + segment) - 1, nu->pntsu)];
- BezTriple *bezt_b = &nu->bezt[mod_i((span_step[0] + segment), nu->pntsu)];
-
- for (int axis = 0; axis < dims; axis++) {
- BKE_curve_forward_diff_bezier(bezt_a->vec[1][axis],
- bezt_a->vec[2][axis],
- bezt_b->vec[0][axis],
- bezt_b->vec[1][axis],
- points_stride + axis,
- points_stride_len,
- dims * sizeof(float));
- }
-
- points_stride += dims * points_stride_len;
- }
-
- BLI_assert(points_stride + dims == points + (points_len * dims));
-
- float tan_l[3], tan_r[3], error_sq_dummy;
- uint error_index_dummy;
-
- sub_v3_v3v3(tan_l, bezt_prev->vec[1], bezt_prev->vec[2]);
- normalize_v3(tan_l);
- sub_v3_v3v3(tan_r, bezt_next->vec[0], bezt_next->vec[1]);
- normalize_v3(tan_r);
-
- curve_fit_cubic_to_points_single_fl(points,
- points_len,
- NULL,
- dims,
- FLT_EPSILON,
- tan_l,
- tan_r,
- bezt_prev->vec[2],
- bezt_next->vec[0],
- &error_sq_dummy,
- &error_index_dummy);
-
- if (!ELEM(bezt_prev->h2, HD_FREE, HD_ALIGN)) {
- bezt_prev->h2 = (bezt_prev->h2 == HD_VECT) ? HD_FREE : HD_ALIGN;
- }
- if (!ELEM(bezt_next->h1, HD_FREE, HD_ALIGN)) {
- bezt_next->h1 = (bezt_next->h1 == HD_VECT) ? HD_FREE : HD_ALIGN;
- }
-
- MEM_freeN(points);
+ ed_dissolve_bez_segment(bezt_prev, bezt_next, nu, cu, span_len, span_step);
}
}
}
diff --git a/source/blender/editors/curve/editcurve_pen.c b/source/blender/editors/curve/editcurve_pen.c
new file mode 100644
index 00000000000..a848fdb56ca
--- /dev/null
+++ b/source/blender/editors/curve/editcurve_pen.c
@@ -0,0 +1,1877 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2001-2002 NaN Holding BV. All rights reserved. */
+
+/** \file
+ * \ingroup edcurve
+ */
+
+#include "DNA_curve_types.h"
+#include "DNA_windowmanager_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+
+#include "BKE_context.h"
+#include "BKE_curve.h"
+
+#include "DEG_depsgraph.h"
+
+#include "WM_api.h"
+
+#include "ED_curve.h"
+#include "ED_screen.h"
+#include "ED_select_utils.h"
+#include "ED_view3d.h"
+
+#include "BKE_object.h"
+
+#include "curve_intern.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "float.h"
+
+#define FOREACH_SELECTED_BEZT_BEGIN(bezt, nurbs) \
+ LISTBASE_FOREACH (Nurb *, nu, nurbs) { \
+ if (nu->type == CU_BEZIER) { \
+ for (int i = 0; i < nu->pntsu; i++) { \
+ BezTriple *bezt = nu->bezt + i; \
+ if (BEZT_ISSEL_ANY(bezt) && !bezt->hide) {
+
+#define FOREACH_SELECTED_BEZT_END \
+ } \
+ } \
+ } \
+ BKE_nurb_handles_calc(nu); \
+ } \
+ ((void)0)
+
+/* Used to scale the default select distance. */
+#define SEL_DIST_FACTOR 0.2f
+
+/**
+ * Data structure to keep track of details about the cut location
+ */
+typedef struct CutData {
+ /* Index of the last #BezTriple or BPoint before the cut. */
+ int bezt_index, bp_index;
+ /* Nurb to which the cut belongs to. */
+ Nurb *nurb;
+ /* Minimum distance to curve from mouse location. */
+ float min_dist;
+ /* Fraction of segments after which the new point divides the curve segment. */
+ float parameter;
+ /* Whether the currently identified closest point has any vertices before/after it. */
+ bool has_prev, has_next;
+ /* Locations of adjacent vertices and cut location. */
+ float prev_loc[3], cut_loc[3], next_loc[3];
+ /* Mouse location in floats. */
+ float mval[2];
+} CutData;
+
+/**
+ * Data required for segment altering functionality.
+ */
+typedef struct MoveSegmentData {
+ /* Nurb being altered. */
+ Nurb *nu;
+ /* Index of the #BezTriple before the segment. */
+ int bezt_index;
+ /* Fraction along the segment at which mouse was pressed. */
+ float t;
+} MoveSegmentData;
+
+typedef struct CurvePenData {
+ MoveSegmentData *msd;
+ /* Whether the mouse is clicking and dragging. */
+ bool dragging;
+ /* Whether a new point was added at the beginning of tool execution. */
+ bool new_point;
+ /* Whether a segment is being altered by click and drag. */
+ bool spline_nearby;
+ /* Whether some action was done. Used for select. */
+ bool acted;
+ /* Whether a point was found underneath the mouse. */
+ bool found_point;
+ /* Whether multiple selected points should be moved. */
+ bool multi_point;
+ /* Whether a point has already been selected. */
+ bool selection_made;
+ /* Whether a shift-click occured. */
+ bool select_multi;
+
+ /* Whether the current handle type of the moved handle is free. */
+ bool free_toggle;
+ /* Whether the shortcut for moving the adjacent handle is pressed. */
+ bool move_adjacent;
+ /* Whether the current state of the moved handle is linked. */
+ bool link_handles;
+ /* Whether the current state of the handle angle is locked. */
+ bool lock_angle;
+ /* Whether the shortcut for moving the entire point is pressed. */
+ bool move_entire;
+
+ /* Data about found point. Used for closing splines. */
+ Nurb *nu;
+ BezTriple *bezt;
+ BPoint *bp;
+} CurvePenData;
+
+static const EnumPropertyItem prop_handle_types[] = {
+ {HD_AUTO, "AUTO", 0, "Auto", ""},
+ {HD_VECT, "VECTOR", 0, "Vector", ""},
+ {0, NULL, 0, NULL, NULL},
+};
+
+typedef enum eClose_opt {
+ OFF = 0,
+ ON_PRESS = 1,
+ ON_CLICK = 2,
+} eClose_opt;
+
+static const EnumPropertyItem prop_close_spline_method[] = {
+ {OFF, "OFF", 0, "None", ""},
+ {ON_PRESS, "ON_PRESS", 0, "On Press", "Move handles after closing the spline"},
+ {ON_CLICK, "ON_CLICK", 0, "On Click", "Spline closes on release if not dragged"},
+ {0, NULL, 0, NULL, NULL},
+};
+
+static void update_location_for_2d_curve(const ViewContext *vc, float location[3])
+{
+ Curve *cu = vc->obedit->data;
+ if (CU_IS_2D(cu)) {
+ const float eps = 1e-6f;
+
+ /* Get the view vector to `location`. */
+ float view_dir[3];
+ ED_view3d_global_to_vector(vc->rv3d, location, view_dir);
+
+ /* Get the plane. */
+ float plane[4];
+ /* Only normalize to avoid precision errors. */
+ normalize_v3_v3(plane, vc->obedit->obmat[2]);
+ plane[3] = -dot_v3v3(plane, vc->obedit->obmat[3]);
+
+ if (fabsf(dot_v3v3(view_dir, plane)) < eps) {
+ /* Can't project on an aligned plane. */
+ }
+ else {
+ float lambda;
+ if (isect_ray_plane_v3(location, view_dir, plane, &lambda, false)) {
+ /* Check if we're behind the viewport */
+ float location_test[3];
+ madd_v3_v3v3fl(location_test, location, view_dir, lambda);
+ if ((vc->rv3d->is_persp == false) ||
+ (mul_project_m4_v3_zfac(vc->rv3d->persmat, location_test) > 0.0f)) {
+ copy_v3_v3(location, location_test);
+ }
+ }
+ }
+ }
+
+ float imat[4][4];
+ invert_m4_m4(imat, vc->obedit->obmat);
+ mul_m4_v3(imat, location);
+
+ if (CU_IS_2D(cu)) {
+ location[2] = 0.0f;
+ }
+}
+
+static void screenspace_to_worldspace(const ViewContext *vc,
+ const float pos_2d[2],
+ const float depth[3],
+ float r_pos_3d[3])
+{
+ mul_v3_m4v3(r_pos_3d, vc->obedit->obmat, depth);
+ ED_view3d_win_to_3d(vc->v3d, vc->region, r_pos_3d, pos_2d, r_pos_3d);
+ update_location_for_2d_curve(vc, r_pos_3d);
+}
+
+static void screenspace_to_worldspace_int(const ViewContext *vc,
+ const int pos_2d[2],
+ const float depth[3],
+ float r_pos_3d[3])
+{
+ const float pos_2d_fl[2] = {UNPACK2(pos_2d)};
+ screenspace_to_worldspace(vc, pos_2d_fl, depth, r_pos_3d);
+}
+
+static bool worldspace_to_screenspace(const ViewContext *vc,
+ const float pos_3d[3],
+ float r_pos_2d[2])
+{
+ return ED_view3d_project_float_object(
+ vc->region, pos_3d, r_pos_2d, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) ==
+ V3D_PROJ_RET_OK;
+}
+
+static void move_bezt_by_displacement(BezTriple *bezt, const float disp_3d[3])
+{
+ add_v3_v3(bezt->vec[0], disp_3d);
+ add_v3_v3(bezt->vec[1], disp_3d);
+ add_v3_v3(bezt->vec[2], disp_3d);
+}
+
+/**
+ * Move entire control point to given worldspace location.
+ */
+static void move_bezt_to_location(BezTriple *bezt, const float location[3])
+{
+ float disp_3d[3];
+ sub_v3_v3v3(disp_3d, location, bezt->vec[1]);
+ move_bezt_by_displacement(bezt, disp_3d);
+}
+
+/**
+ * Alter handle types to allow free movement (Set handles to #FREE or #ALIGN).
+ */
+static void remove_handle_movement_constraints(BezTriple *bezt, const bool f1, const bool f3)
+{
+ if (f1) {
+ if (bezt->h1 == HD_VECT) {
+ bezt->h1 = HD_FREE;
+ }
+ if (bezt->h1 == HD_AUTO) {
+ bezt->h1 = HD_ALIGN;
+ bezt->h2 = HD_ALIGN;
+ }
+ }
+ if (f3) {
+ if (bezt->h2 == HD_VECT) {
+ bezt->h2 = HD_FREE;
+ }
+ if (bezt->h2 == HD_AUTO) {
+ bezt->h1 = HD_ALIGN;
+ bezt->h2 = HD_ALIGN;
+ }
+ }
+}
+
+static void move_bezt_handle_or_vertex_by_displacement(const ViewContext *vc,
+ BezTriple *bezt,
+ const int bezt_idx,
+ const float disp_2d[2],
+ const float distance,
+ const bool link_handles,
+ const bool lock_angle)
+{
+ if (lock_angle) {
+ float disp_3d[3];
+ sub_v3_v3v3(disp_3d, bezt->vec[bezt_idx], bezt->vec[1]);
+ normalize_v3_length(disp_3d, distance);
+ add_v3_v3v3(bezt->vec[bezt_idx], bezt->vec[1], disp_3d);
+ }
+ else {
+ float pos[2], dst[2];
+ worldspace_to_screenspace(vc, bezt->vec[bezt_idx], pos);
+ add_v2_v2v2(dst, pos, disp_2d);
+
+ float location[3];
+ screenspace_to_worldspace(vc, dst, bezt->vec[bezt_idx], location);
+ if (bezt_idx == 1) {
+ move_bezt_to_location(bezt, location);
+ }
+ else {
+ copy_v3_v3(bezt->vec[bezt_idx], location);
+ if (bezt->h1 == HD_ALIGN && bezt->h2 == HD_ALIGN) {
+ /* Move the handle on the opposite side. */
+ float handle_vec[3];
+ sub_v3_v3v3(handle_vec, bezt->vec[1], location);
+ const int other_handle = bezt_idx == 2 ? 0 : 2;
+ normalize_v3_length(handle_vec, len_v3v3(bezt->vec[1], bezt->vec[other_handle]));
+ add_v3_v3v3(bezt->vec[other_handle], bezt->vec[1], handle_vec);
+ }
+ }
+
+ if (link_handles) {
+ float handle[3];
+ sub_v3_v3v3(handle, bezt->vec[1], bezt->vec[bezt_idx]);
+ add_v3_v3v3(bezt->vec[(bezt_idx + 2) % 4], bezt->vec[1], handle);
+ }
+ }
+}
+
+static void move_bp_to_location(const ViewContext *vc, BPoint *bp, const float mval[2])
+{
+ float location[3];
+ screenspace_to_worldspace(vc, mval, bp->vec, location);
+
+ copy_v3_v3(bp->vec, location);
+}
+
+/**
+ * Get the average position of selected points.
+ * \param mid_only: Use only the middle point of the three points on a #BezTriple.
+ * \param bezt_only: Use only points of Bezier splines.
+ */
+static bool get_selected_center(const ListBase *nurbs,
+ const bool mid_only,
+ const bool bezt_only,
+ float r_center[3])
+{
+ int end_count = 0;
+ zero_v3(r_center);
+ LISTBASE_FOREACH (Nurb *, nu, nurbs) {
+ if (nu->type == CU_BEZIER) {
+ for (int i = 0; i < nu->pntsu; i++) {
+ BezTriple *bezt = nu->bezt + i;
+ if (bezt->hide) {
+ continue;
+ }
+ if (mid_only) {
+ if (BEZT_ISSEL_ANY(bezt)) {
+ add_v3_v3(r_center, bezt->vec[1]);
+ end_count++;
+ }
+ }
+ else {
+ if (BEZT_ISSEL_IDX(bezt, 1)) {
+ add_v3_v3(r_center, bezt->vec[1]);
+ end_count++;
+ }
+ else if (BEZT_ISSEL_IDX(bezt, 0)) {
+ add_v3_v3(r_center, bezt->vec[0]);
+ end_count++;
+ }
+ else if (BEZT_ISSEL_IDX(bezt, 2)) {
+ add_v3_v3(r_center, bezt->vec[2]);
+ end_count++;
+ }
+ }
+ }
+ }
+ else if (!bezt_only) {
+ for (int i = 0; i < nu->pntsu; i++) {
+ if (!nu->bp->hide && (nu->bp + i)->f1 & SELECT) {
+ add_v3_v3(r_center, (nu->bp + i)->vec);
+ end_count++;
+ }
+ }
+ }
+ }
+ if (end_count) {
+ mul_v3_fl(r_center, 1.0f / end_count);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Move all selected points by an amount equivalent to the distance moved by mouse.
+ */
+static void move_all_selected_points(const ViewContext *vc,
+ const wmEvent *event,
+ CurvePenData *cpd,
+ ListBase *nurbs,
+ const bool bezt_only)
+{
+ const float mval[2] = {UNPACK2(event->xy)};
+ const float prev_mval[2] = {UNPACK2(event->prev_xy)};
+ float disp_2d[2];
+ sub_v2_v2v2(disp_2d, mval, prev_mval);
+
+ const bool link_handles = cpd->link_handles && !cpd->free_toggle;
+ const bool lock_angle = cpd->lock_angle;
+ const bool move_entire = cpd->move_entire;
+
+ float distance = 0.0f;
+ if (lock_angle) {
+ float mval_3d[3], center_mid[3];
+ get_selected_center(nurbs, true, true, center_mid);
+ screenspace_to_worldspace_int(vc, event->mval, center_mid, mval_3d);
+ distance = len_v3v3(center_mid, mval_3d);
+ }
+
+ LISTBASE_FOREACH (Nurb *, nu, nurbs) {
+ if (nu->type == CU_BEZIER) {
+ for (int i = 0; i < nu->pntsu; i++) {
+ BezTriple *bezt = nu->bezt + i;
+ if (bezt->hide) {
+ continue;
+ }
+ if (BEZT_ISSEL_IDX(bezt, 1) || (move_entire && BEZT_ISSEL_ANY(bezt))) {
+ move_bezt_handle_or_vertex_by_displacement(vc, bezt, 1, disp_2d, 0.0f, false, false);
+ }
+ else {
+ remove_handle_movement_constraints(
+ bezt, BEZT_ISSEL_IDX(bezt, 0), BEZT_ISSEL_IDX(bezt, 2));
+ if (BEZT_ISSEL_IDX(bezt, 0)) {
+ move_bezt_handle_or_vertex_by_displacement(
+ vc, bezt, 0, disp_2d, distance, link_handles, lock_angle);
+ }
+ else if (BEZT_ISSEL_IDX(bezt, 2)) {
+ move_bezt_handle_or_vertex_by_displacement(
+ vc, bezt, 2, disp_2d, distance, link_handles, lock_angle);
+ }
+ }
+ }
+ BKE_nurb_handles_calc(nu);
+ }
+ else if (!bezt_only) {
+ for (int i = 0; i < nu->pntsu; i++) {
+ BPoint *bp = nu->bp + i;
+ if (!bp->hide && (bp->f1 & SELECT)) {
+ float pos[2], dst[2];
+ worldspace_to_screenspace(vc, bp->vec, pos);
+ add_v2_v2v2(dst, pos, disp_2d);
+ move_bp_to_location(vc, bp, dst);
+ }
+ }
+ }
+ }
+}
+
+static int get_nurb_index(const ListBase *nurbs, const Nurb *nurb)
+{
+ return BLI_findindex(nurbs, nurb);
+}
+
+static void delete_nurb(Curve *cu, Nurb *nu)
+{
+ EditNurb *editnurb = cu->editnurb;
+ ListBase *nurbs = &editnurb->nurbs;
+ const int nu_index = get_nurb_index(nurbs, nu);
+ if (cu->actnu == nu_index) {
+ BKE_curve_nurb_vert_active_set(cu, NULL, NULL);
+ }
+
+ BLI_remlink(nurbs, nu);
+ BKE_nurb_free(nu);
+}
+
+static void delete_bezt_from_nurb(const BezTriple *bezt, Nurb *nu, EditNurb *editnurb)
+{
+ BLI_assert(nu->type == CU_BEZIER);
+ const int index = BKE_curve_nurb_vert_index_get(nu, bezt);
+ nu->pntsu -= 1;
+ memmove(nu->bezt + index, nu->bezt + index + 1, (nu->pntsu - index) * sizeof(BezTriple));
+ BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, nu->bezt + index);
+}
+
+static void delete_bp_from_nurb(const BPoint *bp, Nurb *nu, EditNurb *editnurb)
+{
+ BLI_assert(nu->type == CU_NURBS || nu->type == CU_POLY);
+ const int index = BKE_curve_nurb_vert_index_get(nu, bp);
+ nu->pntsu -= 1;
+ memmove(nu->bp + index, nu->bp + index + 1, (nu->pntsu - index) * sizeof(BPoint));
+ BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, nu->bp + index);
+}
+
+/**
+ * Get closest vertex in all nurbs in given #ListBase to a given point.
+ * Returns true if point is found.
+ */
+static bool get_closest_vertex_to_point_in_nurbs(const ViewContext *vc,
+ const ListBase *nurbs,
+ const float point[2],
+ Nurb **r_nu,
+ BezTriple **r_bezt,
+ BPoint **r_bp,
+ int *r_bezt_idx)
+{
+ *r_nu = NULL;
+ *r_bezt = NULL;
+ *r_bp = NULL;
+
+ float min_dist_bezt = FLT_MAX;
+ int closest_handle = 0;
+ BezTriple *closest_bezt = NULL;
+ Nurb *closest_bezt_nu = NULL;
+
+ float min_dist_bp = FLT_MAX;
+ BPoint *closest_bp = NULL;
+ Nurb *closest_bp_nu = NULL;
+
+ LISTBASE_FOREACH (Nurb *, nu, nurbs) {
+ if (nu->type == CU_BEZIER) {
+ for (int i = 0; i < nu->pntsu; i++) {
+ BezTriple *bezt = &nu->bezt[i];
+ float bezt_vec[2];
+ int start = 0, end = 3;
+
+ /* Consider handles only if visible. Else only consider the middle point of the triple. */
+ int handle_display = vc->v3d->overlay.handle_display;
+ if (handle_display == CURVE_HANDLE_NONE ||
+ (handle_display == CURVE_HANDLE_SELECTED && !BEZT_ISSEL_ANY(bezt))) {
+ start = 1, end = 2;
+ }
+
+ /* Loop over each of the 3 points of the #BezTriple and update data of closest bezt. */
+ for (int j = start; j < end; j++) {
+ if (worldspace_to_screenspace(vc, bezt->vec[j], bezt_vec)) {
+ const float dist = len_manhattan_v2v2(bezt_vec, point);
+ if (dist < min_dist_bezt) {
+ min_dist_bezt = dist;
+ closest_bezt = bezt;
+ closest_bezt_nu = nu;
+ closest_handle = j;
+ }
+ }
+ }
+ }
+ }
+ else {
+ for (int i = 0; i < nu->pntsu; i++) {
+ BPoint *bp = &nu->bp[i];
+ float bp_vec[2];
+
+ /* Update data of closest #BPoint. */
+ if (worldspace_to_screenspace(vc, bp->vec, bp_vec)) {
+ const float dist = len_manhattan_v2v2(bp_vec, point);
+ if (dist < min_dist_bp) {
+ min_dist_bp = dist;
+ closest_bp = bp;
+ closest_bp_nu = nu;
+ }
+ }
+ }
+ }
+ }
+
+ /* Assign closest data to the returned variables. */
+ const float threshold_dist = ED_view3d_select_dist_px() * SEL_DIST_FACTOR;
+ if (min_dist_bezt < threshold_dist || min_dist_bp < threshold_dist) {
+ if (min_dist_bp < min_dist_bezt) {
+ *r_bp = closest_bp;
+ *r_nu = closest_bp_nu;
+ }
+ else {
+ *r_bezt = closest_bezt;
+ *r_bezt_idx = closest_handle;
+ *r_nu = closest_bezt_nu;
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Interpolate along the Bezier segment by a parameter (between 0 and 1) and get its location.
+ */
+static void get_bezier_interpolated_point(const BezTriple *bezt1,
+ const BezTriple *bezt2,
+ const float parameter,
+ float r_point[3])
+{
+ float tmp1[3], tmp2[3], tmp3[3];
+ interp_v3_v3v3(tmp1, bezt1->vec[1], bezt1->vec[2], parameter);
+ interp_v3_v3v3(tmp2, bezt1->vec[2], bezt2->vec[0], parameter);
+ interp_v3_v3v3(tmp3, bezt2->vec[0], bezt2->vec[1], parameter);
+ interp_v3_v3v3(tmp1, tmp1, tmp2, parameter);
+ interp_v3_v3v3(tmp2, tmp2, tmp3, parameter);
+ interp_v3_v3v3(r_point, tmp1, tmp2, parameter);
+}
+
+/**
+ * Calculate handle positions of added and adjacent control points such that shape is preserved.
+ */
+static void calculate_new_bezier_point(const float point_prev[3],
+ float handle_prev[3],
+ float new_left_handle[3],
+ float new_right_handle[3],
+ float handle_next[3],
+ const float point_next[3],
+ const float parameter)
+{
+ float center_point[3];
+ interp_v3_v3v3(center_point, handle_prev, handle_next, parameter);
+ interp_v3_v3v3(handle_prev, point_prev, handle_prev, parameter);
+ interp_v3_v3v3(handle_next, handle_next, point_next, parameter);
+ interp_v3_v3v3(new_left_handle, handle_prev, center_point, parameter);
+ interp_v3_v3v3(new_right_handle, center_point, handle_next, parameter);
+}
+
+static bool is_cyclic(const Nurb *nu)
+{
+ return nu->flagu & CU_NURB_CYCLIC;
+}
+
+/**
+ * Insert a #BezTriple to a nurb at the location specified by `data`.
+ */
+static void insert_bezt_to_nurb(Nurb *nu, const CutData *data, Curve *cu)
+{
+ EditNurb *editnurb = cu->editnurb;
+
+ BezTriple *new_bezt_array = (BezTriple *)MEM_mallocN((nu->pntsu + 1) * sizeof(BezTriple),
+ __func__);
+ const int index = data->bezt_index + 1;
+ /* Copy all control points before the cut to the new memory. */
+ ED_curve_beztcpy(editnurb, new_bezt_array, nu->bezt, index);
+ BezTriple *new_bezt = new_bezt_array + index;
+
+ /* Duplicate control point after the cut. */
+ ED_curve_beztcpy(editnurb, new_bezt, new_bezt - 1, 1);
+ copy_v3_v3(new_bezt->vec[1], data->cut_loc);
+
+ if (index < nu->pntsu) {
+ /* Copy all control points after the cut to the new memory. */
+ ED_curve_beztcpy(editnurb, new_bezt_array + index + 1, nu->bezt + index, nu->pntsu - index);
+ }
+
+ nu->pntsu += 1;
+ BKE_curve_nurb_vert_active_set(cu, nu, nu->bezt + index);
+
+ BezTriple *next_bezt;
+ if (is_cyclic(nu) && (index == nu->pntsu - 1)) {
+ next_bezt = new_bezt_array;
+ }
+ else {
+ next_bezt = new_bezt + 1;
+ }
+
+ /* Interpolate radius, tilt, weight */
+ new_bezt->tilt = interpf(next_bezt->tilt, (new_bezt - 1)->tilt, data->parameter);
+ new_bezt->radius = interpf(next_bezt->radius, (new_bezt - 1)->radius, data->parameter);
+ new_bezt->weight = interpf(next_bezt->weight, (new_bezt - 1)->weight, data->parameter);
+
+ new_bezt->h1 = new_bezt->h2 = HD_ALIGN;
+
+ calculate_new_bezier_point((new_bezt - 1)->vec[1],
+ (new_bezt - 1)->vec[2],
+ new_bezt->vec[0],
+ new_bezt->vec[2],
+ next_bezt->vec[0],
+ next_bezt->vec[1],
+ data->parameter);
+
+ MEM_freeN(nu->bezt);
+ nu->bezt = new_bezt_array;
+ ED_curve_deselect_all(editnurb);
+ BKE_nurb_handles_calc(nu);
+ BEZT_SEL_ALL(new_bezt);
+}
+
+/**
+ * Insert a #BPoint to a nurb at the location specified by `op_data`.
+ */
+static void insert_bp_to_nurb(Nurb *nu, const CutData *data, Curve *cu)
+{
+ EditNurb *editnurb = cu->editnurb;
+
+ BPoint *new_bp_array = (BPoint *)MEM_mallocN((nu->pntsu + 1) * sizeof(BPoint), __func__);
+ const int index = data->bp_index + 1;
+ /* Copy all control points before the cut to the new memory. */
+ ED_curve_bpcpy(editnurb, new_bp_array, nu->bp, index);
+ BPoint *new_bp = new_bp_array + index;
+
+ /* Duplicate control point after the cut. */
+ ED_curve_bpcpy(editnurb, new_bp, new_bp - 1, 1);
+ copy_v3_v3(new_bp->vec, data->cut_loc);
+
+ if (index < nu->pntsu) {
+ /* Copy all control points after the cut to the new memory. */
+ ED_curve_bpcpy(editnurb, new_bp_array + index + 1, nu->bp + index, (nu->pntsu - index));
+ }
+
+ nu->pntsu += 1;
+ BKE_curve_nurb_vert_active_set(cu, nu, nu->bp + index);
+
+ BPoint *next_bp;
+ if (is_cyclic(nu) && (index == nu->pntsu - 1)) {
+ next_bp = new_bp_array;
+ }
+ else {
+ next_bp = new_bp + 1;
+ }
+
+ /* Interpolate radius, tilt, weight */
+ new_bp->tilt = interpf(next_bp->tilt, (new_bp - 1)->tilt, data->parameter);
+ new_bp->radius = interpf(next_bp->radius, (new_bp - 1)->radius, data->parameter);
+ new_bp->weight = interpf(next_bp->weight, (new_bp - 1)->weight, data->parameter);
+
+ MEM_freeN(nu->bp);
+ nu->bp = new_bp_array;
+ ED_curve_deselect_all(editnurb);
+ BKE_nurb_knot_calc_u(nu);
+ new_bp->f1 |= SELECT;
+}
+
+/**
+ * Update r_min_dist, r_min_i, and r_param based on the edge and the external point.
+ * \param point: External point
+ * \param point1: One end of the edge
+ * \param point2: The other end of the edge
+ * \param point_idx: Index of the control point out of the points on the Nurb
+ * \param resolu_idx: Index of the edge on a Bezier segment (zero for non-Bezier edges)
+ * \param r_min_dist: minimum distance from point to edge
+ * \param r_min_i: index of closest point on Nurb
+ * \param r_param: the fraction along the edge at which the closest point lies
+ */
+static void get_updated_data_for_edge(const float point[2],
+ const float point1[2],
+ const float point2[2],
+ const int point_idx,
+ const int resolu_idx,
+ float *r_min_dist,
+ int *r_min_i,
+ float *r_param)
+{
+ float edge[2], vec1[2], vec2[2];
+ sub_v2_v2v2(edge, point1, point2);
+ sub_v2_v2v2(vec1, point1, point);
+ sub_v2_v2v2(vec2, point, point2);
+ const float len_vec1 = len_v2(vec1);
+ const float len_vec2 = len_v2(vec2);
+ const float dot1 = dot_v2v2(edge, vec1);
+ const float dot2 = dot_v2v2(edge, vec2);
+
+ /* Signs of dot products being equal implies that the angles formed with the external point are
+ * either both acute or both obtuse, meaning the external point is closer to a point on the edge
+ * rather than an endpoint. */
+ if ((dot1 > 0) == (dot2 > 0)) {
+ const float perp_dist = len_vec1 * sinf(angle_v2v2(vec1, edge));
+ if (*r_min_dist > perp_dist) {
+ *r_min_dist = perp_dist;
+ *r_min_i = point_idx;
+ *r_param = resolu_idx + len_vec1 * cos_v2v2v2(point, point1, point2) / len_v2(edge);
+ }
+ }
+ else {
+ if (*r_min_dist > len_vec2) {
+ *r_min_dist = len_vec2;
+ *r_min_i = point_idx;
+ *r_param = resolu_idx;
+ }
+ }
+}
+
+/**
+ * Update #CutData for a single #Nurb.
+ */
+static void update_cut_data_for_nurb(
+ const ViewContext *vc, CutData *cd, Nurb *nu, const int resolu, const float point[2])
+{
+ float min_dist = cd->min_dist, param = 0.0f;
+ int min_i = 0;
+ const int end = is_cyclic(nu) ? nu->pntsu : nu->pntsu - 1;
+
+ if (nu->type == CU_BEZIER) {
+ for (int i = 0; i < end; i++) {
+ float *points = MEM_mallocN(sizeof(float[3]) * (resolu + 1), __func__);
+
+ const BezTriple *bezt1 = nu->bezt + i;
+ const BezTriple *bezt2 = nu->bezt + (i + 1) % nu->pntsu;
+
+ /* Calculate all points on curve. */
+ for (int j = 0; j < 3; j++) {
+ BKE_curve_forward_diff_bezier(bezt1->vec[1][j],
+ bezt1->vec[2][j],
+ bezt2->vec[0][j],
+ bezt2->vec[1][j],
+ points + j,
+ resolu,
+ sizeof(float[3]));
+ }
+
+ float point1[2], point2[2];
+ worldspace_to_screenspace(vc, points, point1);
+ const float len_vec1 = len_v2v2(point, point1);
+
+ if (min_dist > len_vec1) {
+ min_dist = len_vec1;
+ min_i = i;
+ param = 0;
+ }
+
+ for (int j = 0; j < resolu; j++) {
+ worldspace_to_screenspace(vc, points + 3 * (j + 1), point2);
+ get_updated_data_for_edge(point, point1, point2, i, j, &min_dist, &min_i, &param);
+ copy_v2_v2(point1, point2);
+ }
+
+ MEM_freeN(points);
+ }
+ if (cd->min_dist > min_dist) {
+ cd->min_dist = min_dist;
+ cd->nurb = nu;
+ cd->bezt_index = min_i;
+ cd->parameter = param / resolu;
+ }
+ }
+ else {
+ float point1[2], point2[2];
+ worldspace_to_screenspace(vc, nu->bp->vec, point1);
+ for (int i = 0; i < end; i++) {
+ worldspace_to_screenspace(vc, (nu->bp + (i + 1) % nu->pntsu)->vec, point2);
+ get_updated_data_for_edge(point, point1, point2, i, 0, &min_dist, &min_i, &param);
+ copy_v2_v2(point1, point2);
+ }
+
+ if (cd->min_dist > min_dist) {
+ cd->min_dist = min_dist;
+ cd->nurb = nu;
+ cd->bp_index = min_i;
+ cd->parameter = param;
+ }
+ }
+}
+
+/* Update #CutData for all the Nurbs in the curve. */
+static bool update_cut_data_for_all_nurbs(const ViewContext *vc,
+ const ListBase *nurbs,
+ const float point[2],
+ const float sel_dist,
+ CutData *cd)
+{
+ cd->min_dist = FLT_MAX;
+ LISTBASE_FOREACH (Nurb *, nu, nurbs) {
+ update_cut_data_for_nurb(vc, cd, nu, nu->resolu, point);
+ }
+
+ return cd->min_dist < sel_dist;
+}
+
+static CutData init_cut_data(const wmEvent *event)
+{
+ CutData cd = {.bezt_index = 0,
+ .bp_index = 0,
+ .min_dist = FLT_MAX,
+ .parameter = 0.5f,
+ .has_prev = false,
+ .has_next = false,
+ .mval[0] = event->mval[0],
+ .mval[1] = event->mval[1]};
+ return cd;
+}
+
+static bool insert_point_to_segment(const ViewContext *vc, const wmEvent *event)
+{
+ Curve *cu = vc->obedit->data;
+ CutData cd = init_cut_data(event);
+ float mval[2] = {UNPACK2(event->mval)};
+ const bool near_spline = update_cut_data_for_all_nurbs(
+ vc, BKE_curve_editNurbs_get(cu), mval, SEL_DIST_FACTOR * ED_view3d_select_dist_px(), &cd);
+
+ if (near_spline && !cd.nurb->hide) {
+ Nurb *nu = cd.nurb;
+ if (nu->type == CU_BEZIER) {
+ cd.min_dist = FLT_MAX;
+ /* Update cut data at a higher resolution for better accuracy. */
+ update_cut_data_for_nurb(vc, &cd, cd.nurb, 25, mval);
+
+ get_bezier_interpolated_point(&nu->bezt[cd.bezt_index],
+ &nu->bezt[(cd.bezt_index + 1) % (nu->pntsu)],
+ cd.parameter,
+ cd.cut_loc);
+
+ insert_bezt_to_nurb(nu, &cd, cu);
+ }
+ else {
+ interp_v2_v2v2(cd.cut_loc,
+ (nu->bp + cd.bp_index)->vec,
+ (nu->bp + (cd.bp_index + 1) % nu->pntsu)->vec,
+ cd.parameter);
+ insert_bp_to_nurb(nu, &cd, cu);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Get the first selected point from the curve. If more than one selected point is found,
+ * define and return only the first detected nu.
+ */
+static void get_first_selected_point(
+ Curve *cu, View3D *v3d, Nurb **r_nu, BezTriple **r_bezt, BPoint **r_bp)
+{
+ ListBase *nurbs = &cu->editnurb->nurbs;
+ BezTriple *bezt;
+ BPoint *bp;
+ int a;
+
+ *r_nu = NULL;
+ *r_bezt = NULL;
+ *r_bp = NULL;
+
+ LISTBASE_FOREACH (Nurb *, nu, nurbs) {
+ if (nu->type == CU_BEZIER) {
+ bezt = nu->bezt;
+ a = nu->pntsu;
+ while (a--) {
+ if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) {
+ if (*r_bezt || *r_bp) {
+ *r_bp = NULL;
+ *r_bezt = NULL;
+ return;
+ }
+ *r_bezt = bezt;
+ *r_nu = nu;
+ }
+ bezt++;
+ }
+ }
+ else {
+ bp = nu->bp;
+ a = nu->pntsu * nu->pntsv;
+ while (a--) {
+ if (bp->f1 & SELECT) {
+ if (*r_bezt || *r_bp) {
+ *r_bp = NULL;
+ *r_bezt = NULL;
+ return;
+ }
+ *r_bp = bp;
+ *r_nu = nu;
+ }
+ bp++;
+ }
+ }
+ }
+}
+
+static void extrude_vertices_from_selected_endpoints(EditNurb *editnurb,
+ ListBase *nurbs,
+ Curve *cu,
+ const float disp_3d[3])
+{
+ int nu_index = 0;
+ LISTBASE_FOREACH (Nurb *, nu1, nurbs) {
+ if (nu1->type == CU_BEZIER) {
+ BezTriple *last_bezt = nu1->bezt + nu1->pntsu - 1;
+ const bool first_sel = BEZT_ISSEL_ANY(nu1->bezt);
+ const bool last_sel = BEZT_ISSEL_ANY(last_bezt) && nu1->pntsu > 1;
+ if (first_sel) {
+ if (last_sel) {
+ BezTriple *new_bezt = (BezTriple *)MEM_mallocN((nu1->pntsu + 2) * sizeof(BezTriple),
+ __func__);
+ ED_curve_beztcpy(editnurb, new_bezt, nu1->bezt, 1);
+ ED_curve_beztcpy(editnurb, new_bezt + nu1->pntsu + 1, last_bezt, 1);
+ BEZT_DESEL_ALL(nu1->bezt);
+ BEZT_DESEL_ALL(last_bezt);
+ ED_curve_beztcpy(editnurb, new_bezt + 1, nu1->bezt, nu1->pntsu);
+
+ move_bezt_by_displacement(new_bezt, disp_3d);
+ move_bezt_by_displacement(new_bezt + nu1->pntsu + 1, disp_3d);
+ MEM_freeN(nu1->bezt);
+ nu1->bezt = new_bezt;
+ nu1->pntsu += 2;
+ }
+ else {
+ BezTriple *new_bezt = (BezTriple *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BezTriple),
+ __func__);
+ ED_curve_beztcpy(editnurb, new_bezt, nu1->bezt, 1);
+ BEZT_DESEL_ALL(nu1->bezt);
+ ED_curve_beztcpy(editnurb, new_bezt + 1, nu1->bezt, nu1->pntsu);
+ move_bezt_by_displacement(new_bezt, disp_3d);
+ MEM_freeN(nu1->bezt);
+ nu1->bezt = new_bezt;
+ nu1->pntsu++;
+ }
+ cu->actnu = nu_index;
+ cu->actvert = 0;
+ }
+ else if (last_sel) {
+ BezTriple *new_bezt = (BezTriple *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BezTriple),
+ __func__);
+ ED_curve_beztcpy(editnurb, new_bezt + nu1->pntsu, last_bezt, 1);
+ BEZT_DESEL_ALL(last_bezt);
+ ED_curve_beztcpy(editnurb, new_bezt, nu1->bezt, nu1->pntsu);
+ move_bezt_by_displacement(new_bezt + nu1->pntsu, disp_3d);
+ MEM_freeN(nu1->bezt);
+ nu1->bezt = new_bezt;
+ nu1->pntsu++;
+ cu->actnu = nu_index;
+ cu->actvert = nu1->pntsu - 1;
+ }
+ }
+ else {
+ BPoint *last_bp = nu1->bp + nu1->pntsu - 1;
+ const bool first_sel = nu1->bp->f1 & SELECT;
+ const bool last_sel = last_bp->f1 & SELECT;
+ if (first_sel) {
+ if (last_sel) {
+ BPoint *new_bp = (BPoint *)MEM_mallocN((nu1->pntsu + 2) * sizeof(BPoint), __func__);
+ ED_curve_bpcpy(editnurb, new_bp, nu1->bp, 1);
+ ED_curve_bpcpy(editnurb, new_bp + nu1->pntsu + 1, last_bp, 1);
+ nu1->bp->f1 &= ~SELECT;
+ last_bp->f1 &= ~SELECT;
+ ED_curve_bpcpy(editnurb, new_bp + 1, nu1->bp, nu1->pntsu);
+ add_v3_v3(new_bp->vec, disp_3d);
+ add_v3_v3((new_bp + nu1->pntsu + 1)->vec, disp_3d);
+ MEM_freeN(nu1->bp);
+ nu1->bp = new_bp;
+ nu1->pntsu += 2;
+ }
+ else {
+ BPoint *new_bp = (BPoint *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BPoint), __func__);
+ ED_curve_bpcpy(editnurb, new_bp, nu1->bp, 1);
+ nu1->bp->f1 &= ~SELECT;
+ ED_curve_bpcpy(editnurb, new_bp + 1, nu1->bp, nu1->pntsu);
+ add_v3_v3(new_bp->vec, disp_3d);
+ MEM_freeN(nu1->bp);
+ nu1->bp = new_bp;
+ nu1->pntsu++;
+ }
+ BKE_nurb_knot_calc_u(nu1);
+ cu->actnu = nu_index;
+ cu->actvert = 0;
+ }
+ else if (last_sel) {
+ BPoint *new_bp = (BPoint *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BPoint), __func__);
+ ED_curve_bpcpy(editnurb, new_bp, nu1->bp, nu1->pntsu);
+ ED_curve_bpcpy(editnurb, new_bp + nu1->pntsu, last_bp, 1);
+ last_bp->f1 &= ~SELECT;
+ ED_curve_bpcpy(editnurb, new_bp, nu1->bp, nu1->pntsu);
+ add_v3_v3((new_bp + nu1->pntsu)->vec, disp_3d);
+ MEM_freeN(nu1->bp);
+ nu1->bp = new_bp;
+ nu1->pntsu++;
+ BKE_nurb_knot_calc_u(nu1);
+ cu->actnu = nu_index;
+ cu->actvert = nu1->pntsu - 1;
+ }
+ BKE_curve_nurb_vert_active_validate(cu);
+ }
+ nu_index++;
+ }
+}
+
+/**
+ * Deselect all vertices that are not endpoints.
+ */
+static void deselect_all_center_vertices(ListBase *nurbs)
+{
+ LISTBASE_FOREACH (Nurb *, nu1, nurbs) {
+ if (nu1->pntsu > 1) {
+ int start, end;
+ if (is_cyclic(nu1)) {
+ start = 0;
+ end = nu1->pntsu;
+ }
+ else {
+ start = 1;
+ end = nu1->pntsu - 1;
+ }
+ for (int i = start; i < end; i++) {
+ if (nu1->type == CU_BEZIER) {
+ BEZT_DESEL_ALL(nu1->bezt + i);
+ }
+ else {
+ (nu1->bp + i)->f1 &= ~SELECT;
+ }
+ }
+ }
+ }
+}
+
+static bool is_last_bezt(const Nurb *nu, const BezTriple *bezt)
+{
+ return nu->pntsu > 1 && nu->bezt + nu->pntsu - 1 == bezt && !is_cyclic(nu);
+}
+
+/**
+ * Add new vertices connected to the selected vertices.
+ */
+static void extrude_points_from_selected_vertices(const ViewContext *vc,
+ const wmEvent *event,
+ const int extrude_handle)
+{
+ Curve *cu = vc->obedit->data;
+ ListBase *nurbs = BKE_curve_editNurbs_get(cu);
+ float center[3] = {0.0f, 0.0f, 0.0f};
+ deselect_all_center_vertices(nurbs);
+ bool sel_exists = get_selected_center(nurbs, true, false, center);
+
+ float location[3];
+ if (sel_exists) {
+ mul_v3_m4v3(location, vc->obedit->obmat, center);
+ }
+ else {
+ copy_v3_v3(location, vc->scene->cursor.location);
+ }
+
+ ED_view3d_win_to_3d_int(vc->v3d, vc->region, location, event->mval, location);
+
+ update_location_for_2d_curve(vc, location);
+ EditNurb *editnurb = cu->editnurb;
+
+ if (sel_exists) {
+ float disp_3d[3];
+ sub_v3_v3v3(disp_3d, location, center);
+ /* Reimplemenented due to unexpected behavior for extrusion of 2-point spline. */
+ extrude_vertices_from_selected_endpoints(editnurb, nurbs, cu, disp_3d);
+ }
+ else {
+ Nurb *old_last_nu = editnurb->nurbs.last;
+ ed_editcurve_addvert(cu, editnurb, vc->v3d, location);
+ Nurb *new_last_nu = editnurb->nurbs.last;
+
+ if (old_last_nu != new_last_nu) {
+ new_last_nu->flagu = ~CU_NURB_CYCLIC;
+ }
+ }
+
+ FOREACH_SELECTED_BEZT_BEGIN (bezt, &cu->editnurb->nurbs) {
+ if (bezt) {
+ bezt->h1 = extrude_handle;
+ bezt->h2 = extrude_handle;
+ }
+ }
+ FOREACH_SELECTED_BEZT_END;
+}
+
+/**
+ * Check if a spline segment is nearby.
+ */
+static bool is_spline_nearby(ViewContext *vc,
+ struct wmOperator *op,
+ const wmEvent *event,
+ const float sel_dist)
+{
+ Curve *cu = vc->obedit->data;
+ ListBase *nurbs = BKE_curve_editNurbs_get(cu);
+ CutData cd = init_cut_data(event);
+
+ float mval[2] = {UNPACK2(event->mval)};
+ const bool nearby = update_cut_data_for_all_nurbs(vc, nurbs, mval, sel_dist, &cd);
+
+ if (nearby) {
+ if (cd.nurb && (cd.nurb->type == CU_BEZIER) && RNA_boolean_get(op->ptr, "move_segment")) {
+ MoveSegmentData *seg_data;
+ CurvePenData *cpd = (CurvePenData *)(op->customdata);
+ cpd->msd = seg_data = MEM_callocN(sizeof(MoveSegmentData), __func__);
+ seg_data->bezt_index = cd.bezt_index;
+ seg_data->nu = cd.nurb;
+ seg_data->t = cd.parameter;
+ }
+ return true;
+ }
+ return false;
+}
+
+static void move_segment(ViewContext *vc, MoveSegmentData *seg_data, const wmEvent *event)
+{
+ Nurb *nu = seg_data->nu;
+ BezTriple *bezt1 = nu->bezt + seg_data->bezt_index;
+ BezTriple *bezt2 = BKE_nurb_bezt_get_next(nu, bezt1);
+
+ int h1 = 2, h2 = 0;
+ if (bezt1->hide) {
+ if (bezt2->hide) {
+ return;
+ }
+ else {
+ /*
+ * Swap bezt1 and bezt2 in all calculations if only bezt2 is visible.
+ * (The first point needs to be visible for the calculations of the second point to be valid)
+ */
+ BezTriple *temp_bezt = bezt2;
+ bezt2 = bezt1;
+ bezt1 = temp_bezt;
+ h1 = 0, h2 = 2;
+ }
+ }
+
+ const float t = max_ff(min_ff(seg_data->t, 0.9f), 0.1f);
+ const float t_sq = t * t;
+ const float t_cu = t_sq * t;
+ const float one_minus_t = 1 - t;
+ const float one_minus_t_sq = one_minus_t * one_minus_t;
+ const float one_minus_t_cu = one_minus_t_sq * one_minus_t;
+
+ float mouse_3d[3];
+ float depth[3];
+ /* Use the center of the spline segment as depth. */
+ get_bezier_interpolated_point(bezt1, bezt2, t, depth);
+ screenspace_to_worldspace_int(vc, event->mval, depth, mouse_3d);
+
+ /*
+ * Equation of Bezier Curve
+ * => B(t) = (1-t)^3 * P0 + 3(1-t)^2 * t * P1 + 3(1-t) * t^2 * P2 + t^3 * P3
+ *
+ * Mouse location (Say Pm) should satisfy this equation.
+ * Therefore => (1/t - 1) * P1 + P2 = (Pm - (1 - t)^3 * P0 - t^3 * P3) / [3 * (1 - t) * t^2] = k1
+ * (in code)
+ *
+ * Another constraint is required to identify P1 and P2.
+ * The constraint used is that the vector between P1 and P2 doesn't change.
+ * Therefore => P1 - P2 = k2
+ *
+ * From the two equations => P1 = t(k1 + k2) and P2 = P1 - K2
+ */
+
+ float k1[3];
+ const float denom = 3.0f * one_minus_t * t_sq;
+ k1[0] = (mouse_3d[0] - one_minus_t_cu * bezt1->vec[1][0] - t_cu * bezt2->vec[1][0]) / denom;
+ k1[1] = (mouse_3d[1] - one_minus_t_cu * bezt1->vec[1][1] - t_cu * bezt2->vec[1][1]) / denom;
+ k1[2] = (mouse_3d[2] - one_minus_t_cu * bezt1->vec[1][2] - t_cu * bezt2->vec[1][2]) / denom;
+
+ float k2[3];
+ sub_v3_v3v3(k2, bezt1->vec[h1], bezt2->vec[h2]);
+
+ if (!bezt1->hide) {
+ /* P1 = t(k1 + k2) */
+ add_v3_v3v3(bezt1->vec[h1], k1, k2);
+ mul_v3_fl(bezt1->vec[h1], t);
+
+ remove_handle_movement_constraints(bezt1, true, true);
+
+ /* Move opposite handle as well if type is align. */
+ if (bezt1->h1 == HD_ALIGN) {
+ float handle_vec[3];
+ sub_v3_v3v3(handle_vec, bezt1->vec[1], bezt1->vec[h1]);
+ normalize_v3_length(handle_vec, len_v3v3(bezt1->vec[1], bezt1->vec[h2]));
+ add_v3_v3v3(bezt1->vec[h2], bezt1->vec[1], handle_vec);
+ }
+ }
+
+ if (!bezt2->hide) {
+ /* P2 = P1 - K2 */
+ sub_v3_v3v3(bezt2->vec[h2], bezt1->vec[h1], k2);
+
+ remove_handle_movement_constraints(bezt2, true, true);
+
+ /* Move opposite handle as well if type is align. */
+ if (bezt2->h2 == HD_ALIGN) {
+ float handle_vec[3];
+ sub_v3_v3v3(handle_vec, bezt2->vec[1], bezt2->vec[h2]);
+ normalize_v3_length(handle_vec, len_v3v3(bezt2->vec[1], bezt2->vec[h1]));
+ add_v3_v3v3(bezt2->vec[h1], bezt2->vec[1], handle_vec);
+ }
+ }
+}
+
+/**
+ * Toggle between #HD_FREE and #HD_ALIGN handles of the given #BezTriple
+ */
+static void toggle_bezt_free_align_handles(BezTriple *bezt)
+{
+ if (bezt->h1 != HD_FREE || bezt->h2 != HD_FREE) {
+ bezt->h1 = bezt->h2 = HD_FREE;
+ }
+ else {
+ bezt->h1 = bezt->h2 = HD_ALIGN;
+ }
+}
+
+/**
+ * Toggle between #HD_FREE and #HD_ALIGN handles of the all selected #BezTriple
+ */
+static void toggle_sel_bezt_free_align_handles(ListBase *nurbs)
+{
+ FOREACH_SELECTED_BEZT_BEGIN (bezt, nurbs) {
+ toggle_bezt_free_align_handles(bezt);
+ }
+ FOREACH_SELECTED_BEZT_END;
+}
+
+/**
+ * If a point is found under mouse, delete point and return true. Else return false.
+ */
+static bool delete_point_under_mouse(ViewContext *vc, const wmEvent *event)
+{
+ BezTriple *bezt = NULL;
+ BPoint *bp = NULL;
+ Nurb *nu = NULL;
+ int temp = 0;
+ Curve *cu = vc->obedit->data;
+ EditNurb *editnurb = cu->editnurb;
+ ListBase *nurbs = BKE_curve_editNurbs_get(cu);
+ const float mouse_point[2] = {UNPACK2(event->mval)};
+
+ get_closest_vertex_to_point_in_nurbs(vc, nurbs, mouse_point, &nu, &bezt, &bp, &temp);
+ const bool found_point = nu != NULL;
+
+ bool deleted = false;
+ if (found_point) {
+ ED_curve_deselect_all(cu->editnurb);
+ if (nu) {
+ if (nu->type == CU_BEZIER) {
+ BezTriple *next_bezt = BKE_nurb_bezt_get_next(nu, bezt);
+ BezTriple *prev_bezt = BKE_nurb_bezt_get_prev(nu, bezt);
+ if (next_bezt && prev_bezt) {
+ const int bez_index = BKE_curve_nurb_vert_index_get(nu, bezt);
+ const int span_step[2] = {bez_index, bez_index};
+ ed_dissolve_bez_segment(prev_bezt, next_bezt, nu, cu, 1, span_step);
+ }
+ delete_bezt_from_nurb(bezt, nu, editnurb);
+ }
+ else {
+ delete_bp_from_nurb(bp, nu, editnurb);
+ }
+
+ if (nu->pntsu == 0) {
+ delete_nurb(cu, nu);
+ }
+ deleted = true;
+ cu->actvert = CU_ACT_NONE;
+ }
+ }
+
+ if (nu && nu->type == CU_BEZIER) {
+ BKE_nurb_handles_calc(nu);
+ }
+
+ return deleted;
+}
+
+static void move_adjacent_handle(ViewContext *vc, const wmEvent *event, ListBase *nurbs)
+{
+ FOREACH_SELECTED_BEZT_BEGIN (bezt, nurbs) {
+ BezTriple *adj_bezt;
+ int bezt_idx;
+ if (nu->pntsu == 1) {
+ continue;
+ }
+ if (nu->bezt == bezt) {
+ adj_bezt = BKE_nurb_bezt_get_next(nu, bezt);
+ bezt_idx = 0;
+ }
+ else if (nu->bezt + nu->pntsu - 1 == bezt) {
+ adj_bezt = BKE_nurb_bezt_get_prev(nu, bezt);
+ bezt_idx = 2;
+ }
+ else {
+ if (BEZT_ISSEL_IDX(bezt, 0)) {
+ adj_bezt = BKE_nurb_bezt_get_prev(nu, bezt);
+ bezt_idx = 2;
+ }
+ else if (BEZT_ISSEL_IDX(bezt, 2)) {
+ adj_bezt = BKE_nurb_bezt_get_next(nu, bezt);
+ bezt_idx = 0;
+ }
+ else {
+ continue;
+ }
+ }
+ adj_bezt->h1 = adj_bezt->h2 = HD_FREE;
+
+ int displacement[2];
+ sub_v2_v2v2_int(displacement, event->xy, event->prev_xy);
+ const float disp_fl[2] = {UNPACK2(displacement)};
+ move_bezt_handle_or_vertex_by_displacement(
+ vc, adj_bezt, bezt_idx, disp_fl, 0.0f, false, false);
+ BKE_nurb_handles_calc(nu);
+ }
+ FOREACH_SELECTED_BEZT_END;
+}
+
+/**
+ * Close the spline if endpoints are selected consecutively. Return true if cycle was created.
+ */
+static bool make_cyclic_if_endpoints(ViewContext *vc,
+ Nurb *sel_nu,
+ BezTriple *sel_bezt,
+ BPoint *sel_bp)
+{
+ if (sel_bezt || (sel_bp && sel_nu->pntsu > 2)) {
+ const bool is_bezt_endpoint = (sel_nu->type == CU_BEZIER &&
+ (sel_bezt == sel_nu->bezt ||
+ sel_bezt == sel_nu->bezt + sel_nu->pntsu - 1));
+ const bool is_bp_endpoint = (sel_nu->type != CU_BEZIER &&
+ (sel_bp == sel_nu->bp ||
+ sel_bp == sel_nu->bp + sel_nu->pntsu - 1));
+ if (!(is_bezt_endpoint || is_bp_endpoint)) {
+ return false;
+ }
+
+ Nurb *nu = NULL;
+ BezTriple *bezt = NULL;
+ BPoint *bp = NULL;
+ Curve *cu = vc->obedit->data;
+ int bezt_idx;
+ const float mval_fl[2] = {UNPACK2(vc->mval)};
+
+ get_closest_vertex_to_point_in_nurbs(
+ vc, &(cu->editnurb->nurbs), mval_fl, &nu, &bezt, &bp, &bezt_idx);
+
+ if (nu == sel_nu &&
+ ((nu->type == CU_BEZIER && bezt != sel_bezt &&
+ (bezt == nu->bezt || bezt == nu->bezt + nu->pntsu - 1) && bezt_idx == 1) ||
+ (nu->type != CU_BEZIER && bp != sel_bp &&
+ (bp == nu->bp || bp == nu->bp + nu->pntsu - 1)))) {
+ View3D *v3d = vc->v3d;
+ ListBase *nurbs = object_editcurve_get(vc->obedit);
+ curve_toggle_cyclic(v3d, nurbs, 0);
+ return true;
+ }
+ }
+ return false;
+}
+
+static void init_selected_bezt_handles(ListBase *nurbs)
+{
+ FOREACH_SELECTED_BEZT_BEGIN (bezt, nurbs) {
+ bezt->h1 = bezt->h2 = HD_ALIGN;
+ copy_v3_v3(bezt->vec[0], bezt->vec[1]);
+ copy_v3_v3(bezt->vec[2], bezt->vec[1]);
+ BEZT_DESEL_ALL(bezt);
+ BEZT_SEL_IDX(bezt, is_last_bezt(nu, bezt) ? 2 : 0);
+ }
+ FOREACH_SELECTED_BEZT_END;
+}
+
+static void toggle_select_bezt(BezTriple *bezt, const int bezt_idx, Curve *cu, Nurb *nu)
+{
+ if (bezt_idx == 1) {
+ if (BEZT_ISSEL_IDX(bezt, 1)) {
+ BEZT_DESEL_ALL(bezt);
+ }
+ else {
+ BEZT_SEL_ALL(bezt);
+ }
+ }
+ else {
+ if (BEZT_ISSEL_IDX(bezt, bezt_idx)) {
+ BEZT_DESEL_IDX(bezt, bezt_idx);
+ }
+ else {
+ BEZT_SEL_IDX(bezt, bezt_idx);
+ }
+ }
+
+ if (BEZT_ISSEL_ANY(bezt)) {
+ BKE_curve_nurb_vert_active_set(cu, nu, bezt);
+ }
+}
+
+static void toggle_select_bp(BPoint *bp, Curve *cu, Nurb *nu)
+{
+ if (bp->f1 & SELECT) {
+ bp->f1 &= ~SELECT;
+ }
+ else {
+ bp->f1 |= SELECT;
+ BKE_curve_nurb_vert_active_set(cu, nu, bp);
+ }
+}
+
+static void toggle_handle_types(BezTriple *bezt, int bezt_idx, CurvePenData *cpd)
+{
+ if (bezt_idx == 0) {
+ if (bezt->h1 == HD_VECT) {
+ bezt->h1 = bezt->h2 = HD_AUTO;
+ }
+ else {
+ bezt->h1 = HD_VECT;
+ if (bezt->h2 != HD_VECT) {
+ bezt->h2 = HD_FREE;
+ }
+ }
+ cpd->acted = true;
+ }
+ else if (bezt_idx == 2) {
+ if (bezt->h2 == HD_VECT) {
+ bezt->h1 = bezt->h2 = HD_AUTO;
+ }
+ else {
+ bezt->h2 = HD_VECT;
+ if (bezt->h1 != HD_VECT) {
+ bezt->h1 = HD_FREE;
+ }
+ }
+ cpd->acted = true;
+ }
+}
+
+static void cycle_handles(BezTriple *bezt)
+{
+ if (bezt->h1 == HD_AUTO) {
+ bezt->h1 = bezt->h2 = HD_VECT;
+ }
+ else if (bezt->h1 == HD_VECT) {
+ bezt->h1 = bezt->h2 = HD_ALIGN;
+ }
+ else if (bezt->h1 == HD_ALIGN) {
+ bezt->h1 = bezt->h2 = HD_FREE;
+ }
+ else {
+ bezt->h1 = bezt->h2 = HD_AUTO;
+ }
+}
+
+enum {
+ PEN_MODAL_FREE_ALIGN_TOGGLE = 1,
+ PEN_MODAL_MOVE_ADJACENT,
+ PEN_MODAL_MOVE_ENTIRE,
+ PEN_MODAL_LINK_HANDLES,
+ PEN_MODAL_LOCK_ANGLE
+};
+
+wmKeyMap *curve_pen_modal_keymap(wmKeyConfig *keyconf)
+{
+ static const EnumPropertyItem modal_items[] = {
+ {PEN_MODAL_FREE_ALIGN_TOGGLE,
+ "FREE_ALIGN_TOGGLE",
+ 0,
+ "Free-Align Toggle",
+ "Move handle of newly added point freely"},
+ {PEN_MODAL_MOVE_ADJACENT,
+ "MOVE_ADJACENT",
+ 0,
+ "Move Adjacent Handle",
+ "Move the closer handle of the adjacent vertex"},
+ {PEN_MODAL_MOVE_ENTIRE,
+ "MOVE_ENTIRE",
+ 0,
+ "Move Entire Point",
+ "Move the entire point using its handles"},
+ {PEN_MODAL_LINK_HANDLES,
+ "LINK_HANDLES",
+ 0,
+ "Link Handles",
+ "Mirror the movement of one handle onto the other"},
+ {PEN_MODAL_LOCK_ANGLE,
+ "LOCK_ANGLE",
+ 0,
+ "Lock Angle",
+ "Move the handle along its current angle"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Curve Pen Modal Map");
+
+ /* This function is called for each spacetype, only needs to add map once */
+ if (keymap && keymap->modal_items) {
+ return NULL;
+ }
+
+ keymap = WM_modalkeymap_ensure(keyconf, "Curve Pen Modal Map", modal_items);
+
+ WM_modalkeymap_assign(keymap, "CURVE_OT_pen");
+
+ return keymap;
+}
+
+static int curve_pen_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ ViewContext vc;
+ Object *obedit = CTX_data_edit_object(C);
+
+ ED_view3d_viewcontext_init(C, &vc, depsgraph);
+ Curve *cu = vc.obedit->data;
+ ListBase *nurbs = &cu->editnurb->nurbs;
+
+ BezTriple *bezt = NULL;
+ BPoint *bp = NULL;
+ Nurb *nu = NULL;
+
+ const struct SelectPick_Params params = {
+ .sel_op = SEL_OP_SET,
+ .deselect_all = false,
+ };
+
+ int ret = OPERATOR_RUNNING_MODAL;
+
+ /* Distance threshold for mouse clicks to affect the spline or its points */
+ const float mval_fl[2] = {UNPACK2(event->mval)};
+
+ const bool extrude_point = RNA_boolean_get(op->ptr, "extrude_point");
+ const bool delete_point = RNA_boolean_get(op->ptr, "delete_point");
+ const bool insert_point = RNA_boolean_get(op->ptr, "insert_point");
+ const bool move_seg = RNA_boolean_get(op->ptr, "move_segment");
+ const bool select_point = RNA_boolean_get(op->ptr, "select_point");
+ const bool move_point = RNA_boolean_get(op->ptr, "move_point");
+ const bool close_spline = RNA_boolean_get(op->ptr, "close_spline");
+ const bool toggle_vector = RNA_boolean_get(op->ptr, "toggle_vector");
+ const bool cycle_handle_type = RNA_boolean_get(op->ptr, "cycle_handle_type");
+ const int close_spline_method = RNA_enum_get(op->ptr, "close_spline_method");
+ const int extrude_handle = RNA_enum_get(op->ptr, "extrude_handle");
+
+ CurvePenData *cpd;
+ if (op->customdata == NULL) {
+ op->customdata = cpd = MEM_callocN(sizeof(CurvePenData), __func__);
+ }
+ else {
+ cpd = (CurvePenData *)(op->customdata);
+ cpd->select_multi = event->modifier == KM_SHIFT;
+ }
+
+ if (event->type == EVT_MODAL_MAP) {
+ if (cpd->msd == NULL) {
+ if (event->val == PEN_MODAL_FREE_ALIGN_TOGGLE) {
+ toggle_sel_bezt_free_align_handles(nurbs);
+ cpd->link_handles = false;
+ }
+ else if (event->val == PEN_MODAL_LINK_HANDLES) {
+ cpd->link_handles = !cpd->link_handles;
+ if (cpd->link_handles) {
+ move_all_selected_points(&vc, event, cpd, nurbs, false);
+ }
+ }
+ else if (event->val == PEN_MODAL_MOVE_ENTIRE) {
+ cpd->move_entire = !cpd->move_entire;
+ }
+ else if (event->val == PEN_MODAL_MOVE_ADJACENT) {
+ cpd->move_adjacent = !cpd->move_adjacent;
+ }
+ else if (event->val == PEN_MODAL_LOCK_ANGLE) {
+ cpd->lock_angle = !cpd->lock_angle;
+ }
+ }
+ else {
+ if (event->val == PEN_MODAL_FREE_ALIGN_TOGGLE) {
+ BezTriple *bezt1 = cpd->msd->nu->bezt + cpd->msd->bezt_index;
+ BezTriple *bezt2 = BKE_nurb_bezt_get_next(cpd->msd->nu, bezt1);
+ toggle_bezt_free_align_handles(bezt1);
+ toggle_bezt_free_align_handles(bezt2);
+ }
+ }
+ }
+
+ if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
+ /* Check if dragging */
+ if (!cpd->dragging && WM_event_drag_test(event, event->prev_press_xy)) {
+ cpd->dragging = true;
+
+ if (cpd->new_point) {
+ init_selected_bezt_handles(nurbs);
+ }
+ }
+
+ if (cpd->dragging) {
+ if (cpd->spline_nearby && move_seg && cpd->msd != NULL) {
+ MoveSegmentData *seg_data = cpd->msd;
+ move_segment(&vc, seg_data, event);
+ cpd->acted = true;
+ if (seg_data->nu && seg_data->nu->type == CU_BEZIER) {
+ BKE_nurb_handles_calc(seg_data->nu);
+ }
+ }
+ else if (cpd->move_adjacent) {
+ move_adjacent_handle(&vc, event, nurbs);
+ cpd->acted = true;
+ }
+ else if (cpd->new_point || (move_point && !cpd->spline_nearby && cpd->found_point)) {
+ /* Move only the bezt handles if it's a new point. */
+ move_all_selected_points(&vc, event, cpd, nurbs, cpd->new_point);
+ cpd->acted = true;
+ }
+ }
+ }
+ else if (ELEM(event->type, LEFTMOUSE)) {
+ if (ELEM(event->val, KM_RELEASE, KM_DBL_CLICK)) {
+ if (delete_point && !cpd->new_point && !cpd->dragging) {
+ if (ED_curve_editnurb_select_pick_ex(C, event->mval, SEL_DIST_FACTOR, &params)) {
+ cpd->acted = delete_point_under_mouse(&vc, event);
+ }
+ }
+
+ /* Close spline on Click, if enabled. */
+ if (!cpd->acted && close_spline && close_spline_method == ON_CLICK && cpd->found_point &&
+ !cpd->dragging) {
+ if (cpd->nu && !is_cyclic(cpd->nu)) {
+ copy_v2_v2_int(vc.mval, event->mval);
+ cpd->acted = make_cyclic_if_endpoints(&vc, cpd->nu, cpd->bezt, cpd->bp);
+ }
+ }
+
+ if (!cpd->acted && (insert_point || extrude_point) && cpd->spline_nearby && !cpd->dragging) {
+ if (insert_point) {
+ insert_point_to_segment(&vc, event);
+ cpd->new_point = true;
+ cpd->acted = true;
+ }
+ else if (extrude_point) {
+ extrude_points_from_selected_vertices(&vc, event, extrude_handle);
+ cpd->acted = true;
+ }
+ }
+
+ if (!cpd->acted && toggle_vector) {
+ int bezt_idx;
+ get_closest_vertex_to_point_in_nurbs(&vc, nurbs, mval_fl, &nu, &bezt, &bp, &bezt_idx);
+ if (bezt) {
+ if (bezt_idx == 1 && cycle_handle_type) {
+ cycle_handles(bezt);
+ cpd->acted = true;
+ }
+ else {
+ toggle_handle_types(bezt, bezt_idx, cpd);
+ }
+
+ if (nu && nu->type == CU_BEZIER) {
+ BKE_nurb_handles_calc(nu);
+ }
+ }
+ }
+
+ if (!cpd->selection_made && !cpd->acted) {
+ if (cpd->select_multi) {
+ int bezt_idx;
+ get_closest_vertex_to_point_in_nurbs(&vc, nurbs, mval_fl, &nu, &bezt, &bp, &bezt_idx);
+ if (bezt) {
+ toggle_select_bezt(bezt, bezt_idx, cu, nu);
+ }
+ else if (bp) {
+ toggle_select_bp(bp, cu, nu);
+ }
+ else {
+ ED_curve_deselect_all(cu->editnurb);
+ }
+ }
+ else if (select_point) {
+ ED_curve_editnurb_select_pick_ex(C, event->mval, SEL_DIST_FACTOR, &params);
+ }
+ }
+
+ if (cpd->msd != NULL) {
+ MEM_freeN(cpd->msd);
+ }
+ MEM_freeN(cpd);
+ ret = OPERATOR_FINISHED;
+ }
+ }
+
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
+ DEG_id_tag_update(obedit->data, 0);
+
+ return ret;
+}
+
+static int curve_pen_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ ViewContext vc;
+ ED_view3d_viewcontext_init(C, &vc, CTX_data_ensure_evaluated_depsgraph(C));
+ Curve *cu = vc.obedit->data;
+ ListBase *nurbs = &cu->editnurb->nurbs;
+
+ BezTriple *bezt = NULL;
+ BPoint *bp = NULL;
+ Nurb *nu = NULL;
+
+ CurvePenData *cpd;
+ op->customdata = cpd = MEM_callocN(sizeof(CurvePenData), __func__);
+
+ /* Distance threshold for mouse clicks to affect the spline or its points */
+ const float mval_fl[2] = {UNPACK2(event->mval)};
+
+ const bool extrude_point = RNA_boolean_get(op->ptr, "extrude_point");
+ const bool insert_point = RNA_boolean_get(op->ptr, "insert_point");
+ const bool move_seg = RNA_boolean_get(op->ptr, "move_segment");
+ const bool move_point = RNA_boolean_get(op->ptr, "move_point");
+ const bool close_spline = RNA_boolean_get(op->ptr, "close_spline");
+ const int close_spline_method = RNA_enum_get(op->ptr, "close_spline_method");
+ const int extrude_handle = RNA_enum_get(op->ptr, "extrude_handle");
+
+ if (ELEM(event->type, LEFTMOUSE) && ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) {
+ /* Get the details of points selected at the start of the operation.
+ * Used for closing the spline when endpoints are clicked consecutively and for selecting a
+ * single point. */
+ get_first_selected_point(cu, vc.v3d, &nu, &bezt, &bp);
+ cpd->nu = nu;
+ cpd->bezt = bezt;
+ cpd->bp = bp;
+
+ /* Get the details of the vertex closest to the mouse at the start of the operation. */
+ Nurb *nu1;
+ BezTriple *bezt1;
+ BPoint *bp1;
+ int bezt_idx = 0;
+ cpd->found_point = get_closest_vertex_to_point_in_nurbs(
+ &vc, nurbs, mval_fl, &nu1, &bezt1, &bp1, &bezt_idx);
+
+ if (move_point && nu1 && !nu1->hide &&
+ (bezt || (bezt1 && !BEZT_ISSEL_IDX(bezt1, bezt_idx)) || (bp1 && !(bp1->f1 & SELECT)))) {
+ /* Select the closest bezt or bp. */
+ ED_curve_deselect_all(cu->editnurb);
+ if (bezt1) {
+ if (bezt_idx == 1) {
+ BEZT_SEL_ALL(bezt1);
+ }
+ else {
+ BEZT_SEL_IDX(bezt1, bezt_idx);
+ }
+ BKE_curve_nurb_vert_active_set(cu, nu1, bezt1);
+ }
+ else if (bp1) {
+ bp1->f1 |= SELECT;
+ BKE_curve_nurb_vert_active_set(cu, nu1, bp1);
+ }
+
+ cpd->selection_made = true;
+ }
+ if (cpd->found_point) {
+ /* Close the spline on press. */
+ if (close_spline && close_spline_method == ON_PRESS && cpd->nu && !is_cyclic(cpd->nu)) {
+ copy_v2_v2_int(vc.mval, event->mval);
+ cpd->new_point = cpd->acted = cpd->link_handles = make_cyclic_if_endpoints(
+ &vc, cpd->nu, cpd->bezt, cpd->bp);
+ }
+ }
+ else if (!cpd->acted) {
+ if (is_spline_nearby(&vc, op, event, SEL_DIST_FACTOR * ED_view3d_select_dist_px())) {
+ cpd->spline_nearby = true;
+
+ /* If move segment is disabled, then insert point on key press and set
+ * "new_point" to true so that the new point's handles can be controlled. */
+ if (insert_point && !move_seg) {
+ insert_point_to_segment(&vc, event);
+ cpd->new_point = cpd->acted = cpd->link_handles = true;
+ }
+ }
+ else if (extrude_point) {
+ extrude_points_from_selected_vertices(&vc, event, extrude_handle);
+ cpd->new_point = cpd->acted = cpd->link_handles = true;
+ }
+ }
+ }
+ WM_event_add_modal_handler(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+void CURVE_OT_pen(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Curve Pen";
+ ot->idname = "CURVE_OT_pen";
+ ot->description = "Construct and edit splines";
+
+ /* api callbacks */
+ ot->invoke = curve_pen_invoke;
+ ot->modal = curve_pen_modal;
+ ot->poll = ED_operator_view3d_active;
+
+ /* flags */
+ ot->flag = OPTYPE_UNDO;
+
+ /* properties */
+ WM_operator_properties_mouse_select(ot);
+
+ RNA_def_boolean(ot->srna,
+ "extrude_point",
+ false,
+ "Extrude Point",
+ "Add a point connected to the last selected point");
+ RNA_def_enum(ot->srna,
+ "extrude_handle",
+ prop_handle_types,
+ HD_VECT,
+ "Extrude Handle Type",
+ "Type of the extruded handle");
+ RNA_def_boolean(ot->srna, "delete_point", false, "Delete Point", "Delete an existing point");
+ RNA_def_boolean(
+ ot->srna, "insert_point", false, "Insert Point", "Insert Point into a curve segment");
+ RNA_def_boolean(ot->srna, "move_segment", false, "Move Segment", "Delete an existing point");
+ RNA_def_boolean(
+ ot->srna, "select_point", false, "Select Point", "Select a point or its handles");
+ RNA_def_boolean(ot->srna, "move_point", false, "Move Point", "Move a point or its handles");
+ RNA_def_boolean(ot->srna,
+ "close_spline",
+ true,
+ "Close Spline",
+ "Make a spline cyclic by clicking endpoints");
+ RNA_def_enum(ot->srna,
+ "close_spline_method",
+ prop_close_spline_method,
+ OFF,
+ "Close Spline Method",
+ "The condition for close spline to activate");
+ RNA_def_boolean(
+ ot->srna, "toggle_vector", false, "Toggle Vector", "Toggle between Vector and Auto handles");
+ RNA_def_boolean(ot->srna,
+ "cycle_handle_type",
+ false,
+ "Cycle Handle Type",
+ "Cycle between all four handle types");
+}
diff --git a/source/blender/editors/curve/editcurve_query.c b/source/blender/editors/curve/editcurve_query.c
index 5bc3010678d..684840775c3 100644
--- a/source/blender/editors/curve/editcurve_query.c
+++ b/source/blender/editors/curve/editcurve_query.c
@@ -88,13 +88,14 @@ static void ED_curve_pick_vert__do_closest(void *userData,
UNUSED_VARS_NDEBUG(handles_visible);
}
-bool ED_curve_pick_vert(ViewContext *vc,
- short sel,
- Nurb **r_nurb,
- BezTriple **r_bezt,
- BPoint **r_bp,
- short *r_handle,
- Base **r_base)
+bool ED_curve_pick_vert_ex(ViewContext *vc,
+ short sel,
+ float dist_px,
+ Nurb **r_nurb,
+ BezTriple **r_bezt,
+ BPoint **r_bp,
+ short *r_handle,
+ Base **r_base)
{
/* (sel == 1): selected gets a disadvantage */
/* in nurb and bezt or bp the nearest is written */
@@ -109,7 +110,7 @@ bool ED_curve_pick_vert(ViewContext *vc,
bool is_changed;
} data = {NULL};
- data.dist = ED_view3d_select_dist_px();
+ data.dist = dist_px;
data.hpoint = 0;
data.select = sel;
data.mval_fl[0] = vc->mval[0];
@@ -143,6 +144,18 @@ bool ED_curve_pick_vert(ViewContext *vc,
return (data.bezt || data.bp);
}
+bool ED_curve_pick_vert(ViewContext *vc,
+ short sel,
+ Nurb **r_nurb,
+ BezTriple **r_bezt,
+ BPoint **r_bp,
+ short *r_handle,
+ Base **r_base)
+{
+ return ED_curve_pick_vert_ex(
+ vc, sel, ED_view3d_select_dist_px(), r_nurb, r_bezt, r_bp, r_handle, r_base);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt
index e820423cdff..0810e76fe37 100644
--- a/source/blender/editors/datafiles/CMakeLists.txt
+++ b/source/blender/editors/datafiles/CMakeLists.txt
@@ -765,6 +765,7 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES
ops.armature.extrude_cursor
ops.armature.extrude_move
ops.curve.draw
+ ops.curve.pen
ops.curve.extrude_cursor
ops.curve.extrude_move
ops.curve.radius
diff --git a/source/blender/editors/include/ED_curve.h b/source/blender/editors/include/ED_curve.h
index 6097e7c69d9..191291ad91b 100644
--- a/source/blender/editors/include/ED_curve.h
+++ b/source/blender/editors/include/ED_curve.h
@@ -53,6 +53,13 @@ void ED_curve_editnurb_free(struct Object *obedit);
bool ED_curve_editnurb_select_pick(struct bContext *C,
const int mval[2],
const struct SelectPick_Params *params);
+/**
+ * \param sel_dist_mul: A multiplier on the default select distance.
+ */
+bool ED_curve_editnurb_select_pick_ex(struct bContext *C,
+ const int mval[2],
+ const float sel_dist_mul,
+ const struct SelectPick_Params *params);
struct Nurb *ED_curve_add_nurbs_primitive(
struct bContext *C, struct Object *obedit, float mat[4][4], int type, int newob);
diff --git a/source/tools b/source/tools
-Subproject 7fd2ed908b4f50140670caf6786e5ed245b7913
+Subproject 1e658ca996f11e5ff3398d89bd81f5b719304a5