diff options
-rw-r--r-- | extern/curve_fit_nd/curve_fit_nd.h | 4 | ||||
-rw-r--r-- | extern/curve_fit_nd/intern/curve_fit_cubic.c | 8 | ||||
-rw-r--r-- | release/scripts/startup/bl_ui/space_view3d_toolbar.py | 60 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/scene.c | 6 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_270.c | 12 | ||||
-rw-r--r-- | source/blender/editors/curve/CMakeLists.txt | 8 | ||||
-rw-r--r-- | source/blender/editors/curve/curve_intern.h | 3 | ||||
-rw-r--r-- | source/blender/editors/curve/curve_ops.c | 4 | ||||
-rw-r--r-- | source/blender/editors/curve/editcurve_paint.c | 1150 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_scene_types.h | 36 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_scene.c | 102 |
11 files changed, 1385 insertions, 8 deletions
diff --git a/extern/curve_fit_nd/curve_fit_nd.h b/extern/curve_fit_nd/curve_fit_nd.h index 67b0ed75b8e..d20921c186a 100644 --- a/extern/curve_fit_nd/curve_fit_nd.h +++ b/extern/curve_fit_nd/curve_fit_nd.h @@ -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, diff --git a/extern/curve_fit_nd/intern/curve_fit_cubic.c b/extern/curve_fit_nd/intern/curve_fit_cubic.c index d623b51ed0b..810cf92760d 100644 --- a/extern/curve_fit_nd/intern/curve_fit_cubic.c +++ b/extern/curve_fit_nd/intern/curve_fit_cubic.c @@ -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, diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index d8286c64f39..01b46eb6aed 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -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" diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 6b297a143d6..c0b79118467 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -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); diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c index b9191d545ed..18740d43a26 100644 --- a/source/blender/blenloader/intern/versioning_270.c +++ b/source/blender/blenloader/intern/versioning_270.c @@ -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); + } + } + } } diff --git a/source/blender/editors/curve/CMakeLists.txt b/source/blender/editors/curve/CMakeLists.txt index 83346e9550c..ebdf6bb43ff 100644 --- a/source/blender/editors/curve/CMakeLists.txt +++ b/source/blender/editors/curve/CMakeLists.txt @@ -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}") diff --git a/source/blender/editors/curve/curve_intern.h b/source/blender/editors/curve/curve_intern.h index 1af0b49bd86..d63616e4f43 100644 --- a/source/blender/editors/curve/curve_intern.h +++ b/source/blender/editors/curve/curve_intern.h @@ -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__ */ diff --git a/source/blender/editors/curve/curve_ops.c b/source/blender/editors/curve/curve_ops.c index 967187e66c9..d1994c8fc15 100644 --- a/source/blender/editors/curve/curve_ops.c +++ b/source/blender/editors/curve/curve_ops.c @@ -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 index 00000000000..4af123c0f51 --- /dev/null +++ b/source/blender/editors/curve/editcurve_paint.c @@ -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); +} + +/** \} */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index a8f78f6bb34..1ace2b42f15 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -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; diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index af63f675398..71d1df91c5d 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -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); |