New freehand curve drawing tool
authorCampbell Barton <ideasman42@gmail.com>
Fri, 15 Apr 2016 08:10:05 +0000 (18:10 +1000)
committerCampbell Barton <ideasman42@gmail.com>
Fri, 15 Apr 2016 10:36:38 +0000 (20:36 +1000)
- Access with Shift-LMB or from the 'Create' toolbar tab.
- Uses curve fitting for bezier curves, with error and corner angle options.
- Optional tablet pressure to curve radius mapping.
- Depth can use the cursor or optionally draw onto the surface,
  for the entire stroke or using the stroke start.
- Stroke plane can optionally be perpendicular to, or aligned to the surface normal.
- Optional radius tapering and for start/end points.
- Supports operator redo and calling from Python.

extern/curve_fit_nd/curve_fit_nd.h
extern/curve_fit_nd/intern/curve_fit_cubic.c
release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/blenkernel/intern/scene.c
source/blender/blenloader/intern/versioning_270.c
source/blender/editors/curve/CMakeLists.txt
source/blender/editors/curve/curve_intern.h
source/blender/editors/curve/curve_ops.c
source/blender/editors/curve/editcurve_paint.c [new file with mode: 0644]
source/blender/makesdna/DNA_scene_types.h
source/blender/makesrna/intern/rna_scene.c

index 67b0ed75b8ef031beb8b618bcf8f1d190c402f6f..d20921c186a9d72198b1020e1a054a6abfca0052 100644 (file)
@@ -55,7 +55,7 @@
  *
  * \returns zero on success, nonzero is reserved for error values.
  */
