diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 72b3e166cc1..8c386af57be 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5709,6 +5709,15 @@ def km_3d_view_tool_paint_gpencil_arc(params): ]}, ) +def km_3d_view_tool_paint_gpencil_curve(params): + return ( + "3D View Tool: Paint Gpencil, Curve", + {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, + {"items": [ + ("gpencil.primitive", {"type": params.tool_tweak, "value": 'ANY'}, + {"properties": [("type", 'CURVE'), ("wait_for_input", False)]}), + ]}, + ) def km_3d_view_tool_edit_gpencil_select(params): return ( @@ -6019,6 +6028,7 @@ def generate_keymaps(params=None): km_3d_view_tool_paint_gpencil_box(params), km_3d_view_tool_paint_gpencil_circle(params), km_3d_view_tool_paint_gpencil_arc(params), + km_3d_view_tool_paint_gpencil_curve(params), km_3d_view_tool_edit_gpencil_select(params), km_3d_view_tool_edit_gpencil_select_box(params), km_3d_view_tool_edit_gpencil_select_circle(params), diff --git a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py index 09e81115d6a..32c0aa6ab0b 100644 --- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -645,6 +645,7 @@ class GPENCIL_MT_gpencil_draw_specials(Menu): layout.operator("gpencil.primitive", text="Rectangle", icon='UV_FACESEL').type = 'BOX' layout.operator("gpencil.primitive", text="Circle", icon='ANTIALIASED').type = 'CIRCLE' layout.operator("gpencil.primitive", text="Arc", icon='SPHERECURVE').type = 'ARC' + layout.operator("gpencil.primitive", text="Curve", icon='CURVE_BEZCURVE').type = 'CURVE' class GPENCIL_MT_gpencil_draw_delete(Menu): diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 4da5455298f..aa6adee0d19 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -1076,6 +1076,17 @@ class _defs_gpencil_paint: widget=None, keymap=(), ) + + + @ToolDef.from_fn + def curve(): + return dict( + text="Curve", + icon="ops.gpencil.primitive_curve", + cursor='CROSSHAIR', + widget=None, + keymap=(), + ) class _defs_gpencil_edit: @ToolDef.from_fn @@ -1583,6 +1594,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_gpencil_paint.box, _defs_gpencil_paint.circle, _defs_gpencil_paint.arc, + _defs_gpencil_paint.curve, ], 'EDIT_GPENCIL': [ *_tools_gpencil_select, diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index f73d21dcfb8..a94ebea4561 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -301,7 +301,7 @@ class _draw_left_context_mode: return is_paint = True - if (tool.name in {"Line", "Box", "Circle", "Arc"}): + if (tool.name in {"Line", "Box", "Circle", "Arc", "Curve"}): is_paint = False elif (not tool.has_datablock): return @@ -375,6 +375,17 @@ class _draw_left_context_mode: draw_color_selector() + if tool.name in {"Arc", "Curve", "Line", "Box", "Circle"}: + settings = context.tool_settings.gpencil_sculpt + row = layout.row(align=True) + row.prop(settings, "use_thickness_curve", text="", icon='CURVE_DATA') + sub = row.row(align=True) + sub.active = settings.use_thickness_curve + sub.popover( + panel="TOPBAR_PT_gpencil_primitive", + text="Thickness Profile" + ) + @staticmethod def SCULPT_GPENCIL(context, layout, tool): if (tool is None) or (not tool.has_datablock): @@ -1039,6 +1050,21 @@ class TOPBAR_PT_active_tool(Panel): ToolSelectPanelHelper.draw_active_tool_header(context, layout, show_tool_name=True) +# Grease Pencil Object - Primitive curve +class TOPBAR_PT_gpencil_primitive(Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'HEADER' + bl_label = "Primitives" + + @staticmethod + def draw(self, context): + settings = context.tool_settings.gpencil_sculpt + + layout = self.layout + # Curve + layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True) + + classes = ( TOPBAR_HT_upper_bar, TOPBAR_HT_lower_bar, @@ -1059,6 +1085,7 @@ classes = ( TOPBAR_MT_help, TOPBAR_PT_active_tool, TOPBAR_PT_gpencil_layers, + TOPBAR_PT_gpencil_primitive, ) if __name__ == "__main__": # only for live edit. diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c index aad59e32187..bc34b24c8f2 100644 --- a/source/blender/blenkernel/intern/colortools.c +++ b/source/blender/blenkernel/intern/colortools.c @@ -283,6 +283,7 @@ void curvemap_reset(CurveMap *cuma, const rctf *clipr, int preset, int slope) case CURVE_PRESET_ROUND: cuma->totpoint = 4; break; case CURVE_PRESET_ROOT: cuma->totpoint = 4; break; case CURVE_PRESET_GAUSS: cuma->totpoint = 7; break; + case CURVE_PRESET_BELL: cuma->totpoint = 3; break; } cuma->curve = MEM_callocN(cuma->totpoint * sizeof(CurveMapPoint), "curve points"); @@ -371,6 +372,16 @@ void curvemap_reset(CurveMap *cuma, const rctf *clipr, int preset, int slope) cuma->curve[6].x = 1.0f; cuma->curve[6].y = 0.025f; break; + case CURVE_PRESET_BELL: + cuma->curve[0].x = 0; + cuma->curve[0].y = 0.025f; + + cuma->curve[1].x = 0.50f; + cuma->curve[1].y = 1.0f; + + cuma->curve[2].x = 1.0f; + cuma->curve[2].y = 0.025f; + break; } /* mirror curve in x direction to have positive slope diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index a070bf97317..3894d83c169 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -188,6 +188,7 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag) ts->gp_interpolate.custom_ipo = curvemapping_copy(ts->gp_interpolate.custom_ipo); /* duplicate Grease Pencil multiframe fallof */ ts->gp_sculpt.cur_falloff = curvemapping_copy(ts->gp_sculpt.cur_falloff); + ts->gp_sculpt.cur_primitive = curvemapping_copy(ts->gp_sculpt.cur_primitive); return ts; } @@ -226,6 +227,9 @@ void BKE_toolsettings_free(ToolSettings *toolsettings) if (toolsettings->gp_sculpt.cur_falloff) { curvemapping_free(toolsettings->gp_sculpt.cur_falloff); } + if (toolsettings->gp_sculpt.cur_primitive) { + curvemapping_free(toolsettings->gp_sculpt.cur_primitive); + } MEM_freeN(toolsettings); } @@ -699,6 +703,14 @@ void BKE_scene_init(Scene *sce) CURVE_PRESET_GAUSS, CURVEMAP_SLOPE_POSITIVE); + sce->toolsettings->gp_sculpt.cur_primitive = curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + CurveMapping *gp_primitive_curve = sce->toolsettings->gp_sculpt.cur_primitive; + curvemapping_initialize(gp_primitive_curve); + curvemap_reset(gp_primitive_curve->cm, + &gp_primitive_curve->clipr, + CURVE_PRESET_BELL, + CURVEMAP_SLOPE_POSITIVE); + sce->physics_settings.gravity[0] = 0.0f; sce->physics_settings.gravity[1] = 0.0f; sce->physics_settings.gravity[2] = -9.81f; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 6aff6f6506f..6dd9c80b5d9 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -6275,6 +6275,11 @@ static void direct_link_scene(FileData *fd, Scene *sce) if (sce->toolsettings->gp_sculpt.cur_falloff) { direct_link_curvemapping(fd, sce->toolsettings->gp_sculpt.cur_falloff); } + /* relink grease pencil primitive curve */ + sce->toolsettings->gp_sculpt.cur_primitive = newdataadr(fd, sce->toolsettings->gp_sculpt.cur_primitive); + if (sce->toolsettings->gp_sculpt.cur_primitive) { + direct_link_curvemapping(fd, sce->toolsettings->gp_sculpt.cur_primitive); + } } if (sce->ed) { diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 56ca663e3f0..5a1002702b3 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -2516,6 +2516,21 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + + /* Grease pencil primitive curve */ + if (!DNA_struct_elem_find(fd->filesdna, "GP_Sculpt_Settings", "CurveMapping", "cur_primitive")) { + for (Scene *scene = bmain->scene.first; scene; scene = scene->id.next) { + GP_Sculpt_Settings *gset = &scene->toolsettings->gp_sculpt; + if ((gset) && (gset->cur_primitive == NULL)) { + gset->cur_primitive = curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + curvemapping_initialize(gset->cur_primitive); + curvemap_reset(gset->cur_primitive->cm, + &gset->cur_primitive->clipr, + CURVE_PRESET_BELL, + CURVEMAP_SLOPE_POSITIVE); + } + } + } } { diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 4dd046d9a73..f3ae1426fa8 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -187,7 +187,7 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) } } - /* Be sure curfalloff is initializated */ + /* Be sure curfalloff and primitive are initializated */ for (Scene *scene = bmain->scene.first; scene; scene = scene->id.next) { ToolSettings *ts = scene->toolsettings; if (ts->gp_sculpt.cur_falloff == NULL) { @@ -199,6 +199,15 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) CURVE_PRESET_GAUSS, CURVEMAP_SLOPE_POSITIVE); } + if (ts->gp_sculpt.cur_primitive == NULL) { + ts->gp_sculpt.cur_primitive = curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + CurveMapping *gp_primitive_curve = ts->gp_sculpt.cur_primitive; + curvemapping_initialize(gp_primitive_curve); + curvemap_reset(gp_primitive_curve->cm, + &gp_primitive_curve->clipr, + CURVE_PRESET_BELL, + CURVEMAP_SLOPE_POSITIVE); + } } } diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index 0854af0f818..c1d09a836cc 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -2555,6 +2555,10 @@ static void write_scene(WriteData *wd, Scene *sce) if (tos->gp_sculpt.cur_falloff) { write_curvemapping(wd, tos->gp_sculpt.cur_falloff); } + /* write grease-pencil primitive curve to file */ + if (tos->gp_sculpt.cur_primitive) { + write_curvemapping(wd, tos->gp_sculpt.cur_primitive); + } write_paint(wd, &tos->imapaint.paint); diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c b/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c index c75445f3ae5..a098fb74bc8 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c @@ -252,6 +252,9 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness) tGPspoint *points = gpd->runtime.sbuffer; int totpoints = gpd->runtime.sbuffer_size; + /* if cyclic needs more vertex */ + int cyclic_add = (gpd->runtime.sbuffer_sflag & GP_STROKE_CYCLIC) ? 1 : 0; + int totvertex = totpoints + cyclic_add + 2; static GPUVertFormat format = { 0 }; static uint pos_id, color_id, thickness_id, uvdata_id; @@ -263,11 +266,11 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness) } GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); - GPU_vertbuf_data_alloc(vbo, totpoints + 2); + GPU_vertbuf_data_alloc(vbo, totvertex); /* draw stroke curve */ const tGPspoint *tpt = points; - bGPDspoint pt, pt2; + bGPDspoint pt, pt2, pt3; int idx = 0; /* get origin to reproject point */ @@ -281,19 +284,22 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness) /* first point for adjacency (not drawn) */ if (i == 0) { - if (totpoints > 1) { - ED_gpencil_tpoint_to_point(ar, origin, &points[1], &pt2); + if (gpd->runtime.sbuffer_sflag & GP_STROKE_CYCLIC && totpoints > 2) { + ED_gpencil_tpoint_to_point(ar, origin, &points[totpoints - 1], &pt2); gpencil_set_stroke_point( - vbo, &pt2, idx, - pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + vbo, &pt2, idx, + pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + idx++; } else { + ED_gpencil_tpoint_to_point(ar, origin, &points[1], &pt2); gpencil_set_stroke_point( - vbo, &pt, idx, - pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + vbo, &pt2, idx, + pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + idx++; } - idx++; } + /* set point */ gpencil_set_stroke_point( vbo, &pt, idx, @@ -302,16 +308,27 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness) } /* last adjacency point (not drawn) */ - if (totpoints > 2) { + if (gpd->runtime.sbuffer_sflag & GP_STROKE_CYCLIC && totpoints > 2) { + /* draw line to first point to complete the cycle */ + ED_gpencil_tpoint_to_point(ar, origin, &points[0], &pt2); + gpencil_set_stroke_point( + vbo, &pt2, idx, + pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + idx++; + /* now add adjacency point (not drawn) */ + ED_gpencil_tpoint_to_point(ar, origin, &points[1], &pt3); + gpencil_set_stroke_point( + vbo, &pt3, idx, + pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + idx++; + } + /* last adjacency point (not drawn) */ + else { ED_gpencil_tpoint_to_point(ar, origin, &points[totpoints - 2], &pt2); gpencil_set_stroke_point( - vbo, &pt2, idx, - pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); - } - else { - gpencil_set_stroke_point( - vbo, &pt, idx, - pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + vbo, &pt2, idx, + pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); + idx++; } return GPU_batch_create_ex(GPU_PRIM_LINE_STRIP_ADJ, vbo, NULL, GPU_BATCH_OWNS_VBO); @@ -367,6 +384,42 @@ GPUBatch *DRW_gpencil_get_buffer_point_geom(bGPdata *gpd, short thickness) return GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); } +/* create batch geometry data for current buffer control point shader */ +GPUBatch *DRW_gpencil_get_buffer_ctrlpoint_geom(bGPdata *gpd) +{ + bGPDcontrolpoint *cps = gpd->runtime.cp_points; + int totpoints = gpd->runtime.tot_cp_points; + + static GPUVertFormat format = { 0 }; + static uint pos_id, color_id, size_id; + if (format.attr_len == 0) { + pos_id = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + size_id = GPU_vertformat_attr_add(&format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + color_id = GPU_vertformat_attr_add(&format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + } + + GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(vbo, totpoints); + + int idx = 0; + for (int i = 0; i < gpd->runtime.tot_cp_points; i++) { + bGPDcontrolpoint *cp = &cps[i]; + float color[4]; + copy_v3_v3(color, cp->color); + color[3] = 0.5f; + GPU_vertbuf_attr_set(vbo, color_id, idx, color); + + /* scale size */ + float size = cp->size * 0.8f; + GPU_vertbuf_attr_set(vbo, size_id, idx, &size); + + GPU_vertbuf_attr_set(vbo, pos_id, idx, &cp->x); + idx++; + } + + return GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); +} + /* create batch geometry data for current buffer fill shader */ GPUBatch *DRW_gpencil_get_buffer_fill_geom(bGPdata *gpd) { diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c index 87772a72015..3b1c51cb8eb 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c @@ -434,7 +434,7 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create( DRW_shgroup_uniform_int(grp, "xraymode", (const int *) &gpd->xray_mode, 1); } else { - /* for drawing always on front */ + /* for drawing always on predefined z-depth */ DRW_shgroup_uniform_int(grp, "xraymode", &stl->storage->xray, 1); } @@ -527,7 +527,7 @@ static DRWShadingGroup *DRW_gpencil_shgroup_point_create( DRW_shgroup_uniform_int(grp, "xraymode", (const int *)&gpd->xray_mode, 1); } else { - /* for drawing always on front */ + /* for drawing always on on predefined z-depth */ DRW_shgroup_uniform_int(grp, "xraymode", &stl->storage->xray, 1); } @@ -1161,6 +1161,9 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T { GPENCIL_PassList *psl = ((GPENCIL_Data *)vedata)->psl; GPENCIL_StorageList *stl = ((GPENCIL_Data *)vedata)->stl; + const DRWContextState *draw_ctx = DRW_context_state_get(); + View3D *v3d = draw_ctx->v3d; + const bool overlay = v3d != NULL ? (bool)((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) : true; Brush *brush = BKE_paint_brush(&ts->gp_paint->paint); bGPdata *gpd_eval = ob->data; /* need the original to avoid cow overhead while drawing */ @@ -1184,7 +1187,7 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T /* Check if may need to draw the active stroke cache, only if this layer is the active layer * that is being edited. (Stroke buffer is currently stored in gp-data) */ - if (ED_gpencil_session_active() && (gpd->runtime.sbuffer_size > 0)) { + if (gpd->runtime.sbuffer_size > 0) { if ((gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) { /* It should also be noted that sbuffer contains temporary point types * i.e. tGPspoints NOT bGPDspoints @@ -1194,11 +1197,11 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T if (gpd->runtime.sbuffer_size > 1) { if ((gp_style) && (gp_style->mode == GP_STYLE_MODE_LINE)) { stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_stroke_create( - e_data, vedata, psl->drawing_pass, e_data->gpencil_stroke_sh, NULL, gpd, gp_style, -1, false); + e_data, vedata, psl->drawing_pass, e_data->gpencil_stroke_sh, NULL, gpd, gp_style, -1, false); } else { stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_point_create( - e_data, vedata, psl->drawing_pass, e_data->gpencil_point_sh, NULL, gpd, gp_style, -1, false); + e_data, vedata, psl->drawing_pass, e_data->gpencil_point_sh, NULL, gpd, gp_style, -1, false); } /* clean previous version of the batch */ @@ -1211,32 +1214,32 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T /* use unit matrix because the buffer is in screen space and does not need conversion */ if (gpd->runtime.mode == GP_STYLE_MODE_LINE) { e_data->batch_buffer_stroke = DRW_gpencil_get_buffer_stroke_geom( - gpd, lthick); + gpd, lthick); } else { e_data->batch_buffer_stroke = DRW_gpencil_get_buffer_point_geom( - gpd, lthick); + gpd, lthick); } if (gp_style->flag & GP_STYLE_STROKE_SHOW) { DRW_shgroup_call_add( - stl->g_data->shgrps_drawing_stroke, - e_data->batch_buffer_stroke, - stl->storage->unit_matrix); + stl->g_data->shgrps_drawing_stroke, + e_data->batch_buffer_stroke, + stl->storage->unit_matrix); } if ((gpd->runtime.sbuffer_size >= 3) && - (gpd->runtime.sfill[3] > GPENCIL_ALPHA_OPACITY_THRESH) && - ((gpd->runtime.sbuffer_sflag & GP_STROKE_NOFILL) == 0) && - ((brush->gpencil_settings->flag & GP_BRUSH_DISSABLE_LASSO) == 0) && - (gp_style->flag & GP_STYLE_FILL_SHOW)) + (gpd->runtime.sfill[3] > GPENCIL_ALPHA_OPACITY_THRESH) && + ((gpd->runtime.sbuffer_sflag & GP_STROKE_NOFILL) == 0) && + ((brush->gpencil_settings->flag & GP_BRUSH_DISSABLE_LASSO) == 0) && + (gp_style->flag & GP_STYLE_FILL_SHOW)) { /* if not solid, fill is simulated with solid color */ if (gpd->runtime.bfill_style > 0) { gpd->runtime.sfill[3] = 0.5f; } stl->g_data->shgrps_drawing_fill = DRW_shgroup_create( - e_data->gpencil_drawing_fill_sh, psl->drawing_pass); + e_data->gpencil_drawing_fill_sh, psl->drawing_pass); /* clean previous version of the batch */ if (stl->storage->buffer_fill) { @@ -1247,15 +1250,44 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T e_data->batch_buffer_fill = DRW_gpencil_get_buffer_fill_geom(gpd); DRW_shgroup_call_add( - stl->g_data->shgrps_drawing_fill, - e_data->batch_buffer_fill, - stl->storage->unit_matrix); + stl->g_data->shgrps_drawing_fill, + e_data->batch_buffer_fill, + stl->storage->unit_matrix); stl->storage->buffer_fill = true; } stl->storage->buffer_stroke = true; } } } + + /* control points */ + if ((overlay) && (gpd->runtime.tot_cp_points > 0) && + ((gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) && + ((v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0) && + ((v3d->gizmo_flag & V3D_GIZMO_HIDE_TOOL) == 0)) + { + + DRWShadingGroup *shgrp = DRW_shgroup_create( + e_data->gpencil_edit_point_sh, psl->drawing_pass); + const float *viewport_size = DRW_viewport_size_get(); + DRW_shgroup_uniform_vec2(shgrp, "Viewport", viewport_size, 1); + + /* clean previous version of the batch */ + if (stl->storage->buffer_ctrlpoint) { + GPU_BATCH_DISCARD_SAFE(e_data->batch_buffer_ctrlpoint); + MEM_SAFE_FREE(e_data->batch_buffer_ctrlpoint); + stl->storage->buffer_ctrlpoint = false; + } + + e_data->batch_buffer_ctrlpoint = DRW_gpencil_get_buffer_ctrlpoint_geom(gpd); + + DRW_shgroup_call_add( + shgrp, + e_data->batch_buffer_ctrlpoint, + stl->storage->unit_matrix); + + stl->storage->buffer_ctrlpoint = true; + } } /* create all missing batches */ diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.c b/source/blender/draw/engines/gpencil/gpencil_engine.c index 66d9bac1fac..dc62ca34c92 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.c +++ b/source/blender/draw/engines/gpencil/gpencil_engine.c @@ -289,6 +289,9 @@ static void GPENCIL_engine_free(void) GPU_BATCH_DISCARD_SAFE(e_data.batch_buffer_fill); MEM_SAFE_FREE(e_data.batch_buffer_fill); + GPU_BATCH_DISCARD_SAFE(e_data.batch_buffer_ctrlpoint); + MEM_SAFE_FREE(e_data.batch_buffer_ctrlpoint); + GPU_BATCH_DISCARD_SAFE(e_data.batch_grid); MEM_SAFE_FREE(e_data.batch_grid); diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 3c0675c5e8c..6f2b40136ca 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -133,6 +133,7 @@ typedef struct GPENCIL_Storage { bool reset_cache; bool buffer_stroke; bool buffer_fill; + bool buffer_ctrlpoint; const float *pixsize; float render_pixsize; int tonemapping; @@ -299,6 +300,7 @@ typedef struct GPENCIL_e_data { /* for buffer only one batch is nedeed because the drawing is only of one stroke */ GPUBatch *batch_buffer_stroke; GPUBatch *batch_buffer_fill; + GPUBatch *batch_buffer_ctrlpoint; /* grid geometry */ GPUBatch *batch_grid; @@ -386,6 +388,7 @@ void DRW_gpencil_get_edlin_geom(struct GpencilBatchCacheElem *be, struct bGPDstr struct GPUBatch *DRW_gpencil_get_buffer_stroke_geom(struct bGPdata *gpd, short thickness); struct GPUBatch *DRW_gpencil_get_buffer_fill_geom(struct bGPdata *gpd); struct GPUBatch *DRW_gpencil_get_buffer_point_geom(struct bGPdata *gpd, short thickness); +struct GPUBatch *DRW_gpencil_get_buffer_ctrlpoint_geom(struct bGPdata *gpd); struct GPUBatch *DRW_gpencil_get_grid(Object *ob); /* object cache functions */ diff --git a/source/blender/editors/gpencil/drawgpencil.c b/source/blender/editors/gpencil/drawgpencil.c index b44c9105e10..c16ea84ec81 100644 --- a/source/blender/editors/gpencil/drawgpencil.c +++ b/source/blender/editors/gpencil/drawgpencil.c @@ -1430,63 +1430,6 @@ void ED_gp_draw_interpolation(const bContext *C, tGPDinterpolate *tgpi, const in glDisable(GL_BLEND); } -/* draw interpolate strokes (used only while operator is running) */ -void ED_gp_draw_primitives(const bContext *C, tGPDprimitive *tgpi, const int type) -{ - tGPDdraw tgpw; - ARegion *ar = CTX_wm_region(C); - RegionView3D *rv3d = ar->regiondata; - - /* if idle, do not draw */ - if (tgpi->flag == 0) { - return; - } - - Object *obact = CTX_data_active_object(C); - Depsgraph *depsgraph = CTX_data_depsgraph(C); - - float color[4]; - UI_GetThemeColor3fv(TH_GP_VERTEX_SELECT, color); - color[3] = 0.6f; - int dflag = 0; - /* if 3d stuff, enable flags */ - if (type == REGION_DRAW_POST_VIEW) { - dflag |= (GP_DRAWDATA_ONLY3D | GP_DRAWDATA_NOSTATUS); - } - - tgpw.rv3d = rv3d; - tgpw.depsgraph = depsgraph; - tgpw.ob = obact; - tgpw.gpd = tgpi->gpd; - tgpw.offsx = 0; - tgpw.offsy = 0; - tgpw.winx = tgpi->ar->winx; - tgpw.winy = tgpi->ar->winy; - tgpw.dflag = dflag; - - /* turn on alpha-blending */ - GPU_blend(true); - /* calculate parent position */ - ED_gpencil_parent_location(depsgraph, obact, tgpi->gpd, tgpi->gpl, tgpw.diff_mat); - if (tgpi->gpf) { - tgpw.gps = tgpi->gpf->strokes.first; - if (tgpw.gps->totpoints > 0) { - tgpw.gpl = tgpi->gpl; - tgpw.gpf = tgpi->gpf; - tgpw.t_gpf = tgpi->gpf; - - tgpw.lthick = tgpi->gpl->line_change; - tgpw.opacity = 1.0; - copy_v4_v4(tgpw.tintcolor, color); - tgpw.onion = true; - tgpw.custonion = true; - - gp_draw_strokes(&tgpw); - } - } - GPU_blend(false); -} - /* wrapper to draw strokes for filling operator */ void ED_gp_draw_fill(tGPDdraw *tgpw) { diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 2a92185cdd5..e2cbeda2733 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -138,6 +138,7 @@ typedef struct tGPDinterpolate { /* Temporary primitive operation data */ typedef struct tGPDprimitive { + struct Main *bmain; /* main database pointer */ struct Depsgraph *depsgraph; struct wmWindow *win; /* window where painting originated */ struct Scene *scene; /* current scene from context */ @@ -154,25 +155,34 @@ typedef struct tGPDprimitive { struct bGPDlayer *gpl; /* layer */ struct bGPDframe *gpf; /* frame */ int type; /* type of primitive */ - short cyclic; /* cyclic option */ - short flip; /* flip option */ + int orign_type; /* original type of primitive */ + bool curve; /* type of primitive is a curve */ + short flip; /* flip option */ + tGPspoint *points; /* array of data-points for stroke */ + int point_count; /* number of edges allocated */ + int tot_stored_edges; /* stored number of polygon edges */ int tot_edges; /* number of polygon edges */ - int top[2]; /* first box corner */ - int bottom[2]; /* last box corner */ - int origin[2]; /* initial box corner */ + float origin[2]; /* initial box corner */ + float start[2]; /* first box corner */ + float end[2]; /* last box corner */ + float midpoint[2]; /* midpoint box corner */ + float cp1[2]; /* first control point */ + float cp2[2]; /* second control point */ + int sel_cp; /* flag to determine control point is selected */ int flag; /* flag to determine operations in progress */ + float mval[2]; /* recorded mouse-position */ + float mvalo[2]; /* previous recorded mouse-position */ int lock_axis; /* lock to viewport axis */ + struct RNG *rng; NumInput num; /* numeric input */ - void *draw_handle_3d; /* handle for drawing strokes while operator is running 3d stuff */ } tGPDprimitive; /* Modal Operator Drawing Callbacks ------------------------ */ void ED_gp_draw_interpolation(const struct bContext *C, struct tGPDinterpolate *tgpi, const int type); -void ED_gp_draw_primitives(const struct bContext *C, struct tGPDprimitive *tgpi, const int type); void ED_gp_draw_fill(struct tGPDdraw *tgpw); /* ***************************************************** */ @@ -369,7 +379,8 @@ enum { GP_STROKE_BOX = -1, GP_STROKE_LINE = 1, GP_STROKE_CIRCLE = 2, - GP_STROKE_ARC = 3 + GP_STROKE_ARC = 3, + GP_STROKE_CURVE = 4 }; diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 68d0f6506d0..6d8f337bfc2 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1066,7 +1066,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) } last_valid = i; } - /* invalidate any point other point, to interpolate between + /* invalidate any other point, to interpolate between * first and last contact in an imaginary line between them */ for (i = 0; i < gpd->runtime.sbuffer_size; i++) { if ((i != first_valid) && (i != last_valid)) { diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 029db23499e..0e4f9558842 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -41,9 +41,12 @@ #include "BLI_blenlib.h" #include "BLI_utildefines.h" #include "BLI_math.h" +#include "BLI_rand.h" #include "BLT_translation.h" +#include "PIL_time.h" + #include "DNA_brush_types.h" #include "DNA_gpencil_types.h" #include "DNA_meshdata_types.h" @@ -54,6 +57,7 @@ #include "DNA_view3d_types.h" #include "BKE_brush.h" +#include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_global.h" @@ -64,6 +68,7 @@ #include "BKE_report.h" #include "UI_interface.h" +#include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" @@ -85,13 +90,162 @@ #define MIN_EDGES 2 #define MAX_EDGES 128 +#define MAX_CP 128 #define IDLE 0 #define IN_PROGRESS 1 +#define IN_CURVE_EDIT 2 +#define IN_MOVE 3 + +#define SELECT_NONE 0 +#define SELECT_START 1 +#define SELECT_CP1 2 +#define SELECT_CP2 3 +#define SELECT_END 4 + +#define BIG_SIZE_CTL 15 +#define MID_SIZE_CTL 10 +#define SMALL_SIZE_CTL 8 + +#define MOVE_NONE 0 +#define MOVE_ENDS 1 +#define MOVE_CP 2 /* ************************************************ */ /* Core/Shared Utilities */ +/* clear the session buffers (call this before AND after a paint operation) */ +static void gp_session_validatebuffer(tGPDprimitive *p) +{ + bGPdata *gpd = p->gpd; + + /* clear memory of buffer (or allocate it if starting a new session) */ + if (gpd->runtime.sbuffer) { + memset(gpd->runtime.sbuffer, 0, sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX); + } + else { + gpd->runtime.sbuffer = MEM_callocN(sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer"); + } + + /* reset indices */ + gpd->runtime.sbuffer_size = 0; + + /* reset flags */ + gpd->runtime.sbuffer_sflag = 0; + gpd->runtime.sbuffer_sflag |= GP_STROKE_3DSPACE; + + if (ELEM(p->type, GP_STROKE_BOX, GP_STROKE_CIRCLE)) + gpd->runtime.sbuffer_sflag |= GP_STROKE_CYCLIC; +} + +static void gp_init_colors(tGPDprimitive *p) +{ + bGPdata *gpd = p->gpd; + Brush *brush = p->brush; + + Material *ma = NULL; + MaterialGPencilStyle *gp_style = NULL; + + /* use brush material */ + ma = BKE_gpencil_get_material_from_brush(brush); + + /* if no brush defaults, get material and color info */ + if ((ma == NULL) || (ma->gp_style == NULL)) { + BKE_gpencil_material_ensure(p->bmain, p->ob); + + /* assign always the first material to the brush */ + p->mat = give_current_material(p->ob, 1); + brush->gpencil_settings->material = p->mat; + } + else { + p->mat = ma; + } + + /* check if the material is already on object material slots and add it if missing */ + if (BKE_gpencil_get_material_index(p->ob, p->mat) == 0) { + BKE_object_material_slot_add(p->bmain, p->ob); + assign_material(p->bmain, p->ob, ma, p->ob->totcol, BKE_MAT_ASSIGN_USERPREF); + } + + /* assign color information to temp data */ + gp_style = p->mat->gp_style; + if (gp_style) { + + /* set colors */ + copy_v4_v4(gpd->runtime.scolor, gp_style->stroke_rgba); + copy_v4_v4(gpd->runtime.sfill, gp_style->fill_rgba); + /* add some alpha to make easy the filling without hide strokes */ + if (gpd->runtime.sfill[3] > 0.8f) { + gpd->runtime.sfill[3] = 0.8f; + } + + gpd->runtime.mode = (short)gp_style->mode; + gpd->runtime.bstroke_style = gp_style->stroke_style; + gpd->runtime.bfill_style = gp_style->fill_style; + } +} + +/* Helper to square a primitive */ +static void gpencil_primitive_to_square(tGPDprimitive *tgpi, const float x, const float y) +{ + float w = fabsf(x); + float h = fabsf(y); + if ((x > 0 && y > 0) || (x < 0 && y < 0)) { + if (w > h) + tgpi->end[1] = tgpi->origin[1] + x; + else + tgpi->end[0] = tgpi->origin[0] + y; + } + else { + if (w > h) + tgpi->end[1] = tgpi->origin[1] - x; + else + tgpi->end[0] = tgpi->origin[0] - y; + } +} + +/* Helper to rotate point around origin */ +static void gp_rotate_v2_v2v2fl(float v[2], const float p[2], const float origin[2], const float angle) +{ + float pt[2]; + float r[2]; + sub_v2_v2v2(pt, p, origin); + rotate_v2_v2fl(r, pt, angle); + add_v2_v2v2(v, r, origin); +} + +/* Helper to rotate line around line centre */ +static void gp_primitive_rotate_line(float va[2], float vb[2], const float a[2], const float b[2], const float angle) +{ + float midpoint[2]; + mid_v2_v2v2(midpoint, a, b); + gp_rotate_v2_v2v2fl(va, a, midpoint, angle); + gp_rotate_v2_v2v2fl(vb, b, midpoint, angle); +} + +/* Helper to update cps */ +static void gp_primitive_update_cps(tGPDprimitive *tgpi) +{ + if (!tgpi->curve) { + mid_v2_v2v2(tgpi->midpoint, tgpi->start, tgpi->end); + copy_v2_v2(tgpi->cp1, tgpi->midpoint); + copy_v2_v2(tgpi->cp2, tgpi->cp1); + } + else if (tgpi->type == GP_STROKE_CURVE) { + mid_v2_v2v2(tgpi->midpoint, tgpi->start, tgpi->end); + copy_v2_v2(tgpi->cp1, tgpi->midpoint); + copy_v2_v2(tgpi->cp2, tgpi->cp1); + } + else if (tgpi->type == GP_STROKE_ARC) { + if (tgpi->flip) { + gp_primitive_rotate_line(tgpi->cp1, tgpi->cp2, tgpi->start, tgpi->end, M_PI_2); + } + else { + gp_primitive_rotate_line(tgpi->cp1, tgpi->cp2, tgpi->end, tgpi->start, M_PI_2); + } + } +} + /* Poll callback for primitive operators */ static bool gpencil_primitive_add_poll(bContext *C) { @@ -129,6 +283,15 @@ static bool gpencil_primitive_add_poll(bContext *C) return 1; } +/* Allocate memory to stroke, adds MAX_EDGES on every call */ +static void gpencil_primitive_allocate_memory(tGPDprimitive *tgpi) { + tgpi->point_count += (tgpi->type == GP_STROKE_BOX) ? (MAX_EDGES * 4 + 1) : (MAX_EDGES + 1); + bGPDstroke *gpsf = tgpi->gpf->strokes.first; + gpsf->points = MEM_reallocN(gpsf->points, sizeof(bGPDspoint) * tgpi->point_count); + if (gpsf->dvert != NULL) + gpsf->dvert = MEM_reallocN(gpsf->dvert, sizeof(MDeformVert) * tgpi->point_count); + tgpi->points = MEM_reallocN(tgpi->points, sizeof(tGPspoint) * tgpi->point_count); +} /* ****************** Primitive Interactive *********************** */ @@ -167,13 +330,11 @@ static void gp_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) /* enable recalculation flag by default */ gps->flag |= GP_STROKE_RECALC_CACHES; + gps->flag &= ~GP_STROKE_SELECT; /* the polygon must be closed, so enabled cyclic */ - if (tgpi->type != GP_STROKE_LINE && tgpi->type != GP_STROKE_ARC) { + if (ELEM(tgpi->type,GP_STROKE_BOX ,GP_STROKE_CIRCLE)) gps->flag |= GP_STROKE_CYCLIC; - } - else { - gps->flag &= ~GP_STROKE_CYCLIC; - } + gps->flag |= GP_STROKE_3DSPACE; gps->mat_nr = BKE_gpencil_get_material_index(tgpi->ob, tgpi->mat) - 1; @@ -188,19 +349,38 @@ static void gp_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) /* add to strokes */ BLI_addtail(&tgpi->gpf->strokes, gps); + + /* allocate memory for storage points */ + gpencil_primitive_allocate_memory(tgpi); + + /* Random generator, only init once. */ + tgpi->rng = BLI_rng_new((uint)0); } -/* ----------------------- */ -/* Drawing Callbacks */ - -/* Drawing callback for modal operator in 3d mode */ -static void gpencil_primitive_draw_3d(const bContext *C, ARegion *UNUSED(ar), void *arg) +/* add new segment to curve */ +static void gpencil_primitive_add_segment(tGPDprimitive *tgpi) { - tGPDprimitive *tgpi = (tGPDprimitive *)arg; - ED_gp_draw_primitives(C, tgpi, REGION_DRAW_POST_VIEW); + if(tgpi->tot_stored_edges > 0) + tgpi->tot_stored_edges += (tgpi->tot_edges - 1); + else + tgpi->tot_stored_edges += tgpi->tot_edges; + gpencil_primitive_allocate_memory(tgpi); } -/* ----------------------- */ +/* Helper: set control point */ +static void gp_primitive_set_cp(tGPDprimitive *tgpi, float p[2], float color[4], int size) +{ + bGPDcontrolpoint *cp_points = tgpi->gpd->runtime.cp_points; + + if (tgpi->gpd->runtime.tot_cp_points < MAX_CP) { + CLAMP(size, 5, 20); + bGPDcontrolpoint *cp = &cp_points[tgpi->gpd->runtime.tot_cp_points]; + copy_v2_v2(&cp->x, p); + copy_v4_v4(cp->color, color); + cp->size = size; + tgpi->gpd->runtime.tot_cp_points += 1; + } +} /* Helper: Draw status message while the user is running the operator */ static void gpencil_primitive_status_indicators(bContext *C, tGPDprimitive *tgpi) @@ -209,20 +389,23 @@ static void gpencil_primitive_status_indicators(bContext *C, tGPDprimitive *tgpi char status_str[UI_MAX_DRAW_STR]; char msg_str[UI_MAX_DRAW_STR]; - if (tgpi->type == GP_STROKE_BOX) { - BLI_strncpy(msg_str, IFACE_("Rectangle: ESC/RMB to cancel, LMB set origin, Enter/LMB to confirm, Shift to square, Alt to center"), UI_MAX_DRAW_STR); + if (tgpi->type == GP_STROKE_LINE) { + BLI_strncpy(msg_str, IFACE_("Line: ESC to cancel, LMB set origin, Enter/RMB to confirm, WHEEL/+- to adjust subdivision number, Shift to align, Alt to center"), UI_MAX_DRAW_STR); } - else if (tgpi->type == GP_STROKE_LINE) { - BLI_strncpy(msg_str, IFACE_("Line: ESC/RMB to cancel, LMB set origin, Enter/LMB to confirm, Alt to center"), UI_MAX_DRAW_STR); + else if (tgpi->type == GP_STROKE_BOX) { + BLI_strncpy(msg_str, IFACE_("Rectangle: ESC to cancel, LMB set origin, Enter/RMB to confirm, WHEEL/+- to adjust subdivision number, Shift to square, Alt to center"), UI_MAX_DRAW_STR); + } + else if (tgpi->type == GP_STROKE_CIRCLE) { + BLI_strncpy(msg_str, IFACE_("Circle: ESC to cancel, Enter/RMB to confirm, WHEEL/+- to adjust edge number, Shift to square, Alt to center"), UI_MAX_DRAW_STR); } else if (tgpi->type == GP_STROKE_ARC) { - BLI_strncpy(msg_str, IFACE_("Arc: ESC/RMB to cancel, Enter/LMB to confirm, WHEEL/+- to adjust edge number, Shift to square, Alt to center, F to flip, C to Close"), UI_MAX_DRAW_STR); + BLI_strncpy(msg_str, IFACE_("Arc: ESC to cancel, Enter/RMB to confirm, WHEEL/+- to adjust edge number, Shift to square, Alt to center, M: Flip"), UI_MAX_DRAW_STR); } - else { - BLI_strncpy(msg_str, IFACE_("Circle: ESC/RMB to cancel, Enter/LMB to confirm, WHEEL/+- to adjust edge number, Shift to square, Alt to center"), UI_MAX_DRAW_STR); + else if (tgpi->type == GP_STROKE_CURVE) { + BLI_strncpy(msg_str, IFACE_("Curve: ESC to cancel, Enter/RMB to confirm, WHEEL/+- to adjust edge number, Shift to square, Alt to center, E: extrude"), UI_MAX_DRAW_STR); } - if (tgpi->type == GP_STROKE_CIRCLE || tgpi->type == GP_STROKE_ARC) { + if (ELEM(tgpi->type, GP_STROKE_CIRCLE, GP_STROKE_ARC, GP_STROKE_LINE, GP_STROKE_BOX)) { if (hasNumInput(&tgpi->num)) { char str_offs[NUM_STR_REP_LEN]; @@ -233,114 +416,208 @@ static void gpencil_primitive_status_indicators(bContext *C, tGPDprimitive *tgpi if (tgpi->flag == IN_PROGRESS) { BLI_snprintf( status_str, sizeof(status_str), "%s: %d (%d, %d) (%d, %d)", msg_str, (int)tgpi->tot_edges, - tgpi->top[0], tgpi->top[1], tgpi->bottom[0], tgpi->bottom[1]); + tgpi->start[0], tgpi->start[1], tgpi->end[0], tgpi->end[1]); } else { BLI_snprintf( status_str, sizeof(status_str), "%s: %d (%d, %d)", msg_str, (int)tgpi->tot_edges, - tgpi->bottom[0], tgpi->bottom[1]); + tgpi->end[0], tgpi->end[1]); } } } else { if (tgpi->flag == IN_PROGRESS) { BLI_snprintf( - status_str, sizeof(status_str), "%s: (%d, %d) (%d, %d)", msg_str, - tgpi->top[0], tgpi->top[1], tgpi->bottom[0], tgpi->bottom[1]); + status_str, sizeof(status_str), "%s: %d (%d, %d) (%d, %d)", msg_str, (int)tgpi->tot_edges, + tgpi->start[0], tgpi->start[1], tgpi->end[0], tgpi->end[1]); } else { BLI_snprintf( status_str, sizeof(status_str), "%s: (%d, %d)", msg_str, - tgpi->bottom[0], tgpi->bottom[1]); + tgpi->end[0], tgpi->end[1]); } } ED_workspace_status_text(C, status_str); } -/* ----------------------- */ - /* create a rectangle */ static void gp_primitive_rectangle(tGPDprimitive *tgpi, tGPspoint *points2D) { - BLI_assert(tgpi->tot_edges == 4); + float coords[5][2]; - points2D[0].x = (float)tgpi->top[0]; - points2D[0].y = (float)tgpi->top[1]; + coords[0][0] = tgpi->start[0]; + coords[0][1] = tgpi->start[1]; + coords[1][0] = tgpi->end[0]; + coords[1][1] = tgpi->start[1]; + coords[2][0] = tgpi->end[0]; + coords[2][1] = tgpi->end[1]; + coords[3][0] = tgpi->start[0]; + coords[3][1] = tgpi->end[1]; + coords[4][0] = tgpi->start[0]; + coords[4][1] = tgpi->start[1]; - points2D[1].x = (float)tgpi->bottom[0]; - points2D[1].y = (float)tgpi->top[1]; + const float step = 1.0f / (float)(tgpi->tot_edges); + int i = tgpi->tot_stored_edges; - points2D[2].x = (float)tgpi->bottom[0]; - points2D[2].y = (float)tgpi->bottom[1]; + for (int j = 0; j < 4; j++) { + float a = 0.0f; + for (int k = 0; k < tgpi->tot_edges; k++) { + tGPspoint *p2d = &points2D[i]; + interp_v2_v2v2(&p2d->x, coords[j], coords[j + 1], a); + a += step; + i++; + } + } - points2D[3].x = (float)tgpi->top[0]; - points2D[3].y = (float)tgpi->bottom[1]; + mid_v2_v2v2(tgpi->midpoint, tgpi->start, tgpi->end); + float color[4]; + UI_GetThemeColor4fv(TH_ACTIVE_VERT, color); + gp_primitive_set_cp(tgpi, tgpi->end, color, BIG_SIZE_CTL); + if (tgpi->tot_stored_edges) { + UI_GetThemeColor4fv(TH_REDALERT, color); + gp_primitive_set_cp(tgpi, tgpi->start, color, SMALL_SIZE_CTL); + } + else + gp_primitive_set_cp(tgpi, tgpi->start, color, BIG_SIZE_CTL); + UI_GetThemeColor4fv(TH_REDALERT, color); + gp_primitive_set_cp(tgpi, tgpi->midpoint, color, SMALL_SIZE_CTL); } /* create a line */ static void gp_primitive_line(tGPDprimitive *tgpi, tGPspoint *points2D) { - BLI_assert(tgpi->tot_edges == 2); + if (tgpi->tot_edges == 2) { + int i = tgpi->tot_stored_edges; - points2D[0].x = (float)tgpi->top[0]; - points2D[0].y = (float)tgpi->top[1]; + points2D[i].x = tgpi->start[0]; + points2D[i].y = tgpi->start[1]; - points2D[1].x = (float)tgpi->bottom[0]; - points2D[1].y = (float)tgpi->bottom[1]; + points2D[i + 1].x = tgpi->end[0]; + points2D[i + 1].y = tgpi->end[1]; + } + else { + const int totpoints = (tgpi->tot_edges + tgpi->tot_stored_edges); + const float step = 1.0f / (float)(tgpi->tot_edges - 1); + float a = tgpi->tot_stored_edges ? step : 0.0f; + + for (int i = tgpi->tot_stored_edges; i < totpoints; i++) { + tGPspoint *p2d = &points2D[i]; + interp_v2_v2v2(&p2d->x, tgpi->start, tgpi->end, a); + a += step; + } + } + float color[4]; + UI_GetThemeColor4fv(TH_ACTIVE_VERT, color); + gp_primitive_set_cp(tgpi, tgpi->end, color, BIG_SIZE_CTL); + if (tgpi->tot_stored_edges) { + UI_GetThemeColor4fv(TH_REDALERT, color); + gp_primitive_set_cp(tgpi, tgpi->start, color, SMALL_SIZE_CTL); + } + else + gp_primitive_set_cp(tgpi, tgpi->start, color, BIG_SIZE_CTL); } /* create an arc */ static void gp_primitive_arc(tGPDprimitive *tgpi, tGPspoint *points2D) { - const int totpoints = tgpi->tot_edges; - const float step = M_PI_2 / (float)(totpoints - 1); - float length[2]; - int start[2]; - int end[2]; - float a = 0.0f; + const int totpoints = (tgpi->tot_edges + tgpi->tot_stored_edges); + const float step = M_PI_2 / (float)(tgpi->tot_edges - 1); + float start[2]; + float end[2]; + float cp1[2]; + float corner[2]; + float midpoint[2]; + float a = tgpi->tot_stored_edges ? step : 0.0f; - start[0] = tgpi->top[0]; - start[1] = tgpi->top[1]; - end[0] = tgpi->bottom[0]; - end[1] = tgpi->bottom[1]; + mid_v2_v2v2(tgpi->midpoint, tgpi->start, tgpi->end); + copy_v2_v2(start, tgpi->start); + copy_v2_v2(end, tgpi->end); + copy_v2_v2(cp1, tgpi->cp1); + copy_v2_v2(midpoint, tgpi->midpoint); - if (tgpi->flip) { - SWAP(int, end[0], start[0]); - SWAP(int, end[1], start[1]); - } - - length[0] = end[0] - start[0]; - length[1] = end[1] - start[1]; - - for (int i = 0; i < totpoints; i++) { + corner[0] = midpoint[0] - (cp1[0] - midpoint[0]); + corner[1] = midpoint[1] - (cp1[1] - midpoint[1]); + + for (int i = tgpi->tot_stored_edges; i < totpoints; i++) { tGPspoint *p2d = &points2D[i]; - p2d->x = (start[0] + sinf(a) * length[0]); - p2d->y = (end[1] - cosf(a) * length[1]); + p2d->x = corner[0] + (end[0] - corner[0]) * sinf(a) + (start[0] - corner[0]) * cosf(a); + p2d->y = corner[1] + (end[1] - corner[1]) * sinf(a) + (start[1] - corner[1]) * cosf(a); a += step; } + float color[4]; + UI_GetThemeColor4fv(TH_ACTIVE_VERT, color); + gp_primitive_set_cp(tgpi, tgpi->end, color, BIG_SIZE_CTL); + if (tgpi->tot_stored_edges) { + UI_GetThemeColor4fv(TH_REDALERT, color); + gp_primitive_set_cp(tgpi, tgpi->start, color, SMALL_SIZE_CTL); + } + else + gp_primitive_set_cp(tgpi, tgpi->start, color, BIG_SIZE_CTL); + UI_GetThemeColor4fv(TH_GP_VERTEX_SELECT, color); + gp_primitive_set_cp(tgpi, tgpi->cp1, color, BIG_SIZE_CTL * 0.9f); +} + +/* create a bezier */ +static void gp_primitive_bezier(tGPDprimitive *tgpi, tGPspoint *points2D) +{ + const int totpoints = (tgpi->tot_edges + tgpi->tot_stored_edges); + const float step = 1.0f / (float)(tgpi->tot_edges - 1); + float bcp1[2]; + float bcp2[2]; + float bcp3[2]; + float bcp4[2]; + float a = tgpi->tot_stored_edges ? step : 0.0f; + + copy_v2_v2(bcp1, tgpi->start); + copy_v2_v2(bcp2, tgpi->cp1); + copy_v2_v2(bcp3, tgpi->cp2); + copy_v2_v2(bcp4, tgpi->end); + + for (int i = tgpi->tot_stored_edges; i < totpoints; i++) { + tGPspoint *p2d = &points2D[i]; + interp_v2_v2v2v2v2_cubic(&p2d->x, bcp1, bcp2, bcp3, bcp4, a); + a += step; + } + float color[4]; + UI_GetThemeColor4fv(TH_ACTIVE_VERT, color); + gp_primitive_set_cp(tgpi, tgpi->end, color, BIG_SIZE_CTL); + if (tgpi->tot_stored_edges) { + UI_GetThemeColor4fv(TH_REDALERT, color); + gp_primitive_set_cp(tgpi, tgpi->start, color, SMALL_SIZE_CTL); + } + else + gp_primitive_set_cp(tgpi, tgpi->start, color, BIG_SIZE_CTL); + UI_GetThemeColor4fv(TH_GP_VERTEX_SELECT, color); + gp_primitive_set_cp(tgpi, tgpi->cp1, color, BIG_SIZE_CTL * 0.9f); + gp_primitive_set_cp(tgpi, tgpi->cp2, color, BIG_SIZE_CTL * 0.9f); } /* create a circle */ static void gp_primitive_circle(tGPDprimitive *tgpi, tGPspoint *points2D) { - const int totpoints = tgpi->tot_edges; - const float step = (2.0f * M_PI) / (float)(totpoints); + const int totpoints = (tgpi->tot_edges + tgpi->tot_stored_edges); + const float step = (2.0f * M_PI) / (float)(tgpi->tot_edges); float center[2]; float radius[2]; float a = 0.0f; - /* TODO: Use math-lib functions for these? */ - center[0] = tgpi->top[0] + ((tgpi->bottom[0] - tgpi->top[0]) / 2.0f); - center[1] = tgpi->top[1] + ((tgpi->bottom[1] - tgpi->top[1]) / 2.0f); - radius[0] = fabsf(((tgpi->bottom[0] - tgpi->top[0]) / 2.0f)); - radius[1] = fabsf(((tgpi->bottom[1] - tgpi->top[1]) / 2.0f)); + center[0] = tgpi->start[0] + ((tgpi->end[0] - tgpi->start[0]) / 2.0f); + center[1] = tgpi->start[1] + ((tgpi->end[1] - tgpi->start[1]) / 2.0f); + radius[0] = fabsf(((tgpi->end[0] - tgpi->start[0]) / 2.0f)); + radius[1] = fabsf(((tgpi->end[1] - tgpi->start[1]) / 2.0f)); - for (int i = 0; i < totpoints; i++) { + for (int i = tgpi->tot_stored_edges; i < totpoints; i++) { tGPspoint *p2d = &points2D[i]; p2d->x = (center[0] + cosf(a) * radius[0]); p2d->y = (center[1] + sinf(a) * radius[1]); a += step; } + float color[4]; + UI_GetThemeColor4fv(TH_ACTIVE_VERT, color); + gp_primitive_set_cp(tgpi, tgpi->end, color, BIG_SIZE_CTL); + gp_primitive_set_cp(tgpi, tgpi->start, color, BIG_SIZE_CTL); + UI_GetThemeColor4fv(TH_REDALERT, color); + gp_primitive_set_cp(tgpi, center, color, SMALL_SIZE_CTL); } /* Helper: Update shape of the stroke */ @@ -348,18 +625,26 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) { ToolSettings *ts = tgpi->scene->toolsettings; bGPdata *gpd = tgpi->gpd; + Brush *brush = tgpi->brush; bGPDstroke *gps = tgpi->gpf->strokes.first; + GP_Sculpt_Settings *gset = &ts->gp_sculpt; + int depth_margin = (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 4 : 0; + char *align_flag = &ts->gpencil_v3d_align; + bool is_depth = (bool)(*align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE)); - /* realloc points to new size */ - /* TODO: only do this if the size has changed? */ - gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * tgpi->tot_edges); - if (gps->dvert != NULL) { - gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * tgpi->tot_edges); - } - gps->totpoints = tgpi->tot_edges; + if (tgpi->type == GP_STROKE_BOX) + gps->totpoints = (tgpi->tot_edges * 4 + tgpi->tot_stored_edges); + else + gps->totpoints = (tgpi->tot_edges + tgpi->tot_stored_edges); + + if (tgpi->tot_stored_edges) + gps->totpoints--; + + tgpi->gpd->runtime.tot_cp_points = 0; /* compute screen-space coordinates for points */ - tGPspoint *points2D = MEM_callocN(sizeof(tGPspoint) * tgpi->tot_edges, "gp primitive points2D"); + tGPspoint *points2D = tgpi->points; + switch (tgpi->type) { case GP_STROKE_BOX: gp_primitive_rectangle(tgpi, points2D); @@ -372,46 +657,254 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) break; case GP_STROKE_ARC: gp_primitive_arc(tgpi, points2D); - if (tgpi->cyclic) - gps->flag |= GP_STROKE_CYCLIC; - else - gps->flag &= ~GP_STROKE_CYCLIC; break; + case GP_STROKE_CURVE: + gp_primitive_bezier(tgpi, points2D); default: break; - } + } /* convert screen-coordinates to 3D coordinates */ + gp_session_validatebuffer(tgpi); + gp_init_colors(tgpi); + if (gset->flag & GP_SCULPT_SETT_FLAG_PRIMITIVE_CURVE) { + curvemapping_initialize(ts->gp_sculpt.cur_primitive); + } + if (tgpi->brush->gpencil_settings->flag & GP_BRUSH_USE_JITTER_PRESSURE) { + curvemapping_initialize(tgpi->brush->gpencil_settings->curve_jitter); + } + if (tgpi->brush->gpencil_settings->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { + curvemapping_initialize(tgpi->brush->gpencil_settings->curve_strength); + } + + /* get an array of depths, far depths are blended */ + float *depth_arr = NULL; + if (is_depth) { + int i; + int mval_i[2], mval_prev[2] = { 0 }; + bool interp_depth = false; + bool found_depth = false; + + /* need to restore the original projection settings before packing up */ + view3d_region_operator_needs_opengl(tgpi->win, tgpi->ar); + ED_view3d_autodist_init(tgpi->depsgraph, tgpi->ar, tgpi->v3d, (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 1 : 0); + + depth_arr = MEM_mallocN(sizeof(float) * gps->totpoints, "depth_points"); + tGPspoint *ptc = &points2D[0]; + for (i = 0; i < gps->totpoints; i++, ptc++) { + round_v2i_v2fl(mval_i, &ptc->x); + if ((ED_view3d_autodist_depth(tgpi->ar, mval_i, depth_margin, depth_arr + i) == 0) && + (i && (ED_view3d_autodist_depth_seg(tgpi->ar, mval_i, mval_prev, depth_margin + 1, depth_arr + i) == 0))) + { + interp_depth = true; + } + else { + found_depth = true; + } + copy_v2_v2_int(mval_prev, mval_i); + } + + if (!found_depth) { + for (i = 0; i < gps->totpoints; i++) { + depth_arr[i] = 0.9999f; + } + } + else { + /* if all depth are too high disable */ + bool valid_depth = false; + for (i = 0; i < gps->totpoints; i++) { + if (depth_arr[i] < 0.9999f) { + valid_depth = true; + break; + } + } + if (!valid_depth) { + MEM_SAFE_FREE(depth_arr); + is_depth = false; + } + else { + if ((ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE_ENDPOINTS) || + (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE_FIRST)) + { + int first_valid = 0; + int last_valid = 0; + + /* find first valid contact point */ + for (i = 0; i < gps->totpoints; i++) { + if (depth_arr[i] != FLT_MAX) + break; + } + first_valid = i; + + /* find last valid contact point */ + if (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE_FIRST) { + last_valid = first_valid; + } + else { + for (i = gps->totpoints - 1; i >= 0; i--) { + if (depth_arr[i] != FLT_MAX) + break; + } + last_valid = i; + } + + /* invalidate any other point, to interpolate between + * first and last contact in an imaginary line between them */ + for (i = 0; i < gps->totpoints; i++) { + if ((i != first_valid) && (i != last_valid)) { + depth_arr[i] = FLT_MAX; + } + } + interp_depth = true; + } + + if (interp_depth) { + interp_sparse_array(depth_arr, gps->totpoints, FLT_MAX); + } + } + } + } + + /* load stroke points and sbuffer */ for (int i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; tGPspoint *p2d = &points2D[i]; - /* convert screen-coordinates to 3D coordinates */ - gp_stroke_convertcoords_tpoint(tgpi->scene, tgpi->ar, tgpi->ob, tgpi->gpl, p2d, NULL, &pt->x); + /* Copy points to buffer */ + tGPspoint *tpt = ((tGPspoint *)(gpd->runtime.sbuffer) + gpd->runtime.sbuffer_size); - pt->pressure = 1.0f; - pt->strength = tgpi->brush->gpencil_settings->draw_strength; + /* Store original points */ + float tmp_xyp[2]; + copy_v2_v2(tmp_xyp, &p2d->x); + + /* calc pressure */ + float curve_pressure = 1.0; + float pressure = 1.0; + float strength = brush->gpencil_settings->draw_strength; + + /* normalize value to evaluate curve */ + if (gset->flag & GP_SCULPT_SETT_FLAG_PRIMITIVE_CURVE) { + float value = (float)i / (gps->totpoints - 1); + curve_pressure = curvemapping_evaluateF(gset->cur_primitive, 0, value); + pressure = curve_pressure; + } + + /* apply jitter to position */ + if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && + (brush->gpencil_settings->draw_jitter > 0.0f)) + { + float jitter; + + if (brush->gpencil_settings->flag & GP_BRUSH_USE_JITTER_PRESSURE) { + jitter = curvemapping_evaluateF(brush->gpencil_settings->curve_jitter, 0, curve_pressure); + jitter *= brush->gpencil_settings->draw_sensitivity; + } + else { + jitter = brush->gpencil_settings->draw_jitter; + } + + const float exfactor = (brush->gpencil_settings->draw_jitter + 2.0f) * (brush->gpencil_settings->draw_jitter + 2.0f); /* exponential value */ + const float rnd = BLI_rng_get_float(tgpi->rng); + const float fac = rnd * exfactor * jitter; + if (rnd > 0.5f) { + add_v2_fl(&p2d->x, -fac); + } + else { + add_v2_fl(&p2d->x, fac); + } + } + + /* apply randomness to pressure */ + if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && + (brush->gpencil_settings->draw_random_press > 0.0f)) + { + float rnd = BLI_rng_get_float(tgpi->rng); + if (rnd > 0.5f) { + pressure -= brush->gpencil_settings->draw_random_press * rnd; + } + else { + pressure += brush->gpencil_settings->draw_random_press * rnd; + } + } + + /* color strength */ + if (brush->gpencil_settings->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { + float curvef = curvemapping_evaluateF(brush->gpencil_settings->curve_strength, 0, curve_pressure); + strength *= curvef * brush->gpencil_settings->draw_sensitivity; + strength *= brush->gpencil_settings->draw_strength; + } + + CLAMP(strength, GPENCIL_STRENGTH_MIN, 1.0f); + + /* apply randomness to color strength */ + if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && + (brush->gpencil_settings->draw_random_strength > 0.0f)) + { + const float rnd = BLI_rng_get_float(tgpi->rng); + if (rnd > 0.5f) { + strength -= strength * brush->gpencil_settings->draw_random_strength * rnd; + } + else { + strength += strength * brush->gpencil_settings->draw_random_strength * rnd; + } + CLAMP(strength, GPENCIL_STRENGTH_MIN, 1.0f); + } + + copy_v2_v2(&tpt->x, &p2d->x); + + CLAMP_MIN(pressure, 0.1f); + + tpt->pressure = pressure; + tpt->strength = strength; + tpt->time = p2d->time; + tpt->uv_fac = 1.0f; + tpt->uv_rot = p2d->uv_rot; + + gpd->runtime.sbuffer_size++; + + /* add small offset to keep stroke over the surface */ + if ((depth_arr) && (gpd->zdepth_offset > 0.0f)) { + depth_arr[i] *= (1.0f - gpd->zdepth_offset); + } + + /* convert screen-coordinates to 3D coordinates */ + gp_stroke_convertcoords_tpoint( + tgpi->scene, tgpi->ar, tgpi->ob, tgpi->gpl, + p2d, depth_arr ? depth_arr + i : NULL, + &pt->x); + + pt->pressure = pressure; + pt->strength = strength; pt->time = 0.0f; + pt->flag = 0; + pt->uv_fac = 1.0f; if (gps->dvert != NULL) { MDeformVert *dvert = &gps->dvert[i]; dvert->totweight = 0; dvert->dw = NULL; } + + /* Restore original points */ + copy_v2_v2(&p2d->x, tmp_xyp); } - /* if axis locked, reproject to plane locked */ - if (tgpi->lock_axis > GP_LOCKAXIS_VIEW) { - bGPDspoint *tpt = gps->points; + /* store cps and convert coords */ + if (tgpi->gpd->runtime.tot_cp_points > 0) { + bGPDcontrolpoint *cps = tgpi->gpd->runtime.cp_points; + for (int i = 0; i < tgpi->gpd->runtime.tot_cp_points; i++) { + bGPDcontrolpoint *cp = &cps[i]; + gp_stroke_convertcoords_tpoint(tgpi->scene, tgpi->ar, tgpi->ob, tgpi->gpl, (tGPspoint *)cp, NULL, &cp->x); + } + } + + /* reproject to plane */ + if (!is_depth) { float origin[3]; ED_gp_get_drawing_reference(tgpi->scene, tgpi->ob, tgpi->gpl, ts->gpencil_v3d_align, origin); - - for (int i = 0; i < gps->totpoints; i++, tpt++) { - ED_gp_project_point_to_plane(tgpi->ob, tgpi->rv3d, origin, - ts->gp_sculpt.lock_axis - 1, - tpt); - } + ED_gp_project_stroke_to_plane( + tgpi->ob, tgpi->rv3d, gps, origin, ts->gp_sculpt.lock_axis - 1); } /* if parented change position relative to parent object */ @@ -423,8 +916,7 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) /* force fill recalc */ gps->flag |= GP_STROKE_RECALC_CACHES; - /* free temp data */ - MEM_SAFE_FREE(points2D); + MEM_SAFE_FREE(depth_arr); DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -443,18 +935,14 @@ static void gpencil_primitive_update(bContext *C, wmOperator *op, tGPDprimitive gp_primitive_update_strokes(C, tgpi); } -/* ----------------------- */ - static void gpencil_primitive_interaction_begin(tGPDprimitive *tgpi, const wmEvent *event) { - tgpi->origin[0] = event->mval[0]; - tgpi->origin[1] = event->mval[1]; - - tgpi->top[0] = event->mval[0]; - tgpi->top[1] = event->mval[1]; - - tgpi->bottom[0] = event->mval[0]; - tgpi->bottom[1] = event->mval[1]; + copy_v2fl_v2i(tgpi->mval, event->mval); + copy_v2_v2(tgpi->origin, tgpi->mval); + copy_v2_v2(tgpi->start, tgpi->mval); + copy_v2_v2(tgpi->end, tgpi->mval); + copy_v2_v2(tgpi->cp1, tgpi->mval); + copy_v2_v2(tgpi->cp2, tgpi->mval); } /* Exit and free memory */ @@ -465,20 +953,35 @@ static void gpencil_primitive_exit(bContext *C, wmOperator *op) /* don't assume that operator data exists at all */ if (tgpi) { - /* remove drawing handler */ - if (tgpi->draw_handle_3d) { - ED_region_draw_cb_exit(tgpi->ar->type, tgpi->draw_handle_3d); - } - /* clear status message area */ ED_workspace_status_text(C, NULL); + MEM_SAFE_FREE(tgpi->points); + tgpi->gpd->runtime.tot_cp_points = 0; + MEM_SAFE_FREE(tgpi->gpd->runtime.cp_points); /* finally, free memory used by temp data */ BKE_gpencil_free_strokes(tgpi->gpf); - MEM_freeN(tgpi->gpf); + MEM_SAFE_FREE(tgpi->gpf); + + /* free random seed */ + if (tgpi->rng != NULL) { + BLI_rng_free(tgpi->rng); + } + MEM_freeN(tgpi); } - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + /* free stroke buffer */ + if ((gpd != NULL) && (gpd->runtime.sbuffer)) { + MEM_SAFE_FREE(gpd->runtime.sbuffer); + gpd->runtime.sbuffer = NULL; + + /* clear flags */ + gpd->runtime.sbuffer_size = 0; + gpd->runtime.sbuffer_sflag = 0; + } + + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); /* clear pointer */ @@ -499,7 +1002,10 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op) tGPDprimitive *tgpi = MEM_callocN(sizeof(tGPDprimitive), "GPencil Primitive Data"); op->customdata = tgpi; + tgpi->points = MEM_callocN(sizeof(tGPspoint), "gp primitive points2D"); + /* set current scene and window info */ + tgpi->bmain = CTX_data_main(C); tgpi->scene = scene; tgpi->ob = CTX_data_active_object(C); tgpi->sa = CTX_wm_area(C); @@ -509,11 +1015,20 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op) tgpi->depsgraph = CTX_data_depsgraph(C); tgpi->win = CTX_wm_window(C); + /* save original type */ + tgpi->orign_type = RNA_enum_get(op->ptr, "type"); + /* set current frame number */ tgpi->cframe = cfra_eval; /* set GP datablock */ tgpi->gpd = gpd; + /* region where paint was originated */ + tgpi->gpd->runtime.ar = tgpi->ar; + + /* control points */ + tgpi->gpd->runtime.cp_points = MEM_callocN(sizeof(bGPDcontrolpoint) * MAX_CP, "gp primitive cpoint"); + tgpi->gpd->runtime.tot_cp_points = 0; /* getcolor info */ tgpi->mat = BKE_gpencil_material_ensure(bmain, tgpi->ob); @@ -521,31 +1036,44 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op) /* set parameters */ tgpi->type = RNA_enum_get(op->ptr, "type"); + if(ELEM(tgpi->type, GP_STROKE_ARC, GP_STROKE_CURVE)) + tgpi->curve = true; + else + tgpi->curve = false; + /* set default edge count */ - if (tgpi->type == GP_STROKE_CIRCLE) { - RNA_int_set(op->ptr, "edges", 64); - } - else if (tgpi->type == GP_STROKE_ARC) { - RNA_int_set(op->ptr, "edges", 32); - } - else if (tgpi->type == GP_STROKE_BOX) { - RNA_int_set(op->ptr, "edges", 4); - } - else { /* LINE */ - RNA_int_set(op->ptr, "edges", 2); + switch (tgpi->type) { + case GP_STROKE_LINE: + { + RNA_int_set(op->ptr, "edges", 8); + break; + } + case GP_STROKE_BOX: + { + RNA_int_set(op->ptr, "edges", 8); + break; + } + case GP_STROKE_CIRCLE: + { + RNA_int_set(op->ptr, "edges", 96); + break; + } + default: + { + RNA_int_set(op->ptr, "edges", 64); + break; + } } + tgpi->tot_stored_edges = 0; tgpi->tot_edges = RNA_int_get(op->ptr, "edges"); tgpi->flag = IDLE; - tgpi->lock_axis = ts->gp_sculpt.lock_axis; /* set temp layer, frame and stroke */ gp_primitive_set_initdata(C, tgpi); } -/* ----------------------- */ - /* Invoke handler: Initialize the operator */ static int gpencil_primitive_invoke(bContext *C, wmOperator *op, const wmEvent *event) { @@ -568,9 +1096,6 @@ static int gpencil_primitive_invoke(bContext *C, wmOperator *op, const wmEvent * */ op->flag |= OP_IS_MODAL_CURSOR_REGION; - /* Enable custom drawing handlers */ - tgpi->draw_handle_3d = ED_region_draw_cb_activate(tgpi->ar->type, gpencil_primitive_draw_3d, tgpi, REGION_DRAW_POST_VIEW); - /* set cursor to indicate modal */ WM_cursor_modal_set(win, BC_CROSSCURSOR); @@ -624,7 +1149,6 @@ static void gpencil_primitive_interaction_end(bContext *C, wmOperator *op, wmWin if (dw) { dw->weight = ts->vgroup_weight; } - } } @@ -635,23 +1159,134 @@ static void gpencil_primitive_interaction_end(bContext *C, wmOperator *op, wmWin gpencil_primitive_exit(C, op); } -/* Helper to square a primitive */ -static void gpencil_primitive_to_square(tGPDprimitive *tgpi, const int x, const int y) +/* edit event handling */ +static void gpencil_primitive_edit_event_handling(bContext *C, wmOperator *op, wmWindow *win, const wmEvent *event, tGPDprimitive *tgpi) { - int w = abs(x); - int h = abs(y); - if ((x > 0 && y > 0) || (x < 0 && y < 0)) { - if (w > h) - tgpi->bottom[1] = tgpi->origin[1] + x; - else - tgpi->bottom[0] = tgpi->origin[0] + y; + /* calculate nearest point then set cursor */ + int move = MOVE_NONE; + float a = len_v2v2(tgpi->mval, tgpi->start); + float b = len_v2v2(tgpi->mval, tgpi->end); + + float c = len_v2v2(tgpi->mval, tgpi->cp1); + float d = len_v2v2(tgpi->mval, tgpi->cp2); + + if (tgpi->flag == IN_CURVE_EDIT) { + if ((a < BIG_SIZE_CTL && tgpi->tot_stored_edges == 0) || b < BIG_SIZE_CTL) { + move = MOVE_ENDS; + WM_cursor_modal_set(win, BC_NSEW_SCROLLCURSOR); + } + else if(tgpi->curve) { + move = MOVE_CP; + WM_cursor_modal_set(win, BC_HANDCURSOR); + } + else { + WM_cursor_modal_set(win, BC_CROSSCURSOR); + } } - else { - if (w > h) - tgpi->bottom[1] = tgpi->origin[1] - x; - else - tgpi->bottom[0] = tgpi->origin[0] - y; + else if (tgpi->flag == IN_PROGRESS) { + WM_cursor_modal_set(win, BC_NSEW_SCROLLCURSOR); } + + switch (event->type) { + case MOUSEMOVE: + { + if ((event->val == KM_PRESS) && tgpi->sel_cp != SELECT_NONE) { + if (tgpi->sel_cp == SELECT_START && tgpi->tot_stored_edges == 0) { + copy_v2_v2(tgpi->start, tgpi->mval); + } + else if (tgpi->sel_cp == SELECT_END) { + copy_v2_v2(tgpi->end, tgpi->mval); + } + else if (tgpi->sel_cp == SELECT_CP1 || (tgpi->sel_cp == SELECT_CP2 && tgpi->type != GP_STROKE_CURVE)) { + float dx = (tgpi->mval[0] - tgpi->mvalo[0]); + float dy = (tgpi->mval[1] - tgpi->mvalo[1]); + tgpi->cp1[0] += dx; + tgpi->cp1[1] += dy; + if (event->shift) + copy_v2_v2(tgpi->cp2, tgpi->cp1); + } + else if (tgpi->sel_cp == SELECT_CP2) { + float dx = (tgpi->mval[0] - tgpi->mvalo[0]); + float dy = (tgpi->mval[1] - tgpi->mvalo[1]); + tgpi->cp2[0] += dx; + tgpi->cp2[1] += dy; + if (event->shift) + copy_v2_v2(tgpi->cp1, tgpi->cp2); + } + /* update screen */ + gpencil_primitive_update(C, op, tgpi); + } + break; + } + case LEFTMOUSE: + { + if ((event->val == KM_PRESS)) { + /* find nearest cp based on stroke end points */ + if (move == MOVE_ENDS) + tgpi->sel_cp = (a < b) ? SELECT_START : SELECT_END; + else if (move == MOVE_CP) + tgpi->sel_cp = (c < d) ? SELECT_CP1 : SELECT_CP2; + else + tgpi->sel_cp = SELECT_NONE; + break; + } + else if ((event->val == KM_RELEASE) && (tgpi->flag == IN_PROGRESS)) { + /* set control points and enter edit mode */ + tgpi->flag = IN_CURVE_EDIT; + gp_primitive_update_cps(tgpi); + gpencil_primitive_update(C, op, tgpi); + } + else { + tgpi->sel_cp = SELECT_NONE; + } + break; + } + case MKEY: + { + if ((event->val == KM_PRESS) && + (tgpi->curve) && + (tgpi->orign_type == GP_STROKE_ARC)) + { + tgpi->flip ^= 1; + gp_primitive_update_cps(tgpi); + gpencil_primitive_update(C, op, tgpi); + } + break; + } + case EKEY: + { + if (tgpi->flag == IN_CURVE_EDIT && !ELEM(tgpi->type, GP_STROKE_BOX, GP_STROKE_CIRCLE)) { + tgpi->flag = IN_PROGRESS; + WM_cursor_modal_set(win, BC_NSEW_SCROLLCURSOR); + gpencil_primitive_add_segment(tgpi); + copy_v2_v2(tgpi->start, tgpi->end); + copy_v2_v2(tgpi->origin, tgpi->start); + gp_primitive_update_cps(tgpi); + } + break; + } + } +} + +/* move */ +static void gpencil_primitive_move(tGPDprimitive *tgpi) +{ + float move[2]; + sub_v2_v2v2(move, tgpi->mval, tgpi->mvalo); + + bGPDstroke *gps = tgpi->gpf->strokes.first; + tGPspoint *points2D = tgpi->points; + + for (int i = 0; i < gps->totpoints; i++) { + tGPspoint *p2d = &points2D[i]; + add_v2_v2(&p2d->x, move); + } + + add_v2_v2(tgpi->start, move); + add_v2_v2(tgpi->end, move); + add_v2_v2(tgpi->cp1, move); + add_v2_v2(tgpi->cp2, move); + add_v2_v2(tgpi->origin, move); } /* Modal handler: Events handling during interactive part */ @@ -661,15 +1296,47 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e wmWindow *win = CTX_wm_window(C); const bool has_numinput = hasNumInput(&tgpi->num); + copy_v2fl_v2i(tgpi->mval, event->mval); + + if (tgpi->flag == IN_MOVE) { + + switch (event->type) { + case MOUSEMOVE: + gpencil_primitive_move(tgpi); + gpencil_primitive_update(C, op, tgpi); + break; + case ESCKEY: + case RIGHTMOUSE: + case LEFTMOUSE: + tgpi->flag = IN_CURVE_EDIT; + break; + } + copy_v2_v2(tgpi->mvalo, tgpi->mval); + return OPERATOR_RUNNING_MODAL; + } + else if (tgpi->flag != IDLE) { + gpencil_primitive_edit_event_handling(C, op, win, event, tgpi); + } + switch (event->type) { case LEFTMOUSE: + { if ((event->val == KM_PRESS) && (tgpi->flag == IDLE)) { /* start drawing primitive */ /* TODO: Ignore if not in main region yet */ tgpi->flag = IN_PROGRESS; gpencil_primitive_interaction_begin(tgpi, event); } + else if ((event->val == KM_RELEASE) && (tgpi->flag == IN_MOVE)) { + tgpi->flag = IN_CURVE_EDIT; + } else if ((event->val == KM_RELEASE) && (tgpi->flag == IN_PROGRESS)) { + /* set control points and enter edit mode */ + tgpi->flag = IN_CURVE_EDIT; + gp_primitive_update_cps(tgpi); + gpencil_primitive_update(C, op, tgpi); + } + else if ((event->val == KM_RELEASE) && (tgpi->flag == IN_PROGRESS) && (tgpi->type != GP_STROKE_CURVE)) { /* stop drawing primitive */ tgpi->flag = IDLE; gpencil_primitive_interaction_end(C, op, win, tgpi); @@ -682,15 +1349,25 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e } } break; - case RETKEY: /* confirm */ + } + case SPACEKEY: /* confirm */ + case RETKEY: { tgpi->flag = IDLE; gpencil_primitive_interaction_end(C, op, win, tgpi); /* done! */ return OPERATOR_FINISHED; } - case ESCKEY: /* cancel */ case RIGHTMOUSE: + { + if (tgpi->flag == IN_CURVE_EDIT || (tgpi->flag == IN_PROGRESS && tgpi->tot_stored_edges > 0)) { + tgpi->flag = IDLE; + gpencil_primitive_interaction_end(C, op, win, tgpi); + /* done! */ + return OPERATOR_FINISHED; + } + } + case ESCKEY: { /* return to normal cursor and header status */ ED_workspace_status_text(C, NULL); @@ -702,30 +1379,10 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e /* canceled! */ return OPERATOR_CANCELLED; } - case CKEY: - { - if ((event->val == KM_RELEASE) && tgpi->type == GP_STROKE_ARC) { - tgpi->cyclic ^= 1; - - /* update screen */ - gpencil_primitive_update(C, op, tgpi); - } - break; - } - case FKEY: - { - if ((event->val == KM_RELEASE) && tgpi->type == GP_STROKE_ARC) { - tgpi->flip ^= 1; - - /* update screen */ - gpencil_primitive_update(C, op, tgpi); - } - break; - } case PADPLUSKEY: case WHEELUPMOUSE: { - if ((event->val != KM_RELEASE) && (tgpi->type == GP_STROKE_CIRCLE || tgpi->type == GP_STROKE_ARC)) { + if ((event->val != KM_RELEASE)) { tgpi->tot_edges = tgpi->tot_edges + 1; CLAMP(tgpi->tot_edges, MIN_EDGES, MAX_EDGES); RNA_int_set(op->ptr, "edges", tgpi->tot_edges); @@ -738,7 +1395,7 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e case PADMINUS: case WHEELDOWNMOUSE: { - if ((event->val != KM_RELEASE) && (tgpi->type == GP_STROKE_CIRCLE || tgpi->type == GP_STROKE_ARC)) { + if ((event->val != KM_RELEASE)) { tgpi->tot_edges = tgpi->tot_edges - 1; CLAMP(tgpi->tot_edges, MIN_EDGES, MAX_EDGES); RNA_int_set(op->ptr, "edges", tgpi->tot_edges); @@ -748,30 +1405,69 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e } break; } + case GKEY: /* grab mode */ + { + if ((event->val == KM_PRESS)) { + tgpi->flag = IN_MOVE; + WM_cursor_modal_set(win, BC_NSEW_SCROLLCURSOR); + } + break; + } + case CKEY: /* curve mode */ + { + if ((event->val == KM_PRESS) && + (tgpi->orign_type == GP_STROKE_CURVE)) + { + switch (tgpi->type) { + case GP_STROKE_CURVE: + tgpi->type = GP_STROKE_ARC; + break; + default: + case GP_STROKE_ARC: + tgpi->type = GP_STROKE_CURVE; + break; + } + + RNA_enum_set(op->ptr, "type", tgpi->type); + gp_primitive_update_cps(tgpi); + gpencil_primitive_update(C, op, tgpi); + } + break; + } + case TABKEY: + { + if (tgpi->flag == IN_CURVE_EDIT) { + tgpi->flag = IN_PROGRESS; + WM_cursor_modal_set(win, BC_NSEW_SCROLLCURSOR); + gp_primitive_update_cps(tgpi); + gpencil_primitive_update(C, op, tgpi); + } + break; + } case MOUSEMOVE: /* calculate new position */ { + if (tgpi->flag == IN_CURVE_EDIT) { + break; + } /* only handle mousemove if not doing numinput */ if (has_numinput == false) { /* update position of mouse */ - tgpi->bottom[0] = event->mval[0]; - tgpi->bottom[1] = event->mval[1]; - tgpi->top[0] = tgpi->origin[0]; - tgpi->top[1] = tgpi->origin[1]; + copy_v2_v2(tgpi->end, tgpi->mval); + copy_v2_v2(tgpi->start, tgpi->origin); if (tgpi->flag == IDLE) { - tgpi->origin[0] = event->mval[0]; - tgpi->origin[1] = event->mval[1]; + copy_v2_v2(tgpi->origin, tgpi->mval); } /* Keep square if shift key */ if (event->shift) { - int x = tgpi->bottom[0] - tgpi->origin[0]; - int y = tgpi->bottom[1] - tgpi->origin[1]; - if (tgpi->type == GP_STROKE_LINE) { - float angle = fabsf(atan2f((float)y, (float)x)); - if (angle < 0.4f || angle > (M_PI - 0.4f)) { - tgpi->bottom[1] = tgpi->origin[1]; + float x = tgpi->end[0] - tgpi->origin[0]; + float y = tgpi->end[1] - tgpi->origin[1]; + if (tgpi->type == GP_STROKE_LINE || tgpi->curve) { + float angle = fabsf(atan2f(y, x)); + if (angle < 0.4f || angle >(M_PI - 0.4f)) { + tgpi->end[1] = tgpi->origin[1]; } else if (angle > (M_PI_2 - 0.4f) && angle < (M_PI_2 + 0.4f)) { - tgpi->bottom[0] = tgpi->origin[0]; + tgpi->end[0] = tgpi->origin[0]; } else { gpencil_primitive_to_square(tgpi, x, y); @@ -783,9 +1479,10 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e } /* Center primitive if alt key */ if (event->alt) { - tgpi->top[0] = tgpi->origin[0] - (tgpi->bottom[0] - tgpi->origin[0]); - tgpi->top[1] = tgpi->origin[1] - (tgpi->bottom[1] - tgpi->origin[1]); + tgpi->start[0] = tgpi->origin[0] - (tgpi->end[0] - tgpi->origin[0]); + tgpi->start[1] = tgpi->origin[1] - (tgpi->end[1] - tgpi->origin[1]); } + gp_primitive_update_cps(tgpi); /* update screen */ gpencil_primitive_update(C, op, tgpi); } @@ -793,7 +1490,7 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e } default: { - if ((event->val == KM_PRESS) && handleNumInput(C, &tgpi->num, event)) { + if (tgpi->flag != IN_CURVE_EDIT && (event->val == KM_PRESS) && handleNumInput(C, &tgpi->num, event)) { float value; /* Grab data from numeric input, and store this new value (the user see an int) */ @@ -816,6 +1513,7 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e } } + copy_v2_v2(tgpi->mvalo, tgpi->mval); /* still running... */ return OPERATOR_RUNNING_MODAL; } @@ -834,6 +1532,7 @@ void GPENCIL_OT_primitive(wmOperatorType *ot) {GP_STROKE_LINE, "LINE", 0, "Line", ""}, {GP_STROKE_CIRCLE, "CIRCLE", 0, "Circle", ""}, {GP_STROKE_ARC, "ARC", 0, "Arc", ""}, + {GP_STROKE_CURVE, "CURVE", 0, "Curve", ""}, {0, NULL, 0, NULL, NULL} }; @@ -854,11 +1553,11 @@ void GPENCIL_OT_primitive(wmOperatorType *ot) /* properties */ PropertyRNA *prop; - RNA_def_int(ot->srna, "edges", 4, MIN_EDGES, MAX_EDGES, "Edges", "Number of polygon edges", MIN_EDGES, MAX_EDGES); + prop = RNA_def_int(ot->srna, "edges", 4, MIN_EDGES, MAX_EDGES, "Edges", "Number of polygon edges", MIN_EDGES, MAX_EDGES); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + RNA_def_enum(ot->srna, "type", primitive_type, GP_STROKE_BOX, "Type", "Type of shape"); 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_color_types.h b/source/blender/makesdna/DNA_color_types.h index 01f0b06c178..bf205729a72 100644 --- a/source/blender/makesdna/DNA_color_types.h +++ b/source/blender/makesdna/DNA_color_types.h @@ -103,6 +103,7 @@ typedef enum eCurveMappingPreset { CURVE_PRESET_ROUND = 5, CURVE_PRESET_ROOT = 6, CURVE_PRESET_GAUSS = 7, + CURVE_PRESET_BELL = 8, } eCurveMappingPreset; /* CurveMapping->tone */ diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 43ce3649db3..934ef4a4829 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -48,6 +48,13 @@ struct MDeformVert; /* ***************************************** */ /* GP Stroke Points */ +/* 'Control Point' data for primitives and curves */ +typedef struct bGPDcontrolpoint { + float x, y, z; /* x and y coordinates of control point */ + float color[4]; /* point color */ + int size; /* radius */ +} bGPDcontrolpoint; + /* Grease-Pencil Annotations - 'Stroke Point' * -> Coordinates may either be 2d or 3d depending on settings at the time * -> Coordinates of point on stroke, in proportions of window size @@ -345,6 +352,10 @@ typedef struct bGPdata_Runtime { short sbuffer_size; /* number of elements currently in cache */ short sbuffer_sflag; /* flags for stroke that cache represents */ char pad_[6]; + + int tot_cp_points; /* number of control-points for stroke */ + char pad1_[4]; + bGPDcontrolpoint *cp_points; /* array of control-points for stroke */ } bGPdata_Runtime; /* grid configuration */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index c1853ce1745..b06ab596059 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1035,6 +1035,7 @@ typedef struct GP_Sculpt_Settings { int weighttype; /* eGP_Sculpt_Types (weight paint) */ char pad[4]; struct CurveMapping *cur_falloff; /* multiframe edit falloff effect by frame */ + struct CurveMapping *cur_primitive; /* Curve used for primitve tools */ } GP_Sculpt_Settings; /* GP_Sculpt_Settings.flag */ @@ -1053,6 +1054,8 @@ typedef enum eGP_Sculpt_SettingsFlag { GP_SCULPT_SETT_FLAG_FRAME_FALLOFF = (1 << 5), /* apply brush to uv data */ GP_SCULPT_SETT_FLAG_APPLY_UV = (1 << 6), + /* apply primitve curve */ + GP_SCULPT_SETT_FLAG_PRIMITIVE_CURVE = (1 << 7), } eGP_Sculpt_SettingsFlag; /* Settings for GP Interpolation Operators */ diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 8ee542fcc51..1086abd0c6f 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -69,11 +69,11 @@ const EnumPropertyItem rna_enum_gpencil_sculpt_brush_items[] = { {GP_SCULPT_TYPE_SMOOTH, "SMOOTH", ICON_GPBRUSH_SMOOTH, "Smooth", "Smooth stroke points"}, {GP_SCULPT_TYPE_THICKNESS, "THICKNESS", ICON_GPBRUSH_THICKNESS, "Thickness", "Adjust thickness of strokes"}, {GP_SCULPT_TYPE_STRENGTH, "STRENGTH", ICON_GPBRUSH_STRENGTH, "Strength", "Adjust color strength of strokes" }, + {GP_SCULPT_TYPE_RANDOMIZE, "RANDOMIZE", ICON_GPBRUSH_RANDOMIZE, "Randomize", "Introduce jitter/randomness into strokes"}, {GP_SCULPT_TYPE_GRAB, "GRAB", ICON_GPBRUSH_GRAB, "Grab", "Translate the set of points initially within the brush circle" }, {GP_SCULPT_TYPE_PUSH, "PUSH", ICON_GPBRUSH_PUSH, "Push", "Move points out of the way, as if combing them"}, {GP_SCULPT_TYPE_TWIST, "TWIST", ICON_GPBRUSH_TWIST, "Twist", "Rotate points around the midpoint of the brush"}, {GP_SCULPT_TYPE_PINCH, "PINCH", ICON_GPBRUSH_PINCH, "Pinch", "Pull points towards the midpoint of the brush"}, - {GP_SCULPT_TYPE_RANDOMIZE, "RANDOMIZE", ICON_GPBRUSH_RANDOMIZE, "Randomize", "Introduce jitter/randomness into strokes"}, {GP_SCULPT_TYPE_CLONE, "CLONE", ICON_GPBRUSH_CLONE, "Clone", "Paste copies of the strokes stored on the clipboard"}, { 0, NULL, 0, NULL, NULL } }; @@ -1250,6 +1250,12 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna) RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + prop = RNA_def_property(srna, "use_thickness_curve", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_SCULPT_SETT_FLAG_PRIMITIVE_CURVE); + RNA_def_property_ui_text(prop, "Use Curve", "Use curve to define primitive stroke thickness"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + /* custom falloff curve */ prop = RNA_def_property(srna, "multiframe_falloff_curve", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "cur_falloff"); @@ -1259,6 +1265,15 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna) RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + /* custom primitive curve */ + prop = RNA_def_property(srna, "thickness_primitive_curve", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "cur_primitive"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Curve", + "Custom curve to control primitive thickness"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + /* lock axis */ prop = RNA_def_property(srna, "lock_axis", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "lock_axis");