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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/gpencil')
-rw-r--r--source/blender/editors/gpencil/CMakeLists.txt1
-rw-r--r--source/blender/editors/gpencil/annotate_paint.c10
-rw-r--r--source/blender/editors/gpencil/gpencil_add_monkey.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_add_stroke.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_armature.c13
-rw-r--r--source/blender/editors/gpencil/gpencil_bake_animation.cc2
-rw-r--r--source/blender/editors/gpencil/gpencil_convert.c1
-rw-r--r--source/blender/editors/gpencil/gpencil_data.c7
-rw-r--r--source/blender/editors/gpencil/gpencil_edit.c422
-rw-r--r--source/blender/editors/gpencil/gpencil_fill.c617
-rw-r--r--source/blender/editors/gpencil/gpencil_intern.h2
-rw-r--r--source/blender/editors/gpencil/gpencil_interpolate.c13
-rw-r--r--source/blender/editors/gpencil/gpencil_mesh.cc4
-rw-r--r--source/blender/editors/gpencil/gpencil_ops.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_ops_versioning.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c101
-rw-r--r--source/blender/editors/gpencil/gpencil_primitive.c6
-rw-r--r--source/blender/editors/gpencil/gpencil_sculpt_paint.c14
-rw-r--r--source/blender/editors/gpencil/gpencil_select.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_trace_ops.c31
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c19
-rw-r--r--source/blender/editors/gpencil/gpencil_vertex_ops.c2
22 files changed, 1150 insertions, 125 deletions
diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt
index 9cb9e7ca1af..866df16f3d6 100644
--- a/source/blender/editors/gpencil/CMakeLists.txt
+++ b/source/blender/editors/gpencil/CMakeLists.txt
@@ -12,7 +12,6 @@ set(INC
../../makesdna
../../makesrna
../../windowmanager
- ../../../../intern/glew-mx
../../../../intern/guardedalloc
../../bmesh
# RNA_prototypes.h
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c
index d08d56a354a..ea34e6530fa 100644
--- a/source/blender/editors/gpencil/annotate_paint.c
+++ b/source/blender/editors/gpencil/annotate_paint.c
@@ -1715,7 +1715,7 @@ static void annotation_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_pt
if (p->paintmode == GP_PAINTMODE_ERASER) {
GPUVertFormat *format = immVertexFormat();
const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
@@ -1725,7 +1725,7 @@ static void annotation_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_pt
immUnbindProgram();
- immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
@@ -1782,7 +1782,7 @@ static void annotation_draw_stabilizer(bContext *C, int x, int y, void *p_ptr)
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
GPU_line_width(1.25f);
@@ -2346,7 +2346,7 @@ static int annotation_draw_invoke(bContext *C, wmOperator *op, const wmEvent *ev
return OPERATOR_RUNNING_MODAL;
}
-/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
+/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
static bool annotation_area_exists(bContext *C, ScrArea *area_test)
{
bScreen *screen = CTX_wm_screen(C);
@@ -2698,7 +2698,7 @@ static int annotation_draw_modal(bContext *C, wmOperator *op, const wmEvent *eve
}
}
- /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
+ /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
if (0 == annotation_area_exists(C, p->area)) {
estate = OPERATOR_CANCELLED;
}
diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c
index ce38c261c1f..00066d5f2b8 100644
--- a/source/blender/editors/gpencil/gpencil_add_monkey.c
+++ b/source/blender/editors/gpencil/gpencil_add_monkey.c
@@ -823,6 +823,8 @@ static const ColorTemplate gp_monkey_pct_pupils = {
void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4])
{
+ /* Original model created by Matias Mendiola. */
+
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
bGPdata *gpd = (bGPdata *)ob->data;
diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c
index 4687f9188fd..8522c81cb39 100644
--- a/source/blender/editors/gpencil/gpencil_add_stroke.c
+++ b/source/blender/editors/gpencil/gpencil_add_stroke.c
@@ -192,6 +192,8 @@ static const ColorTemplate gp_stroke_material_grey = {
void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4])
{
+ /* Original design created by Daniel M. Lara and Matias Mendiola. */
+
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
bGPdata *gpd = (bGPdata *)ob->data;
diff --git a/source/blender/editors/gpencil/gpencil_armature.c b/source/blender/editors/gpencil/gpencil_armature.c
index d389f7eb5dd..5f5a4b41b27 100644
--- a/source/blender/editors/gpencil/gpencil_armature.c
+++ b/source/blender/editors/gpencil/gpencil_armature.c
@@ -29,6 +29,7 @@
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_modifier.h"
+#include "BKE_layer.h"
#include "BKE_main.h"
#include "BKE_object_deform.h"
#include "BKE_report.h"
@@ -528,6 +529,7 @@ static bool gpencil_generate_weights_poll(bContext *C)
return false;
}
+ Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
bGPdata *gpd = (bGPdata *)ob->data;
@@ -536,7 +538,8 @@ static bool gpencil_generate_weights_poll(bContext *C)
}
/* need some armature in the view layer */
- LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
+ BKE_view_layer_synced_ensure(scene, view_layer);
+ LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
if (base->object->type == OB_ARMATURE) {
return true;
}
@@ -548,6 +551,7 @@ static bool gpencil_generate_weights_poll(bContext *C)
static int gpencil_generate_weights_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *ob = CTX_data_active_object(C);
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
@@ -566,7 +570,8 @@ static int gpencil_generate_weights_exec(bContext *C, wmOperator *op)
/* get armature */
const int arm_idx = RNA_enum_get(op->ptr, "armature");
if (arm_idx > 0) {
- Base *base = BLI_findlink(&view_layer->object_bases, arm_idx - 1);
+ BKE_view_layer_synced_ensure(scene, view_layer);
+ Base *base = BLI_findlink(BKE_view_layer_object_bases_get(view_layer), arm_idx - 1);
ob_arm = base->object;
}
else {
@@ -607,6 +612,7 @@ static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C,
PropertyRNA *UNUSED(prop),
bool *r_free)
{
+ Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
EnumPropertyItem *item = NULL, item_tmp = {0};
int totitem = 0;
@@ -623,7 +629,8 @@ static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C,
RNA_enum_item_add(&item, &totitem, &item_tmp);
i++;
- LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
+ BKE_view_layer_synced_ensure(scene, view_layer);
+ LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
Object *ob = base->object;
if (ob->type == OB_ARMATURE) {
item_tmp.identifier = item_tmp.name = ob->id.name + 2;
diff --git a/source/blender/editors/gpencil/gpencil_bake_animation.cc b/source/blender/editors/gpencil/gpencil_bake_animation.cc
index e480852a9bb..28d4e1c6d42 100644
--- a/source/blender/editors/gpencil/gpencil_bake_animation.cc
+++ b/source/blender/editors/gpencil/gpencil_bake_animation.cc
@@ -327,7 +327,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op
/* Reproject stroke. */
if (project_type != GP_REPROJECT_KEEP) {
ED_gpencil_stroke_reproject(
- depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false);
+ depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false, 0.0f);
}
else {
BKE_gpencil_stroke_geometry_update(gpd_dst, gps);
diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c
index e02a82f4555..bf78111a636 100644
--- a/source/blender/editors/gpencil/gpencil_convert.c
+++ b/source/blender/editors/gpencil/gpencil_convert.c
@@ -1303,6 +1303,7 @@ static void gpencil_layer_to_curve(bContext *C,
ob = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, gpl->info);
cu = ob->data = BKE_curve_add(bmain, gpl->info, OB_CURVES_LEGACY);
BKE_collection_object_add(bmain, collection, ob);
+ BKE_view_layer_synced_ensure(scene, view_layer);
base_new = BKE_view_layer_base_find(view_layer, ob);
DEG_relations_tag_update(bmain); /* added object */
diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c
index b7ac73b9692..340288b2d74 100644
--- a/source/blender/editors/gpencil/gpencil_data.c
+++ b/source/blender/editors/gpencil/gpencil_data.c
@@ -2076,6 +2076,9 @@ static void gpencil_brush_delete_mode_brushes(Main *bmain,
}
BKE_brush_delete(bmain, brush);
+ if (brush == brush_active) {
+ brush_active = NULL;
+ }
}
}
@@ -2109,8 +2112,8 @@ static int gpencil_brush_reset_all_exec(bContext *C, wmOperator *UNUSED(op))
char tool = '0';
if (paint) {
- Brush *brush_active = paint->brush;
- if (brush_active) {
+ if (paint->brush) {
+ Brush *brush_active = paint->brush;
switch (mode) {
case CTX_MODE_PAINT_GPENCIL: {
tool = brush_active->gpencil_tool;
diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c
index c05ab8c6b28..837a9390b6c 100644
--- a/source/blender/editors/gpencil/gpencil_edit.c
+++ b/source/blender/editors/gpencil/gpencil_edit.c
@@ -67,6 +67,7 @@
#include "ED_armature.h"
#include "ED_gpencil.h"
+#include "ED_keyframing.h"
#include "ED_object.h"
#include "ED_outliner.h"
#include "ED_screen.h"
@@ -1713,12 +1714,17 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op)
}
}
- /* Ensure we have a frame to draw into
+ /* Ensure we have a frame to draw into.
* NOTE: Since this is an op which creates strokes,
- * we are obliged to add a new frame if one
- * doesn't exist already
+ * we reuse active frame or add a new frame if one
+ * doesn't exist already depending on REC button status.
*/
- gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW);
+ if (IS_AUTOKEY_ON(scene) || (gpl->actframe == NULL)) {
+ gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW);
+ }
+ else {
+ gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV);
+ }
if (gpf) {
/* Create new stroke */
bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true);
@@ -3650,7 +3656,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op)
}
elem = &strokes_list[i];
/* Join new_stroke and stroke B. */
- BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false);
+ BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false, true);
elem->used = true;
}
@@ -3791,6 +3797,101 @@ void GPENCIL_OT_stroke_flip(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Stroke Start Set Operator
+ * \{ */
+
+static int gpencil_stroke_start_set_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ bGPdata *gpd = ob->data;
+
+ /* sanity checks */
+ if (ELEM(NULL, ob, gpd)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
+ const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd);
+ if (is_curve_edit) {
+ BKE_report(op->reports, RPT_ERROR, "Curve Edit mode not supported");
+ return OPERATOR_CANCELLED;
+ }
+
+ bool changed = false;
+ /* Read all selected strokes. */
+ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
+ bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
+
+ for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
+ if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
+ if (gpf == NULL) {
+ continue;
+ }
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->flag & GP_STROKE_SELECT) {
+ /* skip strokes that are invalid for current view */
+ if (ED_gpencil_stroke_can_use(C, gps) == false) {
+ continue;
+ }
+ /* check if the color is editable */
+ if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) {
+ continue;
+ }
+
+ /* Only cyclic strokes. */
+ if ((gps->flag & GP_STROKE_CYCLIC) == 0) {
+ continue;
+ }
+
+ /* Find first selected point and set start. */
+ bGPDspoint *pt;
+ for (int i = 0; i < gps->totpoints; i++) {
+ pt = &gps->points[i];
+ if (pt->flag & GP_SPOINT_SELECT) {
+ BKE_gpencil_stroke_start_set(gps, i);
+ BKE_gpencil_stroke_geometry_update(gpd, gps);
+ changed = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ /* If not multi-edit, exit loop. */
+ if (!is_multiedit) {
+ break;
+ }
+ }
+ }
+ CTX_DATA_END;
+
+ if (changed) {
+ /* notifiers */
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_stroke_start_set(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Set Start Point";
+ ot->idname = "GPENCIL_OT_stroke_start_set";
+ ot->description = "Set start point for cyclic strokes";
+
+ /* api callbacks */
+ ot->exec = gpencil_stroke_start_set_exec;
+ ot->poll = gpencil_active_layer_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Stroke Re-project Operator
* \{ */
@@ -3804,6 +3905,7 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op)
const bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd);
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
+ const float offset = RNA_float_get(op->ptr, "offset");
/* Init snap context for geometry projection. */
SnapObjectContext *sctx = NULL;
@@ -3843,7 +3945,8 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op)
BKE_scene_graph_update_for_newframe(depsgraph);
}
- ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original);
+ ED_gpencil_stroke_reproject(
+ depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original, offset);
if (is_curve_edit && gps->editcurve != NULL) {
BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps);
@@ -3883,8 +3986,29 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
+static void gpencil_strokes_reproject_ui(bContext *UNUSED(C), wmOperator *op)
+{
+ uiLayout *layout = op->layout;
+ uiLayout *row;
+
+ const eGP_ReprojectModes type = RNA_enum_get(op->ptr, "type");
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+ row = uiLayoutRow(layout, true);
+ uiItemR(row, op->ptr, "type", 0, NULL, ICON_NONE);
+
+ if (type == GP_REPROJECT_SURFACE) {
+ row = uiLayoutRow(layout, true);
+ uiItemR(row, op->ptr, "offset", 0, NULL, ICON_NONE);
+ }
+ row = uiLayoutRow(layout, true);
+ uiItemR(row, op->ptr, "keep_original", 0, NULL, ICON_NONE);
+}
+
void GPENCIL_OT_reproject(wmOperatorType *ot)
{
+ PropertyRNA *prop;
static const EnumPropertyItem reproject_type[] = {
{GP_REPROJECT_FRONT, "FRONT", 0, "Front", "Reproject the strokes using the X-Z plane"},
{GP_REPROJECT_SIDE, "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"},
@@ -3922,6 +4046,7 @@ void GPENCIL_OT_reproject(wmOperatorType *ot)
ot->invoke = WM_menu_invoke;
ot->exec = gpencil_strokes_reproject_exec;
ot->poll = gpencil_strokes_edit3d_poll;
+ ot->ui = gpencil_strokes_reproject_ui;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@@ -3930,12 +4055,15 @@ void GPENCIL_OT_reproject(wmOperatorType *ot)
ot->prop = RNA_def_enum(
ot->srna, "type", reproject_type, GP_REPROJECT_VIEW, "Projection Type", "");
- RNA_def_boolean(
+ prop = RNA_def_boolean(
ot->srna,
"keep_original",
0,
"Keep Original",
"Keep original strokes and create a copy before reprojecting instead of reproject them");
+ RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MOVIECLIP);
+
+ RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f);
}
static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op))
@@ -3960,6 +4088,284 @@ static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op))
return OPERATOR_FINISHED;
}
+/* -------------------------------------------------------------------- */
+/** \name Stroke Perimeter from View Operator
+ * \{ */
+
+enum {
+ GP_PERIMETER_VIEW = 0,
+ GP_PERIMETER_FRONT = 1,
+ GP_PERIMETER_SIDE = 2,
+ GP_PERIMETER_TOP = 3,
+ GP_PERIMETER_CAMERA = 4,
+};
+
+enum {
+ GP_STROKE_USE_ACTIVE_MATERIAL = 0,
+ GP_STROKE_USE_CURRENT_MATERIAL = 1,
+ GP_STROKE_USE_NEW_MATERIAL = 2,
+};
+
+static int gpencil_stroke_outline_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ RegionView3D *rv3d = CTX_wm_region_view3d(C);
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Object *ob = CTX_data_active_object(C);
+ bGPdata *gpd = (bGPdata *)ob->data;
+ const int subdivisions = RNA_int_get(op->ptr, "subdivisions");
+ const float length = RNA_float_get(op->ptr, "length");
+ const bool keep = RNA_boolean_get(op->ptr, "keep");
+ const int thickness = RNA_int_get(op->ptr, "thickness");
+
+ const int view_mode = RNA_enum_get(op->ptr, "view_mode");
+ const int material_mode = RNA_enum_get(op->ptr, "material_mode");
+ const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
+
+ /* sanity checks */
+ if (ELEM(NULL, gpd)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ bool changed = false;
+
+ float viewmat[4][4];
+ copy_m4_m4(viewmat, rv3d->viewmat);
+
+ switch (view_mode) {
+ case GP_PERIMETER_FRONT:
+ unit_m4(rv3d->viewmat);
+ viewmat[1][1] = 0.0f;
+ viewmat[1][2] = -1.0f;
+
+ viewmat[2][1] = 1.0f;
+ viewmat[2][2] = 0.0f;
+
+ viewmat[3][2] = -10.0f;
+ break;
+ case GP_PERIMETER_SIDE:
+ zero_m4(viewmat);
+ viewmat[0][2] = 1.0f;
+ viewmat[1][0] = 1.0f;
+ viewmat[2][1] = 1.0f;
+ viewmat[3][3] = 1.0f;
+ break;
+ case GP_PERIMETER_TOP:
+ unit_m4(viewmat);
+ break;
+ case GP_PERIMETER_CAMERA: {
+ Scene *scene = CTX_data_scene(C);
+ Object *cam_ob = scene->camera;
+ if (cam_ob != NULL) {
+ invert_m4_m4(viewmat, cam_ob->obmat);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ /* Untag strokes to be sure nothing is pending. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ gps->flag &= ~GP_STROKE_TAG;
+ }
+ }
+ }
+ /* Create a new material. */
+ int mat_idx = 0;
+ if (material_mode == GP_STROKE_USE_NEW_MATERIAL) {
+ Material *ma = BKE_gpencil_object_material_new(bmain, ob, "Material", NULL);
+ MaterialGPencilStyle *gp_style = ma->gp_style;
+
+ gp_style->flag |= GP_MATERIAL_FILL_SHOW;
+ mat_idx = ob->totcol - 1;
+ }
+
+ /* loop all selected strokes */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ /* Prepare transform matrix. */
+ float diff_mat[4][4];
+ BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat);
+
+ bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
+
+ for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
+ if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
+ if (gpf == NULL) {
+ continue;
+ }
+
+ for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
+ if ((gps->flag & GP_STROKE_SELECT) == 0) {
+ continue;
+ }
+ if (gps->totpoints == 0) {
+ continue;
+ }
+ if (!ED_gpencil_stroke_material_visible(ob, gps)) {
+ continue;
+ }
+
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
+ const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0);
+
+ if (!is_stroke) {
+ continue;
+ }
+
+ /* Duplicate the stroke to apply any layer thickness change. */
+ bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
+
+ /* Apply layer thickness change. */
+ gps_duplicate->thickness += gpl->line_change;
+ /* Apply object scale to thickness. */
+ gps_duplicate->thickness *= mat4_to_scale(ob->obmat);
+ CLAMP_MIN(gps_duplicate->thickness, 1.0f);
+
+ /* Stroke. */
+ const float ovr_thickness = keep ? thickness : 0.0f;
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ viewmat, gpd, gpl, gps_duplicate, subdivisions, diff_mat, ovr_thickness);
+ gps_perimeter->flag &= ~GP_STROKE_SELECT;
+ /* Assign material. */
+ switch (material_mode) {
+ case GP_STROKE_USE_ACTIVE_MATERIAL: {
+ if (ob->actcol - 1 < 0) {
+ gps_perimeter->mat_nr = 0;
+ }
+ else {
+ gps_perimeter->mat_nr = ob->actcol - 1;
+ }
+ break;
+ }
+ case GP_STROKE_USE_CURRENT_MATERIAL:
+ gps_perimeter->mat_nr = gps->mat_nr;
+ break;
+ case GP_STROKE_USE_NEW_MATERIAL:
+ gps_perimeter->mat_nr = mat_idx;
+ break;
+ default:
+ break;
+ }
+
+ /* Sample stroke. */
+ if (length > 0.0f) {
+ BKE_gpencil_stroke_sample(gpd, gps_perimeter, length, false, 0);
+ }
+ /* Set stroke thickness. */
+ gps_perimeter->thickness = thickness;
+
+ /* Set pressure constant. */
+ bGPDspoint *pt;
+ for (int i = 0; i < gps_perimeter->totpoints; i++) {
+ pt = &gps_perimeter->points[i];
+ pt->pressure = 1.0f;
+ }
+
+ /* Add perimeter stroke to frame. */
+ BLI_insertlinkafter(&gpf->strokes, gps, gps_perimeter);
+
+ /* Tag original stroke to be removed. */
+ gps->flag |= GP_STROKE_TAG;
+
+ /* Free Temp stroke. */
+ BKE_gpencil_free_stroke(gps_duplicate);
+ changed = true;
+ }
+
+ /* If not multi-edit, exit loop. */
+ if (!is_multiedit) {
+ break;
+ }
+ }
+ }
+ }
+ /* Free old strokes. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->flag & GP_STROKE_TAG) {
+ BLI_remlink(&gpf->strokes, gps);
+ BKE_gpencil_free_stroke(gps);
+ }
+ }
+ }
+ }
+
+ if (changed) {
+ /* notifiers */
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_stroke_outline(wmOperatorType *ot)
+{
+ static const EnumPropertyItem view_mode[] = {
+ {GP_PERIMETER_VIEW, "VIEW", 0, "View", ""},
+ {GP_PERIMETER_FRONT, "FRONT", 0, "Front", ""},
+ {GP_PERIMETER_SIDE, "SIDE", 0, "Side", ""},
+ {GP_PERIMETER_TOP, "TOP", 0, "Top", ""},
+ {GP_PERIMETER_CAMERA, "CAMERA", 0, "Camera", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+ static const EnumPropertyItem material_mode[] = {
+ {GP_STROKE_USE_ACTIVE_MATERIAL, "ACTIVE", 0, "Active Material", ""},
+ {GP_STROKE_USE_CURRENT_MATERIAL, "KEEP", 0, "Keep Material", "Keep current stroke material"},
+ {GP_STROKE_USE_NEW_MATERIAL, "NEW", 0, "New Material", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ /* identifiers */
+ ot->name = "Convert Stroke to Outline";
+ ot->idname = "GPENCIL_OT_stroke_outline";
+ ot->description = "Convert stroke to perimeter";
+
+ /* api callbacks */
+ ot->exec = gpencil_stroke_outline_exec;
+ ot->poll = gpencil_stroke_edit_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ ot->prop = RNA_def_enum(ot->srna, "view_mode", view_mode, GP_PERIMETER_VIEW, "View", "");
+ RNA_def_enum(ot->srna,
+ "material_mode",
+ material_mode,
+ GP_STROKE_USE_ACTIVE_MATERIAL,
+ "Material Mode",
+ "");
+
+ RNA_def_int(ot->srna,
+ "thickness",
+ 1,
+ 1,
+ 1000,
+ "Thickness",
+ "Thickness of the stroke perimeter",
+ 1,
+ 1000);
+ RNA_def_boolean(ot->srna,
+ "keep",
+ true,
+ "Keep Shape",
+ "Try to keep global shape when the stroke thickness change");
+
+ RNA_def_int(ot->srna, "subdivisions", 3, 0, 10, "Subdivisions", "", 0, 10);
+
+ RNA_def_float(ot->srna, "length", 0.0f, 0.0f, 100.0f, "Sample Length", "", 0.0f, 100.0f);
+}
+
+/** \} */
+
void GPENCIL_OT_recalc_geometry(wmOperatorType *ot)
{
/* identifiers */
@@ -4483,6 +4889,8 @@ void GPENCIL_OT_stroke_sample(wmOperatorType *ot)
/* properties */
prop = RNA_def_float(ot->srna, "length", 0.1f, 0.0f, 100.0f, "Length", "", 0.0f, 100.0f);
+ prop = RNA_def_float(
+ ot->srna, "sharp_threshold", 0.1f, 0.0f, M_PI, "Sharp Threshold", "", 0.0f, M_PI);
/* avoid re-using last var */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 5305c764b3a..c173a30a736 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -67,6 +67,7 @@
#define LEAK_HORZ 0
#define LEAK_VERT 1
+#define FILL_LEAK 3.0f
#define MIN_WINDOW_SIZE 128
/* Set to 1 to debug filling internal image. By default, the value must be 0. */
@@ -78,6 +79,20 @@ enum {
GP_DRAWFILLS_ONLY3D = (1 << 1), /* only draw 3d-strokes */
};
+/* Temporary stroke data including stroke extensions. */
+typedef struct tStroke {
+ /* Referenced layer. */
+ bGPDlayer *gpl;
+ /** Referenced frame. */
+ bGPDframe *gpf;
+ /** Referenced stroke. */
+ bGPDstroke *gps;
+ /** Extreme Stroke A. */
+ bGPDstroke *gps_ext_a;
+ /** Extreme Stroke B. */
+ bGPDstroke *gps_ext_b;
+} tStroke;
+
/* Temporary fill operation data `op->customdata`. */
typedef struct tGPDfill {
bContext *C;
@@ -114,7 +129,7 @@ typedef struct tGPDfill {
/** For operations that require occlusion testing. */
struct ViewDepths *depths;
/** flags */
- short flag;
+ int flag;
/** avoid too fast events */
short oldkey;
/** send to back stroke */
@@ -140,6 +155,8 @@ typedef struct tGPDfill {
int fill_simplylvl;
/** boundary limits drawing mode */
int fill_draw_mode;
+ /** types of extensions **/
+ int fill_extend_mode;
/* scaling factor */
float fill_factor;
@@ -157,7 +174,7 @@ typedef struct tGPDfill {
Image *ima;
/** temp points data */
BLI_Stack *stack;
- /** handle for drawing strokes while operator is running 3d stuff */
+ /** handle for drawing strokes while operator is running 3d stuff. */
void *draw_handle_3d;
/* Temporary size x. */
@@ -174,12 +191,28 @@ typedef struct tGPDfill {
/** Factor of extension. */
float fill_extend_fac;
-
+ /** Size of stroke_array. */
+ int stroke_array_num;
+ /** Temp strokes array to handle strokes and stroke extensions. */
+ tStroke **stroke_array;
} tGPDfill;
bool skip_layer_check(short fill_layer_mode, int gpl_active_index, int gpl_index);
static void gpencil_draw_boundary_lines(const struct bContext *UNUSED(C), struct tGPDfill *tgpf);
+/* Free temp stroke array. */
+static void stroke_array_free(tGPDfill *tgpf)
+{
+ if (tgpf->stroke_array) {
+ for (int i = 0; i < tgpf->stroke_array_num; i++) {
+ tStroke *stroke = tgpf->stroke_array[i];
+ MEM_freeN(stroke);
+ }
+ MEM_SAFE_FREE(tgpf->stroke_array);
+ }
+ tgpf->stroke_array_num = 0;
+}
+
/* Delete any temporary stroke. */
static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_frames)
{
@@ -197,7 +230,8 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
/* free stroke */
- if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) {
+ if ((gps->flag & GP_STROKE_NOFILL) &&
+ (gps->flag & GP_STROKE_TAG || gps->flag & GP_STROKE_HELP)) {
BLI_remlink(&gpf->strokes, gps);
BKE_gpencil_free_stroke(gps);
}
@@ -209,6 +243,70 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_
}
}
+static bool extended_bbox_overlap(
+ float min1[3], float max1[3], float min2[3], float max2[3], float extend)
+{
+ for (int axis = 0; axis < 3; axis++) {
+ float intersection_min = max_ff(min1[axis], min2[axis]) - extend;
+ float intersection_max = min_ff(max1[axis], max2[axis]) + extend;
+ if (intersection_min > intersection_max) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void add_stroke_extension(bGPDframe *gpf, bGPDstroke *gps, float p1[3], float p2[3])
+{
+ bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ bGPDspoint *pt = &gps_new->points[0];
+ copy_v3_v3(&pt->x, p1);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &gps_new->points[1];
+ copy_v3_v3(&pt->x, p2);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+}
+
+static void add_endpoint_radius_help(tGPDfill *tgpf,
+ bGPDframe *gpf,
+ bGPDstroke *gps,
+ const float endpoint[3],
+ const float radius,
+ const bool focused)
+{
+ float circumference = 2.0f * M_PI * radius;
+ float vertex_spacing = 0.005f;
+ int num_vertices = min_ii(max_ii((int)ceilf(circumference / vertex_spacing), 3), 40);
+
+ bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, num_vertices, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_CYCLIC | GP_STROKE_HELP;
+ if (focused) {
+ gps_new->flag |= GP_STROKE_TAG;
+ }
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ for (int i = 0; i < num_vertices; i++) {
+ float angle = ((float)i / (float)num_vertices) * 2.0f * M_PI;
+ bGPDspoint *pt = &gps_new->points[i];
+ pt->x = endpoint[0] + radius * cosf(angle);
+ pt->y = endpoint[1];
+ pt->z = endpoint[2] + radius * sinf(angle);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ /* Rotate to object rotation. */
+ sub_v3_v3(&pt->x, endpoint);
+ mul_mat3_m4_v3(tgpf->ob->obmat, &pt->x);
+ add_v3_v3(&pt->x, endpoint);
+ }
+}
+
static void extrapolate_points_by_length(bGPDspoint *a,
bGPDspoint *b,
float length,
@@ -221,8 +319,42 @@ static void extrapolate_points_by_length(bGPDspoint *a,
add_v3_v3v3(r_point, &b->x, ab);
}
-/* Loop all layers create stroke extensions. */
-static void gpencil_create_extensions(tGPDfill *tgpf)
+/* Calculate the size of the array for strokes. */
+static void gpencil_strokes_array_size(tGPDfill *tgpf)
+{
+ bGPdata *gpd = tgpf->gpd;
+ Brush *brush = tgpf->brush;
+ BrushGpencilSettings *brush_settings = brush->gpencil_settings;
+
+ bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd);
+ BLI_assert(gpl_active != NULL);
+
+ const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active);
+ BLI_assert(gpl_active_index >= 0);
+
+ tgpf->stroke_array_num = 0;
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+
+ /* Decide if the strokes of layers are included or not depending on the layer mode. */
+ const int gpl_index = BLI_findindex(&gpd->layers, gpl);
+ bool skip = skip_layer_check(brush_settings->fill_layer_mode, gpl_active_index, gpl_index);
+ if (skip) {
+ continue;
+ }
+
+ bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, tgpf->active_cfra, GP_GETFRAME_USE_PREV);
+ if (gpf == NULL) {
+ continue;
+ }
+ tgpf->stroke_array_num += BLI_listbase_count(&gpf->strokes);
+ }
+}
+
+/* Load all strokes to be procesed by extend lines. */
+static void gpencil_load_array_strokes(tGPDfill *tgpf)
{
Object *ob = tgpf->ob;
bGPdata *gpd = tgpf->gpd;
@@ -235,6 +367,14 @@ static void gpencil_create_extensions(tGPDfill *tgpf)
const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active);
BLI_assert(gpl_active_index >= 0);
+ /* Create array of strokes. */
+ gpencil_strokes_array_size(tgpf);
+ if (tgpf->stroke_array_num == 0) {
+ return;
+ }
+
+ tgpf->stroke_array = MEM_callocN(sizeof(tStroke *) * tgpf->stroke_array_num, __func__);
+ int idx = 0;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if (gpl->flag & GP_LAYER_HIDE) {
continue;
@@ -257,58 +397,379 @@ static void gpencil_create_extensions(tGPDfill *tgpf)
if ((gps->points == NULL) || (gps->totpoints < 2)) {
continue;
}
- if (gps->flag & (GP_STROKE_NOFILL | GP_STROKE_TAG)) {
- continue;
- }
/* Check if the color is visible. */
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
if ((gp_style == NULL) || (gp_style->flag & GP_MATERIAL_HIDE)) {
continue;
}
+ /* Don't include temp strokes. */
+ if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) {
+ continue;
+ }
+
+ tStroke *stroke = MEM_callocN(sizeof(tStroke), "temp stroke data");
+ stroke->gpl = gpl;
+ stroke->gpf = gpf;
+ stroke->gps = gps;
+
+ /* Create the extension strokes only for Lines. */
+ if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) {
+ /* Extend start. */
+ bGPDspoint *pt0 = &gps->points[1];
+ bGPDspoint *pt1 = &gps->points[0];
+ stroke->gps_ext_a = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ stroke->gps_ext_a->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ stroke->gps_ext_a->fill_opacity_fac = FLT_MAX;
+ BLI_addtail(&gpf->strokes, stroke->gps_ext_a);
+
+ bGPDspoint *pt = &stroke->gps_ext_a->points[0];
+ copy_v3_v3(&pt->x, &pt1->x);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &stroke->gps_ext_a->points[1];
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ /* Extend end. */
+ pt0 = &gps->points[gps->totpoints - 2];
+ pt1 = &gps->points[gps->totpoints - 1];
+ stroke->gps_ext_b = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ stroke->gps_ext_b->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ stroke->gps_ext_b->fill_opacity_fac = FLT_MAX;
+ BLI_addtail(&gpf->strokes, stroke->gps_ext_b);
+
+ pt = &stroke->gps_ext_b->points[0];
+ copy_v3_v3(&pt->x, &pt1->x);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &stroke->gps_ext_b->points[1];
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+ }
+ else {
+ stroke->gps_ext_a = NULL;
+ stroke->gps_ext_b = NULL;
+ }
+
+ tgpf->stroke_array[idx] = stroke;
+
+ idx++;
+ }
+ }
+ tgpf->stroke_array_num = idx;
+}
+
+static void set_stroke_collide(bGPDstroke *gps_a, bGPDstroke *gps_b, const float connection_dist)
+{
+ gps_a->flag |= GP_STROKE_COLLIDE;
+ gps_b->flag |= GP_STROKE_COLLIDE;
+
+ /* It uses `fill_opacity_fac` to store distance because this variable is never
+ * used by this type of strokes and can be used for these
+ * temp strokes without adding new variables to the bGPStroke struct. */
+ gps_a->fill_opacity_fac = connection_dist;
+ gps_b->fill_opacity_fac = connection_dist;
+}
+
+/* Cut the extended lines if collide. */
+static void gpencil_cut_extensions(tGPDfill *tgpf)
+{
+ const float connection_dist = tgpf->fill_extend_fac * 0.1f;
+
+ bGPDlayer *gpl_prev = NULL;
+ bGPDframe *gpf_prev = NULL;
+ float diff_mat[4][4], inv_mat[4][4];
+
+ /* Allocate memory for all extend strokes. */
+ bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * tgpf->stroke_array_num * 2,
+ __func__);
+
+ for (int idx = 0; idx < tgpf->stroke_array_num; idx++) {
+ tStroke *stroke = tgpf->stroke_array[idx];
+ bGPDframe *gpf = stroke->gpf;
+ if (stroke->gpl != gpl_prev) {
+ BKE_gpencil_layer_transform_matrix_get(tgpf->depsgraph, tgpf->ob, stroke->gpl, diff_mat);
+ invert_m4_m4(inv_mat, diff_mat);
+ gpl_prev = stroke->gpl;
+ }
+
+ if (gpf == gpf_prev) {
+ continue;
+ }
+ gpf_prev = gpf;
+
+ /* Store all frame extend strokes in an array. */
+ int tot_idx = 0;
+ for (int i = 0; i < tgpf->stroke_array_num; i++) {
+ tStroke *s = tgpf->stroke_array[i];
+ if (s->gpf != gpf) {
+ continue;
+ }
+ if ((s->gps_ext_a) && ((s->gps_ext_a->flag & GP_STROKE_COLLIDE) == 0)) {
+ gps_array[tot_idx] = s->gps_ext_a;
+ tot_idx++;
+ }
+ if ((s->gps_ext_b) && ((s->gps_ext_b->flag & GP_STROKE_COLLIDE) == 0)) {
+ gps_array[tot_idx] = s->gps_ext_b;
+ tot_idx++;
+ }
+ }
+
+ /* Compare all strokes. */
+ for (int i = 0; i < tot_idx; i++) {
+ bGPDstroke *gps_a = gps_array[i];
+
+ bGPDspoint pt2;
+ float a1xy[2], a2xy[2];
+ float b1xy[2], b2xy[2];
+
+ /* First stroke. */
+ bGPDspoint *pt = &gps_a->points[0];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt2);
+ gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a1xy[0], &a1xy[1]);
+
+ pt = &gps_a->points[1];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt2);
+ gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a2xy[0], &a2xy[1]);
+ bGPDspoint *extreme_a = &gps_a->points[1];
+
+ /* Loop all other strokes and check the intersections. */
+ for (int z = 0; z < tot_idx; z++) {
+ bGPDstroke *gps_b = gps_array[z];
+ /* Don't check stroke with itself. */
+ if (i == z) {
+ continue;
+ }
+
+ /* Don't check strokes unless the bounding boxes of the strokes
+ * are close enough together that they can plausibly be connected. */
+ if (!extended_bbox_overlap(gps_a->boundbox_min,
+ gps_a->boundbox_max,
+ gps_b->boundbox_min,
+ gps_b->boundbox_max,
+ connection_dist)) {
+ continue;
+ }
+
+ pt = &gps_b->points[0];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt2);
+ gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b1xy[0], &b1xy[1]);
+
+ pt = &gps_b->points[1];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt2);
+ gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b2xy[0], &b2xy[1]);
+ bGPDspoint *extreme_b = &gps_b->points[1];
+
+ /* Check if extreme points are near. This case is when the
+ * extendend lines are colinear or parallel and close together. */
+ const float gap_pixsize_sq = 25.0f;
+ float intersection3D[3];
+ if (len_squared_v2v2(a2xy, b2xy) <= gap_pixsize_sq) {
+ gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, b2xy, intersection3D);
+ mul_m4_v3(inv_mat, intersection3D);
+ copy_v3_v3(&extreme_a->x, intersection3D);
+ copy_v3_v3(&extreme_b->x, intersection3D);
+ set_stroke_collide(gps_a, gps_b, connection_dist);
+ continue;
+ }
+ /* Check if extensions cross. */
+ if (isect_seg_seg_v2_simple(a1xy, a2xy, b1xy, b2xy)) {
+ float intersection[2];
+ isect_line_line_v2_point(a1xy, a2xy, b1xy, b2xy, intersection);
+ gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, intersection, intersection3D);
+ mul_m4_v3(inv_mat, intersection3D);
+ copy_v3_v3(&extreme_a->x, intersection3D);
+ copy_v3_v3(&extreme_b->x, intersection3D);
+ set_stroke_collide(gps_a, gps_b, connection_dist);
+ continue;
+ }
+ /* Check if extension extreme is near of the origin of any other extension. */
+ if (len_squared_v2v2(a2xy, b1xy) <= gap_pixsize_sq) {
+ gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, b1xy, &extreme_a->x);
+ mul_m4_v3(inv_mat, &extreme_a->x);
+ set_stroke_collide(gps_a, gps_b, connection_dist);
+ continue;
+ }
+ if (len_squared_v2v2(a1xy, b2xy) <= gap_pixsize_sq) {
+ gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, a1xy, &extreme_b->x);
+ mul_m4_v3(inv_mat, &extreme_b->x);
+ set_stroke_collide(gps_a, gps_b, connection_dist);
+ }
+ }
+ }
+ }
+ MEM_SAFE_FREE(gps_array);
+}
+
+/* Loop all strokes and update stroke line extensions. */
+static void gpencil_update_extensions_line(tGPDfill *tgpf)
+{
+ float connection_dist = tgpf->fill_extend_fac * 0.1f;
- /* Extend start. */
+ for (int idx = 0; idx < tgpf->stroke_array_num; idx++) {
+ tStroke *stroke = tgpf->stroke_array[idx];
+ bGPDstroke *gps = stroke->gps;
+ bGPDstroke *gps_a = stroke->gps_ext_a;
+ bGPDstroke *gps_b = stroke->gps_ext_b;
+
+ /* Extend start. */
+ if (((gps_a->flag & GP_STROKE_COLLIDE) == 0) || (gps_a->fill_opacity_fac > connection_dist)) {
bGPDspoint *pt0 = &gps->points[1];
bGPDspoint *pt1 = &gps->points[0];
- bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
- gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
- BLI_addtail(&gpf->strokes, gps_new);
-
- bGPDspoint *pt = &gps_new->points[0];
- copy_v3_v3(&pt->x, &pt1->x);
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
-
- pt = &gps_new->points[1];
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
- extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x);
-
- /* Extend end. */
- pt0 = &gps->points[gps->totpoints - 2];
- pt1 = &gps->points[gps->totpoints - 1];
- gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
- gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
- BLI_addtail(&gpf->strokes, gps_new);
-
- pt = &gps_new->points[0];
- copy_v3_v3(&pt->x, &pt1->x);
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
-
- pt = &gps_new->points[1];
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
- extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x);
+ bGPDspoint *pt = &gps_a->points[1];
+ extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x);
+ gps_a->flag &= ~GP_STROKE_COLLIDE;
+ }
+
+ /* Extend end. */
+ if (((gps_b->flag & GP_STROKE_COLLIDE) == 0) || (gps_b->fill_opacity_fac > connection_dist)) {
+ bGPDspoint *pt0 = &gps->points[gps->totpoints - 2];
+ bGPDspoint *pt1 = &gps->points[gps->totpoints - 1];
+ bGPDspoint *pt = &gps_b->points[1];
+ extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x);
+ gps_b->flag &= ~GP_STROKE_COLLIDE;
}
}
+
+ /* Cut overlength strokes. */
+ gpencil_cut_extensions(tgpf);
+}
+
+/* Loop all strokes and create stroke radius extensions. */
+static void gpencil_create_extensions_radius(tGPDfill *tgpf)
+{
+ float connection_dist = tgpf->fill_extend_fac * 0.1f;
+ GSet *connected_endpoints = BLI_gset_ptr_new(__func__);
+
+ for (int idx = 0; idx < tgpf->stroke_array_num; idx++) {
+ tStroke *stroke = tgpf->stroke_array[idx];
+ bGPDframe *gpf = stroke->gpf;
+ bGPDstroke *gps = stroke->gps;
+
+ /* Find points of high curvature. */
+ float tan1[3];
+ float tan2[3];
+ float d1;
+ float d2;
+ float total_length = 0.f;
+ for (int i = 1; i < gps->totpoints; i++) {
+ if (i > 1) {
+ copy_v3_v3(tan1, tan2);
+ d1 = d2;
+ }
+ bGPDspoint *pt1 = &gps->points[i - 1];
+ bGPDspoint *pt2 = &gps->points[i];
+ sub_v3_v3v3(tan2, &pt2->x, &pt1->x);
+ d2 = normalize_v3(tan2);
+ total_length += d2;
+ if (i > 1) {
+ float curvature[3];
+ sub_v3_v3v3(curvature, tan2, tan1);
+ float k = normalize_v3(curvature);
+ k /= min_ff(d1, d2);
+ float radius = 1.f / k;
+ /*
+ * The smaller the radius of curvature, the sharper the corner.
+ * The thicker the line, the larger the radius of curvature it
+ * takes to be visually indistinguishable from an endpoint.
+ */
+ float min_radius = gps->thickness * 0.0001f;
+
+ if (radius < min_radius) {
+ /* Extend along direction of curvature. */
+ bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ bGPDspoint *pt = &gps_new->points[0];
+ copy_v3_v3(&pt->x, &pt1->x);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &gps_new->points[1];
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+ mul_v3_fl(curvature, -connection_dist);
+ add_v3_v3v3(&pt->x, &pt1->x, curvature);
+ }
+ }
+ }
+
+ /* Connect endpoints within a radius */
+ float *stroke1_start = &gps->points[0].x;
+ float *stroke1_end = &gps->points[gps->totpoints - 1].x;
+ /* Connect the start of the stroke to its own end if the whole stroke
+ * isn't already so short that it's within that distance
+ */
+ if (len_v3v3(stroke1_start, stroke1_end) < connection_dist && total_length > connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_start, stroke1_end);
+ BLI_gset_add(connected_endpoints, stroke1_start);
+ BLI_gset_add(connected_endpoints, stroke1_end);
+ }
+ for (bGPDstroke *gps2 = (bGPDstroke *)(((Link *)gps)->next); gps2 != NULL;
+ gps2 = (bGPDstroke *)(((Link *)gps2)->next)) {
+ /* Don't check distance to temporary extensions. */
+ if ((gps2->flag & GP_STROKE_NOFILL) && (gps2->flag & GP_STROKE_TAG)) {
+ continue;
+ }
+
+ /* Don't check endpoint distances unless the bounding boxes of the strokes
+ are close enough together that they can plausibly be connected. */
+ if (!extended_bbox_overlap(gps->boundbox_min,
+ gps->boundbox_max,
+ gps2->boundbox_min,
+ gps2->boundbox_max,
+ connection_dist)) {
+ continue;
+ }
+
+ float *stroke2_start = &gps2->points[0].x;
+ float *stroke2_end = &gps2->points[gps2->totpoints - 1].x;
+ if (len_v3v3(stroke1_start, stroke2_start) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_start, stroke2_start);
+ BLI_gset_add(connected_endpoints, stroke1_start);
+ BLI_gset_add(connected_endpoints, stroke2_start);
+ }
+ if (len_v3v3(stroke1_start, stroke2_end) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_start, stroke2_end);
+ BLI_gset_add(connected_endpoints, stroke1_start);
+ BLI_gset_add(connected_endpoints, stroke2_end);
+ }
+ if (len_v3v3(stroke1_end, stroke2_start) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_end, stroke2_start);
+ BLI_gset_add(connected_endpoints, stroke1_end);
+ BLI_gset_add(connected_endpoints, stroke2_start);
+ }
+ if (len_v3v3(stroke1_end, stroke2_end) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_end, stroke2_end);
+ BLI_gset_add(connected_endpoints, stroke1_end);
+ BLI_gset_add(connected_endpoints, stroke2_end);
+ }
+ }
+
+ bool start_connected = BLI_gset_haskey(connected_endpoints, stroke1_start);
+ bool end_connected = BLI_gset_haskey(connected_endpoints, stroke1_end);
+ add_endpoint_radius_help(tgpf, gpf, gps, stroke1_start, connection_dist, start_connected);
+ add_endpoint_radius_help(tgpf, gpf, gps, stroke1_end, connection_dist, end_connected);
+ }
+
+ BLI_gset_free(connected_endpoints, NULL);
}
static void gpencil_update_extend(tGPDfill *tgpf)
{
- gpencil_delete_temp_stroke_extension(tgpf, false);
+ if (tgpf->stroke_array == NULL) {
+ gpencil_load_array_strokes(tgpf);
+ }
- if (tgpf->fill_extend_fac > 0.0f) {
- gpencil_create_extensions(tgpf);
+ if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) {
+ gpencil_update_extensions_line(tgpf);
+ }
+ else {
+ gpencil_delete_temp_stroke_extension(tgpf, false);
+ gpencil_create_extensions_radius(tgpf);
}
WM_event_add_notifier(tgpf->C, NC_GPENCIL | NA_EDITED, NULL);
}
@@ -322,15 +783,16 @@ static bool gpencil_stroke_is_drawable(tGPDfill *tgpf, bGPDstroke *gps)
const bool show_help = (tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) != 0;
const bool show_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) != 0;
const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG);
+ const bool is_extend_help = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_HELP);
if ((!show_help) && (show_extend)) {
- if (!is_extend) {
+ if (!is_extend && !is_extend_help) {
return false;
}
}
if ((show_help) && (!show_extend)) {
- if (is_extend) {
+ if (is_extend || is_extend_help) {
return false;
}
}
@@ -357,13 +819,29 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf,
float fpt[3];
float col[4];
const float extend_col[4] = {0.0f, 1.0f, 1.0f, 1.0f};
- const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG);
+ const float help_col[4] = {1.0f, 0.0f, 0.5f, 1.0f};
+ const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG) &&
+ !(gps->flag & GP_STROKE_HELP);
+ const bool is_help = gps->flag & GP_STROKE_HELP;
if (!gpencil_stroke_is_drawable(tgpf, gps)) {
return;
}
- if ((is_extend) && (!tgpf->is_render)) {
+ if (is_help && tgpf->is_render) {
+ /* Help strokes are for display only and shouldn't render. */
+ return;
+ }
+ else if (is_help) {
+ /* Color help strokes that won't affect fill or render separately from
+ * extended strokes, as they will affect them. */
+ copy_v4_v4(col, help_col);
+
+ /* If there is contact, hide the circles to avoid noise and keep the focus
+ * in the pending gaps. */
+ col[3] = (gps->flag & GP_STROKE_TAG) ? 0.0f : 0.5f;
+ }
+ else if ((is_extend) && (!tgpf->is_render)) {
copy_v4_v4(col, extend_col);
}
else {
@@ -379,7 +857,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf,
immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR);
/* draw stroke curve */
- GPU_line_width((!is_extend) ? thickness : thickness * 2.0f);
+ GPU_line_width((!is_extend && !is_help) ? thickness : thickness * 2.0f);
immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints + cyclic_add);
const bGPDspoint *pt = points;
@@ -390,7 +868,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf,
CLAMP(alpha, 0.0f, 1.0f);
col[3] = alpha <= thershold ? 0.0f : 1.0f;
}
- else {
+ else if (!is_help) {
col[3] = 1.0f;
}
/* set point */
@@ -582,7 +1060,8 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4])
/* Normal strokes. */
if (ELEM(tgpf->fill_draw_mode, GP_FILL_DMODE_STROKE, GP_FILL_DMODE_BOTH)) {
- if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0)) {
+ if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0) &&
+ ((gps->flag & GP_STROKE_HELP) == 0)) {
ED_gpencil_draw_fill(&tgpw);
}
}
@@ -1677,7 +2156,8 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf)
/* Helper: Draw status message while the user is running the operator */
static void gpencil_fill_status_indicators(bContext *C)
{
- const char *status_str = TIP_("Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back");
+ const char *status_str = TIP_(
+ "Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back, S: Switch Mode");
ED_workspace_status_text(C, status_str);
}
@@ -1777,13 +2257,17 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op)
tgpf->fill_threshold = brush->gpencil_settings->fill_threshold;
tgpf->fill_simplylvl = brush->gpencil_settings->fill_simplylvl;
tgpf->fill_draw_mode = brush->gpencil_settings->fill_draw_mode;
+ tgpf->fill_extend_mode = brush->gpencil_settings->fill_extend_mode;
tgpf->fill_extend_fac = brush->gpencil_settings->fill_extend_fac;
tgpf->fill_factor = max_ff(GPENCIL_MIN_FILL_FAC,
min_ff(brush->gpencil_settings->fill_factor, GPENCIL_MAX_FILL_FAC));
- tgpf->fill_leak = (int)ceil((float)brush->gpencil_settings->fill_leak * tgpf->fill_factor);
+ tgpf->fill_leak = (int)ceil(FILL_LEAK * tgpf->fill_factor);
int totcol = tgpf->ob->totcol;
+ /* Extensions array */
+ tgpf->stroke_array = NULL;
+
/* get color info */
Material *ma = BKE_gpencil_object_material_ensure_from_active_input_brush(
bmain, tgpf->ob, brush);
@@ -1832,6 +2316,9 @@ static void gpencil_fill_exit(bContext *C, wmOperator *op)
MEM_SAFE_FREE(tgpf->sbuffer);
MEM_SAFE_FREE(tgpf->depth_arr);
+ /* Clean temp strokes. */
+ stroke_array_free(tgpf);
+
/* Remove any temp stroke. */
gpencil_delete_temp_stroke_extension(tgpf, true);
@@ -1928,9 +2415,8 @@ static int gpencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE
tgpf = op->customdata;
/* Enable custom drawing handlers to show help lines */
- const bool do_extend = (tgpf->fill_extend_fac > 0.0f);
- const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) ||
- ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend)));
+ const bool do_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES);
+ const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || (do_extend));
if (help_lines) {
tgpf->draw_handle_3d = ED_region_draw_cb_activate(
@@ -2179,9 +2665,8 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
const bool is_inverted = (is_brush_inv && (event->modifier & KM_CTRL) == 0) ||
(!is_brush_inv && (event->modifier & KM_CTRL) != 0);
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(tgpf->gpd);
- const bool do_extend = (tgpf->fill_extend_fac > 0.0f);
- const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) ||
- ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend)));
+ const bool do_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES);
+ const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || (do_extend));
int estate = OPERATOR_RUNNING_MODAL;
switch (event->type) {
@@ -2315,6 +2800,22 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
}
tgpf->oldkey = event->type;
break;
+ case EVT_SKEY:
+ if ((do_extend) && (event->val == KM_PRESS)) {
+ /* Clean temp strokes. */
+ stroke_array_free(tgpf);
+
+ /* Toogle mode */
+ if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) {
+ tgpf->fill_extend_mode = GP_FILL_EMODE_RADIUS;
+ }
+ else {
+ tgpf->fill_extend_mode = GP_FILL_EMODE_EXTEND;
+ }
+ gpencil_delete_temp_stroke_extension(tgpf, true);
+ gpencil_update_extend(tgpf);
+ }
+ break;
case EVT_PAGEUPKEY:
case WHEELUPMOUSE:
if (tgpf->oldkey == 1) {
diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h
index d656241c463..4d62f834d86 100644
--- a/source/blender/editors/gpencil/gpencil_intern.h
+++ b/source/blender/editors/gpencil/gpencil_intern.h
@@ -593,6 +593,7 @@ void GPENCIL_OT_stroke_cyclical_set(struct wmOperatorType *ot);
*/
void GPENCIL_OT_stroke_caps_set(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_join(struct wmOperatorType *ot);
+void GPENCIL_OT_stroke_start_set(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_flip(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_subdivide(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_simplify(struct wmOperatorType *ot);
@@ -608,6 +609,7 @@ void GPENCIL_OT_stroke_merge_by_distance(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_merge_material(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_reset_vertex_color(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_normalize(struct wmOperatorType *ot);
+void GPENCIL_OT_stroke_outline(struct wmOperatorType *ot);
void GPENCIL_OT_material_to_vertex_color(struct wmOperatorType *ot);
void GPENCIL_OT_extract_palette_vertex(struct wmOperatorType *ot);
diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c
index e7a4f2fe2dc..dc63acf5136 100644
--- a/source/blender/editors/gpencil/gpencil_interpolate.c
+++ b/source/blender/editors/gpencil/gpencil_interpolate.c
@@ -1483,7 +1483,8 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
*/
static const EnumPropertyItem gpencil_interpolation_type_items[] = {
/* Interpolation. */
- RNA_ENUM_ITEM_HEADING(N_("Interpolation"), "Standard transitions between keyframes"),
+ RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Interpolation"),
+ N_("Standard transitions between keyframes")),
{GP_IPO_LINEAR,
"LINEAR",
ICON_IPO_LINEAR,
@@ -1496,9 +1497,9 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
"Custom interpolation defined using a curve map"},
/* Easing. */
- RNA_ENUM_ITEM_HEADING(N_("Easing (by strength)"),
- "Predefined inertial transitions, useful for motion graphics "
- "(from least to most \"dramatic\")"),
+ RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Easing (by strength)"),
+ N_("Predefined inertial transitions, useful for motion graphics "
+ "(from least to most \"dramatic\")")),
{GP_IPO_SINE,
"SINE",
ICON_IPO_SINE,
@@ -1515,7 +1516,8 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
"Circular",
"Circular easing (strongest and most dynamic)"},
- RNA_ENUM_ITEM_HEADING(N_("Dynamic Effects"), "Simple physics-inspired easing effects"),
+ RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Dynamic Effects"),
+ N_("Simple physics-inspired easing effects")),
{GP_IPO_BACK, "BACK", ICON_IPO_BACK, "Back", "Cubic easing with overshoot and settle"},
{GP_IPO_BOUNCE,
"BOUNCE",
@@ -1569,6 +1571,7 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
/* identifiers */
ot->name = "Interpolate Sequence";
ot->idname = "GPENCIL_OT_interpolate_sequence";
+ ot->translation_context = BLT_I18NCONTEXT_ID_GPENCIL;
ot->description = "Generate 'in-betweens' to smoothly interpolate between Grease Pencil frames";
/* api callbacks */
diff --git a/source/blender/editors/gpencil/gpencil_mesh.cc b/source/blender/editors/gpencil/gpencil_mesh.cc
index b27e1c75746..739a1b319c3 100644
--- a/source/blender/editors/gpencil/gpencil_mesh.cc
+++ b/source/blender/editors/gpencil/gpencil_mesh.cc
@@ -213,7 +213,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op)
bool newob = false;
if (target == GP_TARGET_OB_SELECTED) {
- ob_gpencil = BKE_view_layer_non_active_selected_object(CTX_data_view_layer(C), v3d);
+ ob_gpencil = BKE_view_layer_non_active_selected_object(scene, CTX_data_view_layer(C), v3d);
if (ob_gpencil != nullptr) {
if (ob_gpencil->type != OB_GPENCIL) {
BKE_report(op->reports, RPT_WARNING, "Target object not a grease pencil, ignoring!");
@@ -315,7 +315,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op)
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if ((gps->flag & GP_STROKE_TAG) == 0) {
ED_gpencil_stroke_reproject(
- depsgraph, &gsc, sctx, gpl, gpf, gps, project_type, false);
+ depsgraph, &gsc, sctx, gpl, gpf, gps, project_type, false, 0.0f);
gps->flag |= GP_STROKE_TAG;
}
}
diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c
index 99e28270c3e..85cc281ca90 100644
--- a/source/blender/editors/gpencil/gpencil_ops.c
+++ b/source/blender/editors/gpencil/gpencil_ops.c
@@ -621,6 +621,7 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_stroke_caps_set);
WM_operatortype_append(GPENCIL_OT_stroke_join);
WM_operatortype_append(GPENCIL_OT_stroke_flip);
+ WM_operatortype_append(GPENCIL_OT_stroke_start_set);
WM_operatortype_append(GPENCIL_OT_stroke_subdivide);
WM_operatortype_append(GPENCIL_OT_stroke_simplify);
WM_operatortype_append(GPENCIL_OT_stroke_simplify_fixed);
@@ -635,6 +636,7 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_stroke_merge_material);
WM_operatortype_append(GPENCIL_OT_stroke_reset_vertex_color);
WM_operatortype_append(GPENCIL_OT_stroke_normalize);
+ WM_operatortype_append(GPENCIL_OT_stroke_outline);
WM_operatortype_append(GPENCIL_OT_material_to_vertex_color);
WM_operatortype_append(GPENCIL_OT_extract_palette_vertex);
diff --git a/source/blender/editors/gpencil/gpencil_ops_versioning.c b/source/blender/editors/gpencil/gpencil_ops_versioning.c
index 8119646137c..50fbafff732 100644
--- a/source/blender/editors/gpencil/gpencil_ops_versioning.c
+++ b/source/blender/editors/gpencil/gpencil_ops_versioning.c
@@ -92,7 +92,7 @@ static int gpencil_convert_old_files_exec(bContext *C, wmOperator *op)
if ((!is_annotation) && (view_layer != NULL)) {
Object *ob;
ob = BKE_object_add_for_data(
- bmain, view_layer, OB_GPENCIL, "GP_Scene", &scene->gpd->id, false);
+ bmain, scene, view_layer, OB_GPENCIL, "GP_Scene", &scene->gpd->id, false);
zero_v3(ob->loc);
DEG_relations_tag_update(bmain); /* added object */
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 13ea5179b23..7446c727a0c 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -918,6 +918,67 @@ static void gpencil_stroke_unselect(bGPdata *gpd, bGPDstroke *gps)
}
}
+static bGPDstroke *gpencil_stroke_to_outline(tGPsdata *p, bGPDstroke *gps)
+{
+ bGPDlayer *gpl = p->gpl;
+ RegionView3D *rv3d = p->region->regiondata;
+ Brush *brush = p->brush;
+ BrushGpencilSettings *gpencil_settings = brush->gpencil_settings;
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(p->ob, gps->mat_nr + 1);
+ const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0);
+
+ if (!is_stroke) {
+ return gps;
+ }
+
+ /* Duplicate the stroke to apply any layer thickness change. */
+ bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
+
+ /* Apply layer thickness change. */
+ gps_duplicate->thickness += gpl->line_change;
+ /* Apply object scale to thickness. */
+ gps_duplicate->thickness *= mat4_to_scale(p->ob->obmat);
+ CLAMP_MIN(gps_duplicate->thickness, 1.0f);
+
+ /* Stroke. */
+ float diff_mat[4][4];
+ unit_m4(diff_mat);
+ const float outline_thickness = (float)brush->size * gpencil_settings->outline_fac * 0.5f;
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d->viewmat, p->gpd, gpl, gps_duplicate, 3, diff_mat, outline_thickness);
+ /* Assign material. */
+ if (gpencil_settings->material_alt == NULL) {
+ gps_perimeter->mat_nr = gps->mat_nr;
+ }
+ else {
+ Material *ma = gpencil_settings->material_alt;
+ int mat_idx = BKE_gpencil_material_find_index_by_name_prefix(p->ob, ma->id.name + 2);
+ if (mat_idx > -1) {
+ gps_perimeter->mat_nr = mat_idx;
+ }
+ else {
+ gps_perimeter->mat_nr = gps->mat_nr;
+ }
+ }
+
+ /* Set pressure constant. */
+ gps_perimeter->thickness = max_ii((int)outline_thickness, 1);
+
+ bGPDspoint *pt;
+ for (int i = 0; i < gps_perimeter->totpoints; i++) {
+ pt = &gps_perimeter->points[i];
+ pt->pressure = 1.0f;
+ }
+
+ /* Remove original stroke. */
+ BKE_gpencil_free_stroke(gps);
+
+ /* Free Temp stroke. */
+ BKE_gpencil_free_stroke(gps_duplicate);
+
+ return gps_perimeter;
+}
+
/* make a new stroke from the buffer data */
static void gpencil_stroke_newfrombuffer(tGPsdata *p)
{
@@ -1221,6 +1282,23 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
BKE_gpencil_stroke_simplify_adaptive(gpd, gps, brush->gpencil_settings->simplify_f);
}
+ /* Set material index. */
+ gps->mat_nr = BKE_gpencil_object_material_get_index_from_brush(p->ob, p->brush);
+ if (gps->mat_nr < 0) {
+ if (p->ob->actcol - 1 < 0) {
+ gps->mat_nr = 0;
+ }
+ else {
+ gps->mat_nr = p->ob->actcol - 1;
+ }
+ }
+
+ /* Convert to Outline. */
+ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) &&
+ (brush->gpencil_settings->flag & GP_BRUSH_OUTLINE_STROKE)) {
+ gps = gpencil_stroke_to_outline(p, gps);
+ }
+
/* reproject to plane (only in 3d space) */
gpencil_reproject_toplane(p, gps);
/* change position relative to parent object */
@@ -1235,17 +1313,6 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
}
}
- /* Save material index */
- gps->mat_nr = BKE_gpencil_object_material_get_index_from_brush(p->ob, p->brush);
- if (gps->mat_nr < 0) {
- if (p->ob->actcol - 1 < 0) {
- gps->mat_nr = 0;
- }
- else {
- gps->mat_nr = p->ob->actcol - 1;
- }
- }
-
/* add stroke to frame, usually on tail of the listbase, but if on back is enabled the stroke
* is added on listbase head because the drawing order is inverse and the head stroke is the
* first to draw. This is very useful for artist when drawing the background.
@@ -2343,7 +2410,7 @@ static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr)
if (p->paintmode == GP_PAINTMODE_ERASER) {
GPUVertFormat *format = immVertexFormat();
const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
@@ -2353,7 +2420,7 @@ static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr)
immUnbindProgram();
- immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
@@ -3264,7 +3331,7 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event
return OPERATOR_RUNNING_MODAL;
}
-/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
+/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
static bool gpencil_area_exists(bContext *C, ScrArea *area_test)
{
bScreen *screen = CTX_wm_screen(C);
@@ -3658,9 +3725,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
}
}
- /* Exit painting mode (and/or end current stroke).
- *
- */
+ /* Exit painting mode (and/or end current stroke). */
if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, EVT_ESCKEY, EVT_SPACEKEY)) {
p->status = GP_STATUS_DONE;
@@ -3823,7 +3888,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
}
}
- /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
+ /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
if (0 == gpencil_area_exists(C, p->area)) {
estate = OPERATOR_CANCELLED;
}
diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c
index 70f12151fdd..4a4fffc9638 100644
--- a/source/blender/editors/gpencil/gpencil_primitive.c
+++ b/source/blender/editors/gpencil/gpencil_primitive.c
@@ -1024,8 +1024,10 @@ static void gpencil_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi)
gpd->runtime.sbuffer, &gpd->runtime.sbuffer_size, &gpd->runtime.sbuffer_used, false);
/* add small offset to keep stroke over the surface */
- if ((depth_arr) && (gpd->zdepth_offset > 0.0f) && (depth_arr[i] != DEPTH_INVALID)) {
- depth_arr[i] *= (1.0f - (gpd->zdepth_offset / 1000.0f));
+ if (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_VIEW) {
+ if ((depth_arr) && (gpd->zdepth_offset > 0.0f) && (depth_arr[i] != DEPTH_INVALID)) {
+ depth_arr[i] *= (1.0f - (gpd->zdepth_offset / 1000.0f));
+ }
}
/* convert screen-coordinates to 3D coordinates */
diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
index e27cd255217..52e6200978c 100644
--- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c
+++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
@@ -516,7 +516,7 @@ static void gpencil_brush_grab_calc_dvec(tGP_BrushEditData *gso)
float mval_f[2];
- /* convert from 2D screenspace to 3D... */
+ /* Convert from 2D screen-space to 3D. */
mval_f[0] = (float)(gso->mval[0] - gso->mval_prev[0]);
mval_f[1] = (float)(gso->mval[1] - gso->mval_prev[1]);
@@ -700,8 +700,8 @@ static bool gpencil_brush_pinch_apply(tGP_BrushEditData *gso,
/* ----------------------------------------------- */
/* Twist Brush - Rotate Around midpoint */
-/* Take the screenspace coordinates of the point, rotate this around the brush midpoint,
- * convert the rotated point and convert it into "data" space
+/* Take the screen-space coordinates of the point, rotate this around the brush midpoint,
+ * convert the rotated point and convert it into "data" space.
*/
static bool gpencil_brush_twist_apply(tGP_BrushEditData *gso,
@@ -807,7 +807,7 @@ static bool gpencil_brush_randomize_apply(tGP_BrushEditData *gso,
/* apply random to position */
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) {
/* Jitter is applied perpendicular to the mouse movement vector
- * - We compute all effects in screenspace (since it's easier)
+ * - We compute all effects in screen-space (since it's easier)
* and then project these to get the points/distances in
* view-space as needed.
*/
@@ -989,8 +989,8 @@ static void gpencil_brush_clone_add(bContext *C, tGP_BrushEditData *gso)
float delta[3];
size_t strokes_added = 0;
- /* Compute amount to offset the points by */
- /* NOTE: This assumes that screenspace strokes are NOT used in the 3D view... */
+ /* Compute amount to offset the points by. */
+ /* NOTE: This assumes that screen-space strokes are NOT used in the 3D view. */
gpencil_brush_calc_midpoint(gso); /* this puts the cursor location into gso->dvec */
sub_v3_v3v3(delta, gso->dvec, data->buffer_midpoint);
@@ -1063,7 +1063,7 @@ static void gpencil_brush_clone_adjust(tGP_BrushEditData *gso)
/* For each of the stored strokes, apply the offset to each point */
/* NOTE: Again this assumes that in the 3D view,
- * we only have 3d space and not screenspace strokes... */
+ * we only have 3d space and not screen-space strokes. */
for (snum = 0; snum < data->totitems; snum++) {
bGPDstroke *gps = data->new_strokes[snum];
bGPDspoint *pt;
diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c
index a19265720e8..95f43733a36 100644
--- a/source/blender/editors/gpencil/gpencil_select.c
+++ b/source/blender/editors/gpencil/gpencil_select.c
@@ -2071,7 +2071,7 @@ static bool gpencil_generic_stroke_select(bContext *C,
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
- /* convert point coords to screenspace */
+ /* Convert point coords to screen-space. */
const bool is_inside = is_inside_fn(gsc.region, gpstroke_iter.diff_mat, &pt->x, user_data);
if (strokemode == false) {
const bool is_select = (pt_active->flag & GP_SPOINT_SELECT) != 0;
diff --git a/source/blender/editors/gpencil/gpencil_trace_ops.c b/source/blender/editors/gpencil/gpencil_trace_ops.c
index f6e88e05d46..36165c6b7c0 100644
--- a/source/blender/editors/gpencil/gpencil_trace_ops.c
+++ b/source/blender/editors/gpencil/gpencil_trace_ops.c
@@ -71,6 +71,9 @@ typedef struct TraceJob {
int32_t thickness;
int32_t turnpolicy;
int32_t mode;
+ /** Frame to render to be used by python API. Not exposed in UI.
+ * This feature is only used in Studios to run custom video trace for selected frames. */
+ int32_t frame_num;
bool success;
bool was_canceled;
@@ -212,7 +215,10 @@ static void trace_start_job(void *customdata, short *stop, short *do_update, flo
(trace_job->mode == GPENCIL_TRACE_MODE_SINGLE)) {
void *lock;
ImageUser *iuser = trace_job->ob_active->iuser;
- iuser->framenr = init_frame;
+
+ iuser->framenr = ((trace_job->frame_num == 0) || (trace_job->frame_num > iuser->frames)) ?
+ init_frame :
+ trace_job->frame_num;
ImBuf *ibuf = BKE_image_acquire_ibuf(trace_job->image, iuser, &lock);
if (ibuf) {
/* Create frame. */
@@ -300,9 +306,10 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op)
/* Create a new grease pencil object or reuse selected. */
eGP_TargetObjectMode target = RNA_enum_get(op->ptr, "target");
- job->ob_gpencil = (target == GP_TARGET_OB_SELECTED) ? BKE_view_layer_non_active_selected_object(
- CTX_data_view_layer(C), job->v3d) :
- NULL;
+ job->ob_gpencil = (target == GP_TARGET_OB_SELECTED) ?
+ BKE_view_layer_non_active_selected_object(
+ scene, CTX_data_view_layer(C), job->v3d) :
+ NULL;
if (job->ob_gpencil != NULL) {
if (job->ob_gpencil->type != OB_GPENCIL) {
@@ -324,13 +331,14 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op)
job->thickness = RNA_int_get(op->ptr, "thickness");
job->turnpolicy = RNA_enum_get(op->ptr, "turnpolicy");
job->mode = RNA_enum_get(op->ptr, "mode");
+ job->frame_num = RNA_int_get(op->ptr, "frame_number");
trace_initialize_job_data(job);
/* Back to active base. */
ED_object_base_activate(job->C, job->base_active);
- if (job->image->source == IMA_SRC_FILE) {
+ if ((job->image->source == IMA_SRC_FILE) || (job->frame_num > 0)) {
short stop = 0, do_update = true;
float progress;
trace_start_job(job, &stop, &do_update, &progress);
@@ -364,6 +372,8 @@ static int gpencil_trace_image_invoke(bContext *C, wmOperator *op, const wmEvent
void GPENCIL_OT_trace_image(wmOperatorType *ot)
{
+ PropertyRNA *prop;
+
static const EnumPropertyItem turnpolicy_type[] = {
{POTRACE_TURNPOLICY_BLACK,
"BLACK",
@@ -475,4 +485,15 @@ void GPENCIL_OT_trace_image(wmOperatorType *ot)
true,
"Start At Current Frame",
"Trace Image starting in current image frame");
+ prop = RNA_def_int(
+ ot->srna,
+ "frame_number",
+ 0,
+ 0,
+ 9999,
+ "Trace Frame",
+ "Used to trace only one frame of the image sequence, set to zero to trace all",
+ 0,
+ 9999);
+ RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
index 7b659511aaa..729e8412684 100644
--- a/source/blender/editors/gpencil/gpencil_utils.c
+++ b/source/blender/editors/gpencil/gpencil_utils.c
@@ -1038,7 +1038,8 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph,
bGPDframe *gpf,
bGPDstroke *gps,
const eGP_ReprojectModes mode,
- const bool keep_original)
+ const bool keep_original,
+ const float offset)
{
ToolSettings *ts = gsc->scene->toolsettings;
ARegion *region = gsc->region;
@@ -1156,7 +1157,13 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph,
&depth,
&location[0],
&normal[0])) {
- copy_v3_v3(&pt->x, location);
+ /* Apply offset over surface. */
+ float normal_vector[3];
+ sub_v3_v3v3(normal_vector, ray_start, location);
+ normalize_v3(normal_vector);
+ mul_v3_fl(normal_vector, offset);
+
+ add_v3_v3v3(&pt->x, location, normal_vector);
}
else {
/* Default to planar */
@@ -1683,7 +1690,7 @@ void ED_gpencil_brush_draw_eraser(Brush *brush, int x, int y)
GPUVertFormat *format = immVertexFormat();
const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
@@ -1693,7 +1700,7 @@ void ED_gpencil_brush_draw_eraser(Brush *brush, int x, int y)
immUnbindProgram();
- immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
@@ -1865,7 +1872,7 @@ static void gpencil_brush_cursor_draw(bContext *C, int x, int y, void *customdat
/* draw icon */
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
@@ -3189,7 +3196,7 @@ bGPDstroke *ED_gpencil_stroke_join_and_trim(
/* Join both strokes. */
int totpoint = gps_final->totpoints;
- BKE_gpencil_stroke_join(gps_final, gps, false, true, true);
+ BKE_gpencil_stroke_join(gps_final, gps, false, true, true, true);
/* Select the join points and merge if the distance is very small. */
pt = &gps_final->points[totpoint - 1];
diff --git a/source/blender/editors/gpencil/gpencil_vertex_ops.c b/source/blender/editors/gpencil/gpencil_vertex_ops.c
index 865c4e360b5..41f939813e4 100644
--- a/source/blender/editors/gpencil/gpencil_vertex_ops.c
+++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c
@@ -131,7 +131,7 @@ static int gpencil_vertexpaint_brightness_contrast_exec(bContext *C, wmOperator
/*
* The algorithm is by Werner D. Streidt
* (http://visca.com/ffactory/archives/5-99/msg00021.html)
- * Extracted of OpenCV demhist.c
+ * Extracted of OpenCV `demhist.c`.
*/
if (contrast > 0) {
gain = 1.0f - delta * 2.0f;