-int curve_fit_cubic_from_points_db(
+int curve_fit_cubic_to_points_db(
         const double       *points,
         const unsigned int  points_len,
         const unsigned int  dims,
@@ -67,7 +67,7 @@ int curve_fit_cubic_from_points_db(
         unsigned int **r_cubic_orig_index,
         unsigned int **r_corner_index_array, unsigned int *r_corner_index_len);
 
-int curve_fit_cubic_from_points_fl(
+int curve_fit_cubic_to_points_fl(
         const float        *points,
         const unsigned int  points_len,
         const unsigned int  dims,
index d623b51ed0b8f9b83b54885c554902197fbf84b2..810cf92760d539198865142518e32e14cfb5d7a1 100644 (file)
@@ -846,7 +846,7 @@ static void fit_cubic_to_points(
  * Take an array of 3d points.
  * return the cubic splines
  */
-int curve_fit_cubic_from_points_db(
+int curve_fit_cubic_to_points_db(
         const double *points,
         const uint    points_len,
         const uint    dims,
@@ -984,9 +984,9 @@ int curve_fit_cubic_from_points_db(
 }
 
 /**
- * A version of #curve_fit_cubic_from_points_db to handle floats
+ * A version of #curve_fit_cubic_to_points_db to handle floats
  */
-int curve_fit_cubic_from_points_fl(
+int curve_fit_cubic_to_points_fl(
         const float  *points,
         const uint    points_len,
         const uint    dims,
@@ -1009,7 +1009,7 @@ int curve_fit_cubic_from_points_fl(
        float  *cubic_array_fl = NULL;
        uint    cubic_array_len = 0;
 
-       int result = curve_fit_cubic_from_points_db(
+       int result = curve_fit_cubic_to_points_db(
                points_db, points_len, dims, error_threshold, corners, corners_len,
                &cubic_array_db, &cubic_array_len,
                r_cubic_orig_index,
index d8286c64f39b18b05d887213784b10aa4eb235a4..01b46eb6aedf20cd0d4c0ba0b9b45b9d4c12b57e 100644 (file)
@@ -136,6 +136,7 @@ class VIEW3D_PT_tools_add_object(View3DPanel, Panel):
 
     @staticmethod
     def draw_add_curve(layout, label=False):
+
         if label:
             layout.label(text="Bezier:")
         layout.operator("curve.primitive_bezier_curve_add", text="Bezier", icon='CURVE_BEZCURVE')
@@ -149,6 +150,10 @@ class VIEW3D_PT_tools_add_object(View3DPanel, Panel):
         layout.operator("curve.primitive_nurbs_circle_add", text="Nurbs Circle", icon='CURVE_NCIRCLE')
         layout.operator("curve.primitive_nurbs_path_add", text="Path", icon='CURVE_PATH')
 
+        layout.separator()
+
+        layout.operator("curve.draw", icon='LINE_DATA')
+
     @staticmethod
     def draw_add_surface(layout):
         layout.operator("surface.primitive_nurbs_surface_curve_add", text="Nurbs Curve", icon='SURFACE_NCURVE')
@@ -546,8 +551,61 @@ class VIEW3D_PT_tools_add_curve_edit(View3DPanel, Panel):
 
         VIEW3D_PT_tools_add_object.draw_add_curve(col, label=True)
 
-# ********** default tools for editmode_surface ****************
 
+class VIEW3D_PT_tools_curveedit_options_stroke(View3DPanel, Panel):
+    bl_category = "Options"
+    bl_context = "curve_edit"
+    bl_label = "Curve Stroke"
+
+    def draw(self, context):
+        layout = self.layout
+
+        tool_settings = context.tool_settings
+        cps = tool_settings.curve_paint_settings
+
+        col = layout.column()
+
+        col.prop(cps, "curve_type")
+
+        if cps.curve_type == 'BEZIER':
+            col.label("Bezier Options:")
+            col.prop(cps, "error_threshold")
+            col.prop(cps, "use_corners_detect")
+
+            col = layout.column()
+            col.active = cps.use_corners_detect
+            col.prop(cps, "corner_angle")
+
+        col.label("Pressure Radius:")
+        row = layout.row(align=True)
+        rowsub = row.row(align=True)
+        rowsub.prop(cps, "radius_min", text="Min")
+        rowsub.prop(cps, "radius_max", text="Max")
+
+        row.prop(cps, "use_pressure_radius", text="", icon_only=True)
+
+        col = layout.column()
+        col.label("Taper Radius:")
+        row = layout.row(align=True)
+        row.prop(cps, "radius_taper_start", text="Start")
+        row.prop(cps, "radius_taper_end", text="End")
+
+        col = layout.column()
+        col.label("Projection Depth:")
+        row = layout.row(align=True)
+        row.prop(cps, "depth_mode", expand=True)
+
+        col = layout.column()
+        if cps.depth_mode == 'SURFACE':
+            col.prop(cps, "use_stroke_endpoints")
+            if cps.use_stroke_endpoints:
+                colsub = layout.column(align=True)
+                colsub.prop(cps, "surface_plane", expand=True)
+            else:
+                col.prop(cps, "radius_offset")
+
+
+# ********** default tools for editmode_surface ****************
 
 class VIEW3D_PT_tools_transform_surface(View3DPanel, Panel):
     bl_category = "Tools"
index 6b297a143d601dbdabbea0288a7d46c0d8be9bfd..c0b791184672236d2be0723efe85b502f3d0bab2 100644 (file)
@@ -616,6 +616,12 @@ void BKE_scene_init(Scene *sce)
        sce->toolsettings->skgen_subdivisions[1] = SKGEN_SUB_LENGTH;
        sce->toolsettings->skgen_subdivisions[2] = SKGEN_SUB_ANGLE;
 
+       sce->toolsettings->curve_paint_settings.curve_type = CU_BEZIER;
+       sce->toolsettings->curve_paint_settings.flag |= CURVE_PAINT_FLAG_CORNERS_DETECT;
+       sce->toolsettings->curve_paint_settings.error_threshold = 8;
+       sce->toolsettings->curve_paint_settings.radius_max = 1.0f;
+       sce->toolsettings->curve_paint_settings.corner_angle = DEG2RADF(70.0f);
+
        sce->toolsettings->statvis.overhang_axis = OB_NEGZ;
        sce->toolsettings->statvis.overhang_min = 0;
        sce->toolsettings->statvis.overhang_max = DEG2RADF(45.0f);
index b9191d545edd73983db3d2028da05c48b1d2c98a..18740d43a2622d190ddd3c0f9e1ae77f5731cebb 100644 (file)
@@ -1072,5 +1072,17 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main)
                                }
                        }
                }
+
+               for (Scene *scene = main->scene.first; scene; scene = scene->id.next) {
+                       CurvePaintSettings *cps = &scene->toolsettings->curve_paint_settings;
+                       if (cps->error_threshold == 0) {
+                               cps->curve_type = CU_BEZIER;
+                               cps->flag |= CURVE_PAINT_FLAG_CORNERS_DETECT;
+                               cps->error_threshold = 8;
+                               cps->radius_max = 1.0f;
+                               cps->corner_angle = DEG2RADF(70.0f);
+                       }
+               }
+
        }
 }
index 83346e9550c20a59d1c909f9b273a7f47312ac45..ebdf6bb43ff883e15eac04f24eb1c73875a94cc2 100644 (file)
@@ -23,20 +23,24 @@ set(INC
        ../../blenkernel
        ../../blenlib
        ../../blentranslation
+       ../../gpu
        ../../makesdna
        ../../makesrna
        ../../windowmanager
        ../../../../intern/guardedalloc
+       ../../../../intern/glew-mx
+       ../../../../extern/curve_fit_nd
 )
 
 set(INC_SYS
-
+       ${GLEW_INCLUDE_PATH}
 )
 
 set(SRC
        curve_ops.c
        editcurve.c
        editcurve_add.c
+       editcurve_paint.c
        editcurve_select.c
        editfont.c
 
@@ -47,4 +51,6 @@ if(WITH_INTERNATIONAL)
        add_definitions(-DWITH_INTERNATIONAL)
 endif()
 
+add_definitions(${GL_DEFINITIONS})
+
 blender_add_lib(bf_editor_curve "${SRC}" "${INC}" "${INC_SYS}")
index 1af0b49bd86c1f4ff9717f917a6098b02d89acc5..d63616e4f43f074604bf57168cd909b74e4256e0 100644 (file)
@@ -166,4 +166,7 @@ void SURFACE_OT_primitive_nurbs_surface_cylinder_add(struct wmOperatorType *ot);
 void SURFACE_OT_primitive_nurbs_surface_sphere_add(struct wmOperatorType *ot);
 void SURFACE_OT_primitive_nurbs_surface_torus_add(struct wmOperatorType *ot);
 
+/* editcurve_paint.c */
+void CURVE_OT_draw(struct wmOperatorType *ot);
+
 #endif /* __CURVE_INTERN_H__ */
index 967187e66c9a8d07d050db34b0eb1f8d4cb957a7..d1994c8fc15bc1763a08da6628a48873d1fb3603 100644 (file)
@@ -135,6 +135,7 @@ void ED_operatortypes_curve(void)
        WM_operatortype_append(CURVE_OT_make_segment);
        WM_operatortype_append(CURVE_OT_spin);
        WM_operatortype_append(CURVE_OT_vertex_add);
+       WM_operatortype_append(CURVE_OT_draw);
        WM_operatortype_append(CURVE_OT_extrude);
        WM_operatortype_append(CURVE_OT_cyclic_toggle);
 
@@ -234,6 +235,9 @@ void ED_keymap_curve(wmKeyConfig *keyconf)
 
        WM_keymap_add_item(keymap, "CURVE_OT_vertex_add", ACTIONMOUSE, KM_CLICK, KM_CTRL, 0);
 
+       kmi = WM_keymap_add_item(keymap, "CURVE_OT_draw", ACTIONMOUSE, KM_PRESS, KM_SHIFT, 0);
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+
        kmi = WM_keymap_add_item(keymap, "CURVE_OT_select_all", AKEY, KM_PRESS, 0, 0);
        RNA_enum_set(kmi->ptr, "action", SEL_TOGGLE);
        kmi = WM_keymap_add_item(keymap, "CURVE_OT_select_all", IKEY, KM_PRESS, KM_CTRL, 0);
diff --git a/source/blender/editors/curve/editcurve_paint.c b/source/blender/editors/curve/editcurve_paint.c
new file mode 100644 (file)
index 0000000..4af123c
--- /dev/null
@@ -0,0 +1,1150 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/curve/editcurve_paint.c
+ *  \ingroup edcurve
+ */
+
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_mempool.h"
+
+#include "BKE_context.h"
+#include "BKE_curve.h"
+#include "BKE_depsgraph.h"
+#include "BKE_fcurve.h"
+#include "BKE_report.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_space_api.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+#include "ED_curve.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "curve_intern.h"
+
+#include "UI_resources.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#define USE_SPLINE_FIT
+
+#ifdef USE_SPLINE_FIT
+#include "curve_fit_nd.h"
+#endif
+
+/* Distance between input samples */
+#define STROKE_SAMPLE_DIST_MIN_PX 3
+#define STROKE_SAMPLE_DIST_MAX_PX 6
+
+
+/* -------------------------------------------------------------------- */
+
+/** \name Depth Utilities
+ * \{ */
+
+
+static float depth_read_zbuf(const ViewContext *vc, int x, int y)
+{
+       ViewDepths *vd = vc->rv3d->depths;
+
+       if (vd && vd->depths && x > 0 && y > 0 && x < vd->w && y < vd->h)
+               return vd->depths[y * vd->w + x];
+       else
+               return -1.0f;
+}
+
+static bool depth_unproject(
+        const ARegion *ar, const bglMats *mats,
+        const int mval[2], const float depth,
+        float r_location_world[3])
+{
+       double p[3];
+       if (gluUnProject(
+               (double)ar->winrct.xmin + mval[0] + 0.5,
+               (double)ar->winrct.ymin + mval[1] + 0.5,
+               depth, mats->modelview, mats->projection, (const GLint *)mats->viewport,
+               &p[0], &p[1], &p[2]))
+       {
+               copy_v3fl_v3db(r_location_world, p);
+               return true;
+       }
+       return false;
+}
+
+static bool depth_read_normal(
+        const ViewContext *vc, const bglMats *mats, const int mval[2],
+        float r_normal[3])
+{
+       /* pixels surrounding */
+       bool  depths_valid[9] = {false};
+       float coords[9][3] = {{0}};
+
+       ARegion *ar = vc->ar;
+       const ViewDepths *depths = vc->rv3d->depths;
+
+       for (int x = 0, i = 0; x < 2; x++) {
+               for (int y = 0; y < 2; y++) {
+                       const int mval_ofs[2] = {mval[0] + (x - 1), mval[1] + (y - 1)};
+
+                       float depth = depth_read_zbuf(vc, mval_ofs[0], mval_ofs[1]);
+                       if ((depth > depths->depth_range[0]) && (depth < depths->depth_range[1])) {
+                               if (depth_unproject(ar, mats, mval_ofs, depth, coords[i])) {
+                                       depths_valid[i] = true;
+                               }
+                       }
+                       i++;
+               }
+       }
+
+       const int edges[2][6][2] = {
+           /* x edges */
+           {{0, 1}, {1, 2},
+            {3, 4}, {4, 5},
+            {6, 7}, {7, 8}},
+           /* y edges */
+           {{0, 3}, {3, 6},
+            {1, 4}, {4, 7},
+            {2, 5}, {5, 8}},
+       };
+
+       float cross[2][3] = {{0.0f}};
+
+       for (int i = 0; i < 6; i++) {
+               for (int axis = 0; axis < 2; axis++) {
+                       if (depths_valid[edges[axis][i][0]] && depths_valid[edges[axis][i][1]]) {
+                               float delta[3];
+                               sub_v3_v3v3(delta, coords[edges[axis][i][0]], coords[edges[axis][i][1]]);
+                               add_v3_v3(cross[axis], delta);
+                       }
+               }
+       }
+
+       cross_v3_v3v3(r_normal, cross[0], cross[1]);
+
+       if (normalize_v3(r_normal) != 0.0f) {
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+/** \} */
+
+
+/* -------------------------------------------------------------------- */
+
+/** \name StrokeElem / #RNA_OperatorStrokeElement Conversion Functions
+ * \{ */
+
+struct StrokeElem {
+       float mval[2];
+       float location_world[3];
+       float location_local[3];
+       float pressure;
+};
+
+struct CurveDrawData {
+       short init_event_type;
+       short curve_type;
+
+       /* projecting 2D into 3D space */
+       struct {
+               /* use a plane or project to the surface */
+               bool use_plane;
+               float    plane[4];
+
+               /* use 'rv3d->depths', note that this will become 'damaged' while drawing, but thats OK. */
+               bool use_depth;
+       } project;
+
+       /* cursor sampling */
+       struct {
+               /* use substeps, needed for nicely interpolating depth */
+               bool use_substeps;
+       } sample;
+
+       struct {
+               float min, max, range;
+               float offset;
+       } radius;
+
+       struct {
+               float mouse[2];
+               /* used incase we can't calculate the depth */
+               float location_world[3];
+
+               float location_world_valid[3];
+
+               const struct StrokeElem *selem;
+       } prev;
+
+       ViewContext vc;
+       bglMats mats;
+       enum {
+               CURVE_DRAW_IDLE = 0,
+               CURVE_DRAW_PAINTING = 1,
+       } state;
+
+       /* StrokeElem */
+       BLI_mempool *stroke_elem_pool;
+
+       void *draw_handle_view;
+};
+
+static float stroke_elem_radius(const struct CurveDrawData *cdd, const struct StrokeElem *selem)
+{
+       const Curve *cu = cdd->vc.obedit->data;
+       return ((selem->pressure * cdd->radius.range) + cdd->radius.min) * cu->ext2;
+}
+
+static void stroke_elem_interp(
+        struct StrokeElem *selem_out,
+        const struct StrokeElem *selem_a,  const struct StrokeElem *selem_b, float t)
+{
+       interp_v2_v2v2(selem_out->mval, selem_a->mval, selem_b->mval, t);
+       interp_v3_v3v3(selem_out->location_world, selem_a->location_world, selem_b->location_world, t);
+       interp_v3_v3v3(selem_out->location_local, selem_a->location_local, selem_b->location_local, t);
+       selem_out->pressure = interpf(selem_a->pressure, selem_b->pressure, t);
+}
+
+
+/**
+ * Sets the depth from #StrokeElem.mval
+ */
+static bool stroke_elem_project(
+        const struct CurveDrawData *cdd,
+        const int mval_i[2], const float mval_fl[2],
+        const float radius_offset, const float radius,
+        float r_location_world[3])
+{
+       View3D *v3d = cdd->vc.v3d;
+       ARegion *ar = cdd->vc.ar;
+       RegionView3D *rv3d = cdd->vc.rv3d;
+
+       bool is_location_world_set = false;
+
+       /* project to 'location_world' */
+       if (cdd->project.use_plane) {
+               /* get the view vector to 'location' */
+               float ray_origin[3], ray_direction[3];
+               ED_view3d_win_to_ray(cdd->vc.ar, v3d, mval_fl, ray_origin, ray_direction, false);
+
+               float lambda;
+               if (isect_ray_plane_v3(ray_origin, ray_direction, cdd->project.plane, &lambda, true)) {
+                       madd_v3_v3v3fl(r_location_world, ray_origin, ray_direction, lambda);
+                       is_location_world_set = true;
+               }
+       }
+       else {
+               const ViewDepths *depths = rv3d->depths;
+               if (depths &&
+                   ((unsigned int)mval_i[0] < depths->w) &&
+                   ((unsigned int)mval_i[1] < depths->h))
+               {
+                       float depth = depth_read_zbuf(&cdd->vc, mval_i[0], mval_i[1]);
+                       if ((depth > depths->depth_range[0]) && (depth < depths->depth_range[1])) {
+                               if (depth_unproject(ar, &cdd->mats, mval_i, depth, r_location_world)) {
+                                       is_location_world_set = true;
+
+                                       if (radius_offset != 0.0f) {
+                                               float normal[3];
+                                               if (depth_read_normal(&cdd->vc, &cdd->mats, mval_i, normal)) {
+                                                       madd_v3_v3fl(r_location_world, normal, radius_offset * radius);
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return is_location_world_set;
+}
+
+static bool stroke_elem_project_fallback(
+        const struct CurveDrawData *cdd,
+        const int mval_i[2], const float mval_fl[2],
+        const float radius_offset, const float radius,
+        const float location_fallback_depth[3],
+        float r_location_world[3], float r_location_local[3])
+{
+       bool is_depth_found = stroke_elem_project(
+               cdd, mval_i, mval_fl,
+               radius_offset, radius,
+               r_location_world);
+       if (is_depth_found == false) {
+               ED_view3d_win_to_3d(cdd->vc.ar, location_fallback_depth, mval_fl, r_location_world);
+       }
+       mul_v3_m4v3(r_location_local, cdd->vc.obedit->imat, r_location_world);
+
+       return is_depth_found;
+}
+
+/**
+ * \note #StrokeElem.mval & #StrokeElem.pressure must be set first.
+ */
+static bool stroke_elem_project_fallback_elem(
+        const struct CurveDrawData *cdd,
+        const float location_fallback_depth[3],
+        struct StrokeElem *selem)
+{
+       const int mval_i[2] = {UNPACK2(selem->mval)};
+       const float radius = stroke_elem_radius(cdd, selem);
+       return stroke_elem_project_fallback(
+               cdd, mval_i, selem->mval,
+               cdd->radius.offset, radius,
+               location_fallback_depth,
+               selem->location_world, selem->location_local);
+}
+
+/** \} */
+
+
+/* -------------------------------------------------------------------- */
+
+/** \name Operator/Stroke Conversion
+ * \{ */
+
+static void curve_draw_stroke_to_operator_elem(
+        wmOperator *op, const struct StrokeElem *selem)
+{
+       PointerRNA itemptr;
+       RNA_collection_add(op->ptr, "stroke", &itemptr);
+
+       RNA_float_set_array(&itemptr, "mouse", selem->mval);
+       RNA_float_set_array(&itemptr, "location", selem->location_world);
+       RNA_float_set(&itemptr, "pressure", selem->pressure);
+}
+
+static void curve_draw_stroke_from_operator_elem(
+        wmOperator *op, PointerRNA *itemptr)
+{
+       struct CurveDrawData *cdd = op->customdata;
+
+       struct StrokeElem *selem = BLI_mempool_calloc(cdd->stroke_elem_pool);
+
+       RNA_float_get_array(itemptr, "mouse", selem->mval);
+       RNA_float_get_array(itemptr, "location", selem->location_world);
+       mul_v3_m4v3(selem->location_local, cdd->vc.obedit->imat, selem->location_world);
+       selem->pressure = RNA_float_get(itemptr, "pressure");
+}
+
+static void curve_draw_stroke_to_operator(wmOperator *op)
+{
+       struct CurveDrawData *cdd = op->customdata;
+
+       BLI_mempool_iter iter;
+       const struct StrokeElem *selem;
+
+       BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+       for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) {
+               curve_draw_stroke_to_operator_elem(op, selem);
+       }
+}
+
+static void curve_draw_stroke_from_operator(wmOperator *op)
+{
+       RNA_BEGIN (op->ptr, itemptr, "stroke")
+       {
+               curve_draw_stroke_from_operator_elem(op, &itemptr);
+       }
+       RNA_END;
+}
+
+/** \} */
+
+
+/* -------------------------------------------------------------------- */
+
+/** \name Operator Callbacks & Helpers
+ * \{ */
+
+static void curve_draw_stroke_3d(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
+{
+       wmOperator *op = arg;
+       struct CurveDrawData *cdd = op->customdata;
+
+       const int stroke_len = BLI_mempool_count(cdd->stroke_elem_pool);
+
+       if (stroke_len == 0) {
+               return;
+       }
+
+       View3D *v3d = cdd->vc.v3d;
+       Object *obedit = cdd->vc.obedit;
+       Curve *cu = obedit->data;
+
+       UI_ThemeColor(TH_WIRE);
+
+       if (cu->ext2 > 0.0f) {
+               GLUquadricObj *qobj = gluNewQuadric();
+
+               gluQuadricDrawStyle(qobj, GLU_FILL);
+
+               BLI_mempool_iter iter;
+               const struct StrokeElem *selem;
+
+               const float  location_zero[3] = {0};
+               const float *location_prev = location_zero;
+
+               /* scale to edit-mode space */
+               glPushMatrix();
+               glMultMatrixf(obedit->obmat);
+
+               BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+               for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) {
+                       glTranslatef(
+                               selem->location_local[0] - location_prev[0],
+                               selem->location_local[1] - location_prev[1],
+                               selem->location_local[2] - location_prev[2]);
+                       location_prev = selem->location_local;
+                       const float radius = stroke_elem_radius(cdd, selem);
+                       gluSphere(qobj, radius , 12, 8);
+
+                       location_prev = selem->location_local;
+               }
+
+               glPopMatrix();
+
+               gluDeleteQuadric(qobj);
+       }
+
+       if (stroke_len > 1) {
+               float (*coord_array)[3] = MEM_mallocN(sizeof(*coord_array) * stroke_len, __func__);
+
+               {
+                       BLI_mempool_iter iter;
+                       const struct StrokeElem *selem;
+                       int i;
+                       BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+                       for (selem = BLI_mempool_iterstep(&iter), i = 0; selem; selem = BLI_mempool_iterstep(&iter), i++) {
+                               copy_v3_v3(coord_array[i], selem->location_world);
+                       }
+               }
+
+               {
+                       glEnable(GL_BLEND);
+                       glEnable(GL_LINE_SMOOTH);
+
+                       glEnableClientState(GL_VERTEX_ARRAY);
+                       glVertexPointer(3, GL_FLOAT, 0, coord_array);
+
+                       cpack(0x0);
+                       glLineWidth(3.0f);
+                       glDrawArrays(GL_LINE_STRIP, 0, stroke_len);
+
+                       if (v3d->zbuf)
+                               glDisable(GL_DEPTH_TEST);
+
+                       cpack(0xffffffff);
+                       glLineWidth(1.0f);
+                       glDrawArrays(GL_LINE_STRIP, 0, stroke_len);
+
+                       if (v3d->zbuf)
+                               glEnable(GL_DEPTH_TEST);
+
+                       glDisableClientState(GL_VERTEX_ARRAY);
+
+                       glDisable(GL_BLEND);
+                       glDisable(GL_LINE_SMOOTH);
+               }
+
+               MEM_freeN(coord_array);
+       }
+}
+
+static void curve_draw_event_add(wmOperator *op, const wmEvent *event)
+{
+       struct CurveDrawData *cdd = op->customdata;
+       Object *obedit = cdd->vc.obedit;
+
+       invert_m4_m4(obedit->imat, obedit->obmat);
+
+       struct StrokeElem *selem = BLI_mempool_calloc(cdd->stroke_elem_pool);
+
+       ARRAY_SET_ITEMS(selem->mval, event->mval[0], event->mval[1]);
+
+       /* handle pressure sensitivity (which is supplied by tablets) */
+       if (event->tablet_data) {
+               const wmTabletData *wmtab = event->tablet_data;
+               selem->pressure = wmtab->Pressure;
+       }
+       else {
+               selem->pressure = 1.0f;
+       }
+
+       bool is_depth_found = stroke_elem_project_fallback_elem(
+               cdd, cdd->prev.location_world_valid, selem);
+
+       if (is_depth_found) {
+               /* use the depth if a fallback wasn't used */
+               copy_v3_v3(cdd->prev.location_world_valid, selem->location_world);
+       }
+       copy_v3_v3(cdd->prev.location_world, selem->location_world);
+
+       float len_sq = len_squared_v2v2(cdd->prev.mouse, selem->mval);
+       copy_v2_v2(cdd->prev.mouse, selem->mval);
+
+       if (cdd->sample.use_substeps && cdd->prev.selem) {
+               const struct StrokeElem selem_target = *selem;
+               struct StrokeElem *selem_new_last = selem;
+               if (len_sq >= SQUARE(STROKE_SAMPLE_DIST_MAX_PX)) {
+                       int n = (int)ceil(sqrt((double)len_sq)) / STROKE_SAMPLE_DIST_MAX_PX ;
+
+                       for (int i = 1; i < n; i++) {
+                               struct StrokeElem *selem_new = selem_new_last;
+                               stroke_elem_interp(selem_new, cdd->prev.selem, &selem_target, (float)i / n);
+
+                               const bool is_depth_found_substep = stroke_elem_project_fallback_elem(
+                                       cdd, cdd->prev.location_world_valid, selem_new);
+                               if (is_depth_found == false) {
+                                       if (is_depth_found_substep) {
+                                               copy_v3_v3(cdd->prev.location_world_valid, selem_new->location_world);
+                                       }
+                               }
+
+                               selem_new_last = BLI_mempool_calloc(cdd->stroke_elem_pool);
+                       }
+               }
+               selem = selem_new_last;
+               *selem_new_last = selem_target;
+       }
+
+       cdd->prev.selem = selem;
+
+       ED_region_tag_redraw(cdd->vc.ar);
+}
+
+static void curve_draw_event_add_first(wmOperator *op, const wmEvent *event)
+{
+       struct CurveDrawData *cdd = op->customdata;
+       const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings;
+
+       /* add first point */
+       curve_draw_event_add(op, event);
+
+       if ((cps->depth_mode == CURVE_PAINT_PROJECT_SURFACE) && cdd->project.use_depth &&
+           (cps->flag & CURVE_PAINT_FLAG_DEPTH_STROKE_ENDPOINTS))
+       {
+               RegionView3D *rv3d = cdd->vc.rv3d;
+
+               cdd->project.use_depth = false;
+               cdd->project.use_plane = true;
+
+               float normal[3] = {0.0f};
+               if (ELEM(cps->surface_plane,
+                        CURVE_PAINT_SURFACE_PLANE_NORMAL_VIEW,
+                        CURVE_PAINT_SURFACE_PLANE_NORMAL_SURFACE))
+               {
+                       if (depth_read_normal(&cdd->vc, &cdd->mats, event->mval, normal)) {
+                               if (cps->surface_plane == CURVE_PAINT_SURFACE_PLANE_NORMAL_VIEW) {
+                                       float cross_a[3], cross_b[3];
+                                       cross_v3_v3v3(cross_a, rv3d->viewinv[2], normal);
+                                       cross_v3_v3v3(cross_b, normal, cross_a);
+                                       copy_v3_v3(normal, cross_b);
+                               }
+                       }
+               }
+
+               /* CURVE_PAINT_SURFACE_PLANE_VIEW or fallback */
+               if (is_zero_v3(normal)) {
+                       copy_v3_v3(normal, rv3d->viewinv[2]);
+               }
+
+               normalize_v3_v3(cdd->project.plane, normal);
+               cdd->project.plane[3] = -dot_v3v3(cdd->project.plane, cdd->prev.location_world_valid);
+       }
+
+       cdd->init_event_type = event->type;
+       cdd->state = CURVE_DRAW_PAINTING;
+}
+
+static bool curve_draw_init(bContext *C, wmOperator *op, bool is_invoke)
+{
+       BLI_assert(op->customdata == NULL);
+
+       struct CurveDrawData *cdd = MEM_callocN(sizeof(*cdd), __func__);
+
+       if (is_invoke) {
+               view3d_set_viewcontext(C, &cdd->vc);
+               if (ELEM(NULL, cdd->vc.ar, cdd->vc.rv3d, cdd->vc.v3d, cdd->vc.win, cdd->vc.scene)) {
+                       MEM_freeN(cdd);
+                       BKE_report(op->reports, RPT_ERROR, "Unable to access 3D viewport.");
+                       return false;
+               }
+       }
+       else {
+               cdd->vc.scene = CTX_data_scene(C);
+               cdd->vc.obedit = CTX_data_edit_object(C);
+       }
+
+       op->customdata = cdd;
+
+       const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings;
+
+       cdd->curve_type = cps->curve_type;
+
+       cdd->radius.min = cps->radius_min;
+       cdd->radius.max = cps->radius_max;
+       cdd->radius.range = cps->radius_max - cps->radius_min;
+       cdd->radius.offset = cps->radius_offset;
+
+       cdd->stroke_elem_pool = BLI_mempool_create(
+               sizeof(struct StrokeElem), 0, 512, BLI_MEMPOOL_ALLOW_ITER);
+
+       return true;
+}
+
+
+static void curve_draw_exit(wmOperator *op)
+{
+       struct CurveDrawData *cdd = op->customdata;
+       if (cdd) {
+               if (cdd->draw_handle_view) {
+                       ED_region_draw_cb_exit(cdd->vc.ar->type, cdd->draw_handle_view);
+                       WM_cursor_modal_restore(cdd->vc.win);
+               }
+
+               if (cdd->stroke_elem_pool) {
+                       BLI_mempool_destroy(cdd->stroke_elem_pool);
+               }
+
+               MEM_freeN(cdd);
+               op->customdata = NULL;
+       }
+}
+
+/**
+ * Initialize values before calling 'exec' (when running interactively).
+ */
+static void curve_draw_exec_precalc(wmOperator *op)
+{
+       struct CurveDrawData *cdd = op->customdata;
+       const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings;
+       PropertyRNA *prop;
+
+       prop = RNA_struct_find_property(op->ptr, "corner_angle");
+       if (!RNA_property_is_set(op->ptr, prop)) {
+               const float corner_angle = (cps->flag & CURVE_PAINT_FLAG_CORNERS_DETECT) ? cps->corner_angle : M_PI;
+               RNA_property_float_set(op->ptr, prop, corner_angle);
+       }
+
+       prop = RNA_struct_find_property(op->ptr, "error_threshold");
+       if (!RNA_property_is_set(op->ptr, prop)) {
+
+               /* error isnt set so we'll have to calculate it from the pixel values */
+               BLI_mempool_iter iter;
+               const struct StrokeElem *selem, *selem_prev;
+
+               float len_3d = 0.0f, len_2d = 0.0f;
+               float scale_px;  /* pixel to local space scale */
+
+               int i = 0;
+               BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+               selem_prev = BLI_mempool_iterstep(&iter);
+               for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter), i++) {
+                       len_3d += len_v3v3(selem->location_local, selem_prev->location_local);
+                       len_2d += len_v2v2(selem->mval, selem_prev->mval);
+                       selem_prev = selem;
+               }
+               scale_px = ((len_3d > 0.0f) && (len_2d > 0.0f)) ?  (len_3d / len_2d) : 0.0f;
+               float error_threshold = (cps->error_threshold * U.pixelsize) * scale_px;
+               RNA_property_float_set(op->ptr, prop, error_threshold);
+       }
+
+       if ((cps->radius_taper_start != 0.0f) ||
+           (cps->radius_taper_end   != 0.0f))
+       {
+               /* note, we could try to de-duplicate the length calculations above */
+               const int stroke_len = BLI_mempool_count(cdd->stroke_elem_pool);
+
+               BLI_mempool_iter iter;
+               struct StrokeElem *selem, *selem_prev;
+
+               float *lengths = MEM_mallocN(sizeof(float) * stroke_len, __func__);
+               struct StrokeElem **selem_array = MEM_mallocN(sizeof(*selem_array) * stroke_len, __func__);
+               lengths[0] = 0.0f;
+
+               float len_3d = 0.0f;
+
+               int i = 1;
+               BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+               selem_prev = BLI_mempool_iterstep(&iter);
+               selem_array[0] = selem_prev;
+               for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter), i++) {
+                       const float len_3d_segment = len_v3v3(selem->location_local, selem_prev->location_local);
+                       len_3d += len_3d_segment;
+                       lengths[i] = len_3d;
+                       selem_array[i] = selem;
+                       selem_prev = selem;
+               }
+
+               if (cps->radius_taper_start != 0.0) {
+                       selem_array[0]->pressure = 0.0f;
+                       const float len_taper_max = cps->radius_taper_start * len_3d;
+                       for (i = 1; i < stroke_len && lengths[i] < len_taper_max; i++) {
+                               selem_array[i]->pressure *=   lengths[i] / len_taper_max;
+                       }
+               }
+
+               if (cps->radius_taper_end != 0.0) {
+                       selem_array[stroke_len - 1]->pressure = 0.0f;
+                       const float len_taper_max = cps->radius_taper_end * len_3d;
+                       const float len_taper_min = len_3d - len_taper_max;
+                       for (i = stroke_len - 2; i > 0 && lengths[i] > len_taper_min; i--) {
+                               selem_array[i]->pressure *= (len_3d - lengths[i]) / len_taper_max;
+                       }
+               }
+
+               MEM_freeN(lengths);
+               MEM_freeN(selem_array);
+       }
+}
+
+static int curve_draw_exec(bContext *C, wmOperator *op)
+{
+       if (op->customdata == NULL) {
+               if (!curve_draw_init(C, op, false)) {
+                       return OPERATOR_CANCELLED;
+               }
+       }
+
+       struct CurveDrawData *cdd = op->customdata;
+
+       const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings;
+       Object *obedit = cdd->vc.scene->obedit;
+       Curve *cu = obedit->data;
+       ListBase *nurblist = object_editcurve_get(obedit);
+
+       int stroke_len = BLI_mempool_count(cdd->stroke_elem_pool);
+
+       const bool is_3d = (cu->flag & CU_3D) != 0;
+       invert_m4_m4(obedit->imat, obedit->obmat);
+
+       if (BLI_mempool_count(cdd->stroke_elem_pool) == 0) {
+               curve_draw_stroke_from_operator(op);
+               stroke_len = BLI_mempool_count(cdd->stroke_elem_pool);
+       }
+
+       ED_curve_deselect_all(cu->editnurb);
+
+       const double radius_min = cps->radius_min;
+       const double radius_max = cps->radius_max;
+       const double radius_range = cps->radius_max - cps->radius_min;
+
+       Nurb *nu = MEM_callocN(sizeof(Nurb), __func__);
+       nu->pntsv = 1;
+       nu->resolu = cu->resolu;
+       nu->resolv = cu->resolv;
+       nu->flag |= CU_SMOOTH;
+
+       const bool use_pressure_radius =
+               (cps->flag & CURVE_PAINT_FLAG_PRESSURE_RADIUS) ||
+               ((cps->radius_taper_start != 0.0f) ||
+                (cps->radius_taper_end   != 0.0f));
+
+       if (cdd->curve_type == CU_BEZIER) {
+               nu->type = CU_BEZIER;
+
+#ifdef USE_SPLINE_FIT
+
+               /* Allow to interpolate multiple channels */
+               int dims = 3;
+               struct {
+                       int radius;
+               } coords_indices;
+               coords_indices.radius = use_pressure_radius ? dims++ : -1;
+
+               float *coords = MEM_mallocN(sizeof(*coords) * stroke_len * dims, __func__);
+
+               float       *cubic_spline = NULL;
+               unsigned int cubic_spline_len = 0;
+
+               /* error in object local space */
+               const float error_threshold = RNA_float_get(op->ptr, "error_threshold");
+               const float corner_angle = RNA_float_get(op->ptr, "corner_angle");
+
+               {
+                       BLI_mempool_iter iter;
+                       const struct StrokeElem *selem;
+                       float *co = coords;
+
+                       BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+                       for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter), co += dims) {
+                               copy_v3_v3(co, selem->location_local);
+                               if (coords_indices.radius != -1) {
+                                       co[coords_indices.radius] = selem->pressure;
+                               }
+                       }
+               }
+
+               unsigned int *corners = NULL;
+               unsigned int  corners_len = 0;
+
+               if (corner_angle < M_PI) {
+                       /* this could be configurable... */
+                       const float corner_radius_min = error_threshold / 8;
+                       const float corner_radius_max = error_threshold * 2;
+                       const unsigned int samples_max = 16;
+
+                       curve_fit_corners_detect_fl(
+                               (const float *)coords, stroke_len, dims,
+                               corner_radius_min, corner_radius_max,
+                               samples_max, corner_angle,
+                               &corners, &corners_len);
+               }
+
+               unsigned int *corners_index = NULL;
+               unsigned int  corners_index_len = 0;
+
+               const int result = curve_fit_cubic_to_points_fl(
+                       coords, stroke_len, dims, error_threshold,
+                       corners, corners_len,
+                       &cubic_spline, &cubic_spline_len,
+                       NULL,
+                       &corners_index, &corners_index_len);
+
+               MEM_freeN(coords);
+               if (corners) {
+                       free(corners);
+               }
+
+               if (result == 0) {
+                       nu->pntsu = cubic_spline_len;
+                       nu->bezt = MEM_callocN(sizeof(BezTriple) * nu->pntsu, __func__);
+
+                       float *co = cubic_spline;
+                       BezTriple *bezt = nu->bezt;
+                       for (int j = 0; j < cubic_spline_len; j++, bezt++, co += (dims * 3)) {
+                               const float *handle_l = co + (dims * 0);
+                               const float *pt       = co + (dims * 1);
+                               const float *handle_r = co + (dims * 2);
+
+                               copy_v3_v3(bezt->vec[0], handle_l);
+                               copy_v3_v3(bezt->vec[1], pt);
+                               copy_v3_v3(bezt->vec[2], handle_r);
+
+                               if (coords_indices.radius != -1) {
+                                       bezt->radius = (pt[coords_indices.radius] * cdd->radius.range) + cdd->radius.min;
+                               }
+                               else {
+                                       bezt->radius = radius_max;
+                               }
+
+                               bezt->h1 = bezt->h2 = HD_ALIGN;  /* will set to free in second pass */
+                               bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
+                       }
+
+                       if (corners_index) {
+                               /* ignore the first and last */
+                               for (unsigned int i = 1; i < corners_index_len - 1; i++) {
+                                       bezt = &nu->bezt[corners_index[i]];
+                                       bezt->h1 = bezt->h2 = HD_FREE;
+                               }
+                       }
+               }
+
+               if (corners_index) {
+                       free(corners_index);
+               }
+
+               if (cubic_spline) {
+                       free(cubic_spline);
+               }
+
+#else
+               nu->pntsu = stroke_len;
+               nu->bezt = MEM_callocN(nu->pntsu * sizeof(BezTriple), __func__);
+
+               BezTriple *bezt = nu->bezt;
+
+               {
+                       BLI_mempool_iter iter;
+                       const struct StrokeElem *selem;
+
+                       BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+                       for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) {
+                               copy_v3_v3(bezt->vec[1], selem->location_local);
+                               if (!is_3d) {
+                                       bezt->vec[1][2] = 0.0f;
+                               }
+
+                               if (use_pressure_radius) {
+                                       bezt->radius = selem->pressure;
+                               }
+                               else {
+                                       bezt->radius = radius_max;
+                               }
+
+                               bezt->h1 = bezt->h2 = HD_AUTO;
+
+                               bezt->f1 |= SELECT;
+                               bezt->f2 |= SELECT;
+                               bezt->f3 |= SELECT;
+
+                               bezt++;
+                       }
+               }
+#endif
+
+               BKE_nurb_handles_calc(nu);
+       }
+       else {  /* CU_POLY */
+               BLI_mempool_iter iter;
+               const struct StrokeElem *selem;
+
+               nu->pntsu = stroke_len;
+               nu->type = CU_POLY;
+               nu->bp = MEM_callocN(nu->pntsu * sizeof(BPoint), __func__);
+
+               BPoint *bp = nu->bp;
+
+               BLI_mempool_iternew(cdd->stroke_elem_pool, &iter);
+               for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) {
+                       copy_v3_v3(bp->vec, selem->location_local);
+                       if (!is_3d) {
+                               bp->vec[2] = 0.0f;
+                       }
+
+                       if (use_pressure_radius) {
+                               bp->radius = (selem->pressure * radius_range) + radius_min;
+                       }
+                       else {
+                               bp->radius = cps->radius_max;
+                       }
+                       bp->f1 = SELECT;
+                       bp->vec[3] = 1.0f;
+
+                       bp++;
+               }
+
+               BKE_nurb_knot_calc_u(nu);
+       }
+
+       BLI_addtail(nurblist, nu);
+
+       BKE_curve_nurb_active_set(cu, nu);
+       cu->actvert = nu->pntsu - 1;
+
+       WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
+       DAG_id_tag_update(obedit->data, 0);
+
+       curve_draw_exit(op);
+
+       return OPERATOR_FINISHED;
+}
+
+static int curve_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       if (RNA_struct_property_is_set(op->ptr, "stroke")) {
+               return curve_draw_exec(C, op);
+       }
+
+       if (!curve_draw_init(C, op, true)) {
+               return OPERATOR_CANCELLED;
+       }
+
+       struct CurveDrawData *cdd = op->customdata;
+
+       const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings;
+
+       const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
+
+       /* fallback (incase we can't find the depth on first test) */
+       {
+               const float mval_fl[2] = {UNPACK2(event->mval)};
+               float center[3];
+               negate_v3_v3(center, cdd->vc.rv3d->ofs);
+               ED_view3d_win_to_3d(cdd->vc.ar, center, mval_fl, cdd->prev.location_world);
+               copy_v3_v3(cdd->prev.location_world_valid, cdd->prev.location_world);
+       }
+
+       cdd->draw_handle_view = ED_region_draw_cb_activate(
+               cdd->vc.ar->type, curve_draw_stroke_3d, op, REGION_DRAW_POST_VIEW);
+       WM_cursor_modal_set(cdd->vc.win, BC_PAINTBRUSHCURSOR);
+
+       {
+               View3D *v3d = cdd->vc.v3d;
+               RegionView3D *rv3d = cdd->vc.rv3d;
+               Object *obedit = cdd->vc.obedit;
+               Curve *cu = obedit->data;
+
+               const float *plane_no = NULL;
+               const float *plane_co = NULL;
+
+               if ((cu->flag & CU_3D) == 0) {
+                       /* 2D overrides other options */
+                       plane_co = obedit->obmat[3];
+                       plane_no = obedit->obmat[2];
+                       cdd->project.use_plane = true;
+               }
+               else {
+                       if ((cps->depth_mode == CURVE_PAINT_PROJECT_SURFACE) &&
+                           (v3d->drawtype > OB_WIRE))
+                       {
+                               view3d_get_transformation(cdd->vc.ar, cdd->vc.rv3d, NULL, &cdd->mats);
+
+                               /* needed or else the draw matrix can be incorrect */
+                               view3d_operator_needs_opengl(C);
+
+                               ED_view3d_autodist_init(cdd->vc.scene, cdd->vc.ar, cdd->vc.v3d, 0);
+
+                               if (cdd->vc.rv3d->depths) {
+                                       cdd->vc.rv3d->depths->damaged = true;
+                               }
+
+                               ED_view3d_depth_update(cdd->vc.ar);
+
+                               if (cdd->vc.rv3d->depths != NULL) {
+                                       cdd->project.use_depth = true;
+                               }
+                               else {
+                                       BKE_report(op->reports, RPT_WARNING, "Unable to access depth buffer, using view plane.");
+                                       cdd->project.use_depth = false;
+                               }
+                       }
+
+                       /* use view plane (when set or as fallback when surface can't be found) */
+                       if (cdd->project.use_depth == false) {
+                               plane_co = ED_view3d_cursor3d_get(cdd->vc.scene, v3d);;
+                               plane_no = rv3d->viewinv[2];
+                               cdd->project.use_plane = true;
+                       }
+
+                       if (cdd->project.use_depth && (cdd->curve_type != CU_POLY)) {
+                               cdd->sample.use_substeps = true;
+                       }
+               }
+
+               if (cdd->project.use_plane) {
+                       normalize_v3_v3(cdd->project.plane, plane_no);
+                       cdd->project.plane[3] = -dot_v3v3(cdd->project.plane, plane_co);
+               }
+       }
+
+       if (is_modal == false) {
+               curve_draw_event_add_first(op, event);
+       }
+
+       /* add temp handler */
+       WM_event_add_modal_handler(C, op);
+
+       return OPERATOR_RUNNING_MODAL;
+}
+
+static void curve_draw_cancel(bContext *UNUSED(C), wmOperator *op)
+{
+       curve_draw_exit(op);
+}
+
+
+/* Modal event handling of frame changing */
+static int curve_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       int ret = OPERATOR_RUNNING_MODAL;
+       struct CurveDrawData *cdd = op->customdata;
+
+       UNUSED_VARS(C, op);
+
+       if (event->type == cdd->init_event_type) {
+               if (event->val == KM_RELEASE) {
+                       ED_region_tag_redraw(cdd->vc.ar);
+
+                       curve_draw_exec_precalc(op);
+
+                       curve_draw_stroke_to_operator(op);
+
+                       curve_draw_exec(C, op);
+
+                       return OPERATOR_FINISHED;
+               }
+       }
+       else if (ELEM(event->type, ESCKEY, RIGHTMOUSE)) {
+               ED_region_tag_redraw(cdd->vc.ar);
+               curve_draw_cancel(C, op);
+               return OPERATOR_CANCELLED;
+       }
+       else if (ELEM(event->type, LEFTMOUSE)) {
+               if (event->val == KM_PRESS) {
+                       curve_draw_event_add_first(op, event);
+               }
+       }
+       else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
+               if (cdd->state == CURVE_DRAW_PAINTING) {
+                       const float mval_fl[2] = {UNPACK2(event->mval)};
+                       if (len_squared_v2v2(mval_fl, cdd->prev.location_world) > SQUARE(STROKE_SAMPLE_DIST_MIN_PX)) {
+                               curve_draw_event_add(op, event);
+                       }
+               }
+       }
+
+       return ret;
+}
+
+void CURVE_OT_draw(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Draw Curve";
+       ot->idname = "CURVE_OT_draw";
+       ot->description = "Draw a freehand spline";
+
+       /* api callbacks */
+       ot->exec = curve_draw_exec;
+       ot->invoke = curve_draw_invoke;
+       ot->cancel = curve_draw_cancel;
+       ot->modal = curve_draw_modal;
+       ot->poll = ED_operator_editcurve;
+
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+       /* properties */
+       PropertyRNA *prop;
+
+       prop = RNA_def_float_distance(
+               ot->srna, "error_threshold", 0.0f, 0.0f, 10.0f, "Error",
+               "Error distance threshold (in object units)",
+               0.0001f, 10.0f);
+       RNA_def_property_ui_range(prop, 0.0, 10, 1, 4);
+
+       prop = RNA_def_float_distance(
+               ot->srna, "corner_angle", DEG2RADF(70.0f), 0.0f, M_PI, "Corner Angle", "", 0.0f, M_PI);
+       RNA_def_property_subtype(prop, PROP_ANGLE);
+
+       prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+       RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+
+       prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "");
+       RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+}
+
+/** \} */
index a8f78f6bb343ff4d54441215dd1b2d04d89d7cfd..1ace2b42f15be9c050fdb4f772580adb0ecb482f 100644 (file)
@@ -1266,6 +1266,40 @@ typedef enum {
        UNIFIED_PAINT_BRUSH_ALPHA_PRESSURE  = (1 << 4)
 } UnifiedPaintSettingsFlags;
 
+
+typedef struct CurvePaintSettings {
+       char curve_type;
+       char flag;
+       char depth_mode;
+       char surface_plane;
+       int error_threshold;
+       float radius_min, radius_max;
+       float radius_taper_start, radius_taper_end;
+       float radius_offset;
+       float corner_angle;
+} CurvePaintSettings;
+
+/* CurvePaintSettings.flag */
+enum {
+       CURVE_PAINT_FLAG_CORNERS_DETECT             = (1 << 0),
+       CURVE_PAINT_FLAG_PRESSURE_RADIUS            = (1 << 1),
+       CURVE_PAINT_FLAG_DEPTH_STROKE_ENDPOINTS     = (1 << 2),
+};
+
+/* CurvePaintSettings.depth_mode */
+enum {
+       CURVE_PAINT_PROJECT_CURSOR              = 0,
+       CURVE_PAINT_PROJECT_SURFACE             = 1,
+};
+
+/* CurvePaintSettings.surface_plane */
+enum {
+       CURVE_PAINT_SURFACE_PLANE_NORMAL_VIEW           = 0,
+       CURVE_PAINT_SURFACE_PLANE_NORMAL_SURFACE        = 1,
+       CURVE_PAINT_SURFACE_PLANE_VIEW                  = 2,
+};
+
+
 /* *************************************************************** */
 /* Stats */
 
@@ -1417,6 +1451,8 @@ typedef struct ToolSettings {
        /* Unified Paint Settings */
        struct UnifiedPaintSettings unified_paint_settings;
 
+       struct CurvePaintSettings curve_paint_settings;
+
        struct MeshStatVis statvis;
 } ToolSettings;
 
index af63f6753981c8cf5bd37f9d36326aa9406ef9b4..71d1df91c5d191c4b1c58fcc382e4b1a351dfc4e 100644 (file)
@@ -1767,6 +1767,11 @@ static char *rna_UnifiedPaintSettings_path(PointerRNA *UNUSED(ptr))
        return BLI_strdup("tool_settings.unified_paint_settings");
 }
 
+static char *rna_CurvePaintSettings_path(PointerRNA *UNUSED(ptr))
+{
+       return BLI_strdup("tool_settings.curve_paint_settings");
+}
+
 /* generic function to recalc geometry */
 static void rna_EditMesh_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr))
 {
@@ -2497,6 +2502,12 @@ static void rna_def_tool_settings(BlenderRNA  *brna)
        RNA_def_property_struct_type(prop, "UnifiedPaintSettings");
        RNA_def_property_ui_text(prop, "Unified Paint Settings", NULL);
 
+       /* Curve Paint Settings */
+       prop = RNA_def_property(srna, "curve_paint_settings", PROP_POINTER, PROP_NONE);
+       RNA_def_property_flag(prop, PROP_NEVER_NULL);
+       RNA_def_property_struct_type(prop, "CurvePaintSettings");
+       RNA_def_property_ui_text(prop, "Curve Paint Settings", NULL);
+
        /* Mesh Statistics */
        prop = RNA_def_property(srna, "statvis", PROP_POINTER, PROP_NONE);
        RNA_def_property_flag(prop, PROP_NEVER_NULL);
@@ -2595,6 +2606,96 @@ static void rna_def_unified_paint_settings(BlenderRNA  *brna)
                                 "when unlocked brush size is given in pixels");
 }
 
+
+static void rna_def_curve_paint_settings(BlenderRNA  *brna)
+{
+       StructRNA *srna;
+       PropertyRNA *prop;
+
+       srna = RNA_def_struct(brna, "CurvePaintSettings", NULL);
+       RNA_def_struct_path_func(srna, "rna_CurvePaintSettings_path");
+       RNA_def_struct_ui_text(srna, "Curve Paint Settings", "");
+
+       static EnumPropertyItem curve_type_items[] = {
+               {CU_POLY, "POLY", 0, "Poly", ""},
+               {CU_BEZIER, "BEZIER", 0, "Bezier", ""},
+               {0, NULL, 0, NULL, NULL}};
+
+       prop = RNA_def_property(srna, "curve_type", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_sdna(prop, NULL, "curve_type");
+       RNA_def_property_enum_items(prop, curve_type_items);
+       RNA_def_property_ui_text(prop, "Type", "Type of curve to use for new strokes");
+
+       prop = RNA_def_property(srna, "use_corners_detect", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", CURVE_PAINT_FLAG_CORNERS_DETECT);
+       RNA_def_property_ui_text(prop, "Detect Corners", "Detect corners and use non-aligned handles");
+
+       prop = RNA_def_property(srna, "use_pressure_radius", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", CURVE_PAINT_FLAG_PRESSURE_RADIUS);
+       RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0);
+       RNA_def_property_ui_text(prop, "Use Pressure", "Map tablet pressure to curve radius");
+
+       prop = RNA_def_property(srna, "use_stroke_endpoints", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", CURVE_PAINT_FLAG_DEPTH_STROKE_ENDPOINTS);
+       RNA_def_property_ui_text(prop, "Only First", "Use the start of the stroke for the depth");
+
+       prop = RNA_def_property(srna, "error_threshold", PROP_INT, PROP_PIXEL);
+       RNA_def_property_range(prop, 1, 100);
+       RNA_def_property_ui_text(prop, "Tolerance", "Allow deviation for a smoother, less preceise line");
+
+       prop = RNA_def_property(srna, "corner_angle", PROP_FLOAT, PROP_ANGLE);
+       RNA_def_property_range(prop, 0, M_PI);
+       RNA_def_property_ui_text(prop, "Corner Angle", "Angles above this are considered corners");
+
+       prop = RNA_def_property(srna, "radius_min", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_range(prop, 0.0, 100.0);
+       RNA_def_property_ui_range(prop, 0.0f, 10.0, 10, 2);
+       RNA_def_property_ui_text(prop, "Radius Min",
+                                "Minimum radius when the minimum pressure is applied (also the minimum when tapering)");
+
+       prop = RNA_def_property(srna, "radius_max", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_range(prop, 0.0, 100.0);
+       RNA_def_property_ui_range(prop, 0.0f, 10.0, 10, 2);
+       RNA_def_property_ui_text(prop, "Radius Max",
+                                "Radius to use when the maximum pressure is applied (or when a tablet isn't used)");
+
+       prop = RNA_def_property(srna, "radius_taper_start", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_range(prop, 0.0, 1.0);
+       RNA_def_property_ui_range(prop, 0.0f, 1.0, 1, 2);
+       RNA_def_property_ui_text(prop, "Radius Min", "Taper factor for the radius of each point along the curve");
+
+       prop = RNA_def_property(srna, "radius_taper_end", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_range(prop, 0.0, 10.0);
+       RNA_def_property_ui_range(prop, 0.0f, 1.0, 1, 2);
+       RNA_def_property_ui_text(prop, "Radius Max", "Taper factor for the radius of each point along the curve");
+
+       prop = RNA_def_property(srna, "radius_offset", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_range(prop, -10.0, 10.0);
+       RNA_def_property_ui_range(prop, -1.0f, 1.0, 1, 2);
+       RNA_def_property_ui_text(prop, "Offset", "Offset the stroke from the surface");
+
+       static EnumPropertyItem depth_mode_items[] = {
+               {CURVE_PAINT_PROJECT_CURSOR,  "CURSOR",  0, "Cursor",  ""},
+               {CURVE_PAINT_PROJECT_SURFACE, "SURFACE", 0, "Surface", ""},
+               {0, NULL, 0, NULL, NULL}};
+
+       prop = RNA_def_property(srna, "depth_mode", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_sdna(prop, NULL, "depth_mode");
+       RNA_def_property_enum_items(prop, depth_mode_items);
+       RNA_def_property_ui_text(prop, "Depth", "Method of projecting depth");
+
+       static EnumPropertyItem surface_plane_items[] = {
+               {CURVE_PAINT_SURFACE_PLANE_NORMAL_VIEW,  "NORMAL_VIEW", 0, "Normal/View", "Draw perpendicular to the surface"},
+               {CURVE_PAINT_SURFACE_PLANE_NORMAL_SURFACE, "NORMAL_SURFACE", 0, "Normal/Surface", "Draw aligned to the surface"},
+               {CURVE_PAINT_SURFACE_PLANE_VIEW, "VIEW", 0, "View", "Draw aligned to the viewport"},
+               {0, NULL, 0, NULL, NULL}};
+
+       prop = RNA_def_property(srna, "surface_plane", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_sdna(prop, NULL, "surface_plane");
+       RNA_def_property_enum_items(prop, surface_plane_items);
+       RNA_def_property_ui_text(prop, "Plane", "Plane for projected stroke");
+}
+
 static void rna_def_statvis(BlenderRNA  *brna)
 {
        StructRNA *srna;
@@ -6720,6 +6821,7 @@ void RNA_def_scene(BlenderRNA *brna)
        RNA_define_animate_sdna(false);
        rna_def_tool_settings(brna);
        rna_def_unified_paint_settings(brna);
+       rna_def_curve_paint_settings(brna);
        rna_def_statvis(brna);
        rna_def_unit_settings(brna);
        rna_def_scene_image_format_data(brna);