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:
authorDilith Jayakody <dilithjay@gmail.com>2022-04-03 20:07:22 +0300
committerDilith Jayakody <dilithjay@gmail.com>2022-04-03 20:07:22 +0300
commit336082acba51b3a237338e2cc316b04320c05ad4 (patch)
tree1efef10ea6854931bbb2c0f2916eba1e2fbfe8b7 /source/blender/editors/curve
parent1cdf8b19e5885c26f7341a0c21d243401a89d50e (diff)
Curves: Curve Pen
This tool can be used to rapidly edit curves. The current set of functionalities for Bezier splines are as follows: The functionalities are divided into three versions of the operator: * Left-Click * Ctrl + Left-Click * Double Click All current functionalities and their defaults are as follows: * Extrude Point: Add a point connected to an existing point. Enabled for Left-Click. * Extrude Handle Type: Type of the handles of the extruded points. Can be either Vector or Auto. Defaults to Vector. * Delete Point: Delete existing point. Enabled for Ctrl + Left-Click. * Insert Point: Insert a point into a curve segment. Enabled for Ctrl + Left-Click. * Move Segment: Move curve segment. Enabled for Left-Click. * Select Point: Select a single point or handle at a time. Enabled for Left-Click. * Move point: Move existing points or handles. Enabled for Left-Click. * Close Spline: Close spline by clicking the endpoints consecutively. Defaults to True. * Close Spline Method: The condition for Close Spline to activate. Can be one of None, On Press or On Click. Defaults to On Click for Left-Click and None for the others. * None: Functionality is turned off. * On Press: Activate on mouse down. This makes it possible to move the handles by dragging immediately after closing the spline. * On Click: Activate on mouse release. This makes it possible to avoid triggering the Close Spline functionality by dragging afterward. * Toggle Vector: Toggle handle between Vector and Auto handle types. Enabled for Double Click on a handle. * Cycle Handle Type: Cycle between all four handle types. Enabled for Double Click on the middle point of a Bezier point. The keybindings for the following functionalities can be adjusted from the modal keymap * Free-Align Toggle: Toggle between Free and Align handle types. Defaults to Left Shift. Activated on hold. * Move Adjacent Handle: Move the closer handle of the adjacent vertex. Defaults to Left Ctrl. Activated on hold. * Move Entire: Move the entire point by moving by grabbing on the handle Defaults to Spacebar. Activated on hold. * Link Handles: Mirror the movement of one handle onto the other. Defaults to Right Ctrl. Activated on press. * Lock Handle Angle: Move the handle along its current angle. Defaults to Left Alt. Activated on hold. All the above functionalities, except for Move Segment and those that work with handles, work similarly in the case of Poly and NURBS splines. Reviewed By: HooglyBoogly, weasel, campbellbarton Differential Revision: http://developer.blender.org/D12155
Diffstat (limited to 'source/blender/editors/curve')
-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
6 files changed, 2006 insertions, 71 deletions
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);
+}
+
/** \} */
/* -------------------------------------------------------------------- */