GPencil: Fill tool refactor and Multiframe in Draw mode

This commit is a refactor of the fill tool to solve several problems we had since the first version of the tool.

Changes:

* The filling speed has been improved for each step of the process with the optimization of each algorithm/function.
* New `AutoFit` option to fill areas outside of the viewport. When enable, the total size of the frame is calculated to fit the filling area.
* New support multiframe filling. Now it is possible to fill multiple similar frames in one go.
* New `Stroke Extension` option to create temporary closing strokes. These strokes can be displayed and adjusted dynamically using wheel mouse or PageUp/Down keys.
* Parameter `Resolution` now is named `Precision` and has been moved to topbar.
* `Resolution` now has decimals and can be lower than 1 to allow quick filling in storyboarding workflows. Maximum value has been set as 5.
* Parameter `Simplify` has been moved to Advanced panel.
* Improved fill outline detection. In some cases, the outline penetrated the area to be filled with unexpected results.
* Fixes some corner case bugs with infinite loops.

As a result of this refactor, also these new functionalities has been added.

* New support for multiframe in `Draw` mode. Any drawing in active frame is duplicated to all selected frame.
* New multiframe display mode. Keyframes before or after of the active frame are displayed using onion colors. This can be disable using Onion overlay options.
This commit is contained in:
Antonio Vazquez 2021-02-08 16:28:42 +01:00
parent 5213b18eb2
commit 1352d81b17
Notes: blender-bot 2023-02-13 19:36:30 +01:00
Referenced by issue #85587, Grease Pencil crash selecting multiple frames with Multiframe enabled
20 changed files with 1089 additions and 384 deletions

View File

@ -1218,11 +1218,13 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
row = layout.row(align=True)
row.prop(gp_settings, "fill_direction", text="", expand=True)
row = layout.row(align=True)
row.prop(gp_settings, "fill_factor")
row = layout.row(align=True)
row.prop(gp_settings, "fill_leak", text="Leak Size")
row = layout.row(align=True)
row.prop(brush, "size", text="Thickness")
row = layout.row(align=True)
row.prop(gp_settings, "fill_simplify_level", text="Simplify")
row.prop(gp_settings, "use_fill_autofit", text="", icon="SNAP_FACE_CENTER")
else: # brush.gpencil_tool == 'DRAW/TINT':
row = layout.row(align=True)

View File

@ -695,6 +695,10 @@ class VIEW3D_HT_header(Header):
row.prop(tool_settings, "use_gpencil_vertex_select_mask_stroke", text="")
row.prop(tool_settings, "use_gpencil_vertex_select_mask_segment", text="")
if gpd.is_stroke_paint_mode:
row = layout.row(align=True)
row.prop(gpd, "use_multiedit", text="", icon='GP_MULTIFRAME_EDITING')
if (
gpd.use_stroke_edit_mode or
gpd.is_stroke_sculpt_mode or

View File

@ -1394,7 +1394,7 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel):
bl_parent_id = 'VIEW3D_PT_tools_grease_pencil_brush_settings'
bl_category = "Tool"
bl_options = {'DEFAULT_CLOSED'}
bl_ui_units_x = 11
bl_ui_units_x = 13
@classmethod
def poll(cls, context):
@ -1447,7 +1447,12 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel):
row.prop(gp_settings, "fill_layer_mode", text="Layers")
col.separator()
col.prop(gp_settings, "fill_factor")
row = col.row(align=True)
row.prop(gp_settings, "extend_stroke_factor")
row.prop(gp_settings, "show_fill_extend", text="", icon='GRID')
col.separator()
col.prop(gp_settings, "fill_simplify_level", text="Simplify")
if gp_settings.fill_draw_mode != 'STROKE':
col = layout.column(align=False, heading="Ignore Transparent")
col.use_property_decorate = False

View File

@ -115,6 +115,8 @@ struct bGPDlayer *BKE_gpencil_layer_duplicate(const struct bGPDlayer *gpl_src,
const bool dup_frames,
const bool dup_strokes);
void BKE_gpencil_frame_copy_strokes(struct bGPDframe *gpf_src, struct bGPDframe *gpf_dst);
void BKE_gpencil_frame_selected_hash(struct bGPdata *gpd, struct GHash *r_list);
struct bGPDcurve *BKE_gpencil_stroke_curve_duplicate(struct bGPDcurve *gpc_src);
struct bGPDstroke *BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src,
const bool dup_points,

View File

@ -149,6 +149,11 @@ void BKE_gpencil_stroke_join(struct bGPDstroke *gps_a,
struct bGPDstroke *gps_b,
const bool leave_gaps,
const bool fit_thickness);
void BKE_gpencil_stroke_copy_to_keyframes(struct bGPdata *gpd,
struct bGPDlayer *gpl,
struct bGPDframe *gpf,
struct bGPDstroke *gps,
const bool tail);
bool BKE_gpencil_convert_mesh(struct Main *bmain,
struct Depsgraph *depsgraph,

View File

@ -975,7 +975,7 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type)
break;
}
case GP_BRUSH_PRESET_FILL_AREA: {
brush->size = 20.0f;
brush->size = 5.0f;
brush->gpencil_settings->fill_leak = 3;
brush->gpencil_settings->fill_threshold = 0.1f;
@ -989,6 +989,8 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type)
brush->gpencil_settings->draw_smoothlvl = 1;
brush->gpencil_settings->draw_subdivide = 1;
brush->gpencil_settings->flag |= GP_BRUSH_FILL_SHOW_EXTENDLINES;
brush->gpencil_settings->icon_id = GP_BRUSH_ICON_FILL;
brush->gpencil_tool = GPAINT_TOOL_FILL;
brush->gpencil_settings->vertex_mode = GPPAINT_MODE_FILL;

View File

@ -851,6 +851,7 @@ bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness)
gps->mat_nr = mat_idx;
gps->dvert = NULL;
gps->editcurve = NULL;
return gps;
@ -2606,6 +2607,15 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
if (gpf == act_gpf || (gpf->flag & GP_FRAME_SELECT)) {
gpf->runtime.onion_id = 0;
if (do_onion) {
if (gpf->framenum < act_gpf->framenum) {
gpf->runtime.onion_id = -1;
}
else {
gpf->runtime.onion_id = 1;
}
}
if (sta_gpf == NULL) {
sta_gpf = gpf;
}
@ -2939,4 +2949,26 @@ int BKE_gpencil_material_find_index_by_name_prefix(Object *ob, const char *name_
return -1;
}
/* Create a hash with the list of selected frame number. */
void BKE_gpencil_frame_selected_hash(bGPdata *gpd, struct GHash *r_list)
{
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd);
LISTBASE_FOREACH (bGPDlayer *, gpl_iter, &gpd->layers) {
if ((gpl != NULL) && (!is_multiedit) && (gpl != gpl_iter)) {
continue;
}
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl_iter->frames) {
if (((gpf == gpl->actframe) && (!is_multiedit)) ||
((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
if (!BLI_ghash_lookup(r_list, POINTER_FROM_INT(gpf->framenum))) {
BLI_ghash_insert(r_list, POINTER_FROM_INT(gpf->framenum), gpf);
}
}
}
}
}
/** \} */

View File

@ -3228,6 +3228,45 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
}
}
/* Copy the stroke of the frame to all frames selected (except current). */
void BKE_gpencil_stroke_copy_to_keyframes(
bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, const bool tail)
{
GHash *frame_list = BLI_ghash_int_new_ex(__func__, 64);
BKE_gpencil_frame_selected_hash(gpd, frame_list);
GHashIterator gh_iter;
GHASH_ITER (gh_iter, frame_list) {
int cfra = POINTER_AS_INT(BLI_ghashIterator_getKey(&gh_iter));
if (gpf->framenum != cfra) {
bGPDframe *gpf_new = BKE_gpencil_layer_frame_find(gpl, cfra);
if (gpf_new == NULL) {
gpf_new = BKE_gpencil_frame_addnew(gpl, cfra);
}
if (gpf_new == NULL) {
continue;
}
bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, true, true);
if (gps_new == NULL) {
continue;
}
if (tail) {
BLI_addhead(&gpf_new->strokes, gps_new);
}
else {
BLI_addtail(&gpf_new->strokes, gps_new);
}
}
}
/* Free hash table. */
BLI_ghash_free(frame_list, NULL, NULL);
}
/* Stroke Uniform Subdivide ------------------------------------- */
typedef struct tSamplePoint {

View File

@ -171,7 +171,8 @@ static void gpencil_draw_stroke_3d(tGPDdraw *tgpw,
int totpoints = tgpw->gps->totpoints;
const float viewport[2] = {(float)tgpw->winx, (float)tgpw->winy};
float curpressure = points[0].pressure;
const float min_thickness = 0.05f;
float fpt[3];
/* if cyclic needs more vertex */
@ -205,7 +206,6 @@ static void gpencil_draw_stroke_3d(tGPDdraw *tgpw,
immUniform1i("fill_stroke", (int)tgpw->is_fill_stroke);
/* draw stroke curve */
GPU_line_width(max_ff(curpressure * thickness, 1.0f));
immBeginAtMost(GPU_PRIM_LINE_STRIP_ADJ, totpoints + cyclic_add + 2);
const bGPDspoint *pt = points;
@ -215,18 +215,19 @@ static void gpencil_draw_stroke_3d(tGPDdraw *tgpw,
gpencil_set_point_varying_color(points, ink, attr_id.color, (bool)tgpw->is_fill_stroke);
if ((cyclic) && (totpoints > 2)) {
immAttr1f(attr_id.thickness, max_ff((points + totpoints - 1)->pressure * thickness, 1.0f));
immAttr1f(attr_id.thickness,
max_ff((points + totpoints - 1)->pressure * thickness, min_thickness));
mul_v3_m4v3(fpt, tgpw->diff_mat, &(points + totpoints - 1)->x);
}
else {
immAttr1f(attr_id.thickness, max_ff((points + 1)->pressure * thickness, 1.0f));
immAttr1f(attr_id.thickness, max_ff((points + 1)->pressure * thickness, min_thickness));
mul_v3_m4v3(fpt, tgpw->diff_mat, &(points + 1)->x);
}
immVertex3fv(attr_id.pos, fpt);
}
/* set point */
gpencil_set_point_varying_color(pt, ink, attr_id.color, (bool)tgpw->is_fill_stroke);
immAttr1f(attr_id.thickness, max_ff(pt->pressure * thickness, 1.0f));
immAttr1f(attr_id.thickness, max_ff(pt->pressure * thickness, min_thickness));
mul_v3_m4v3(fpt, tgpw->diff_mat, &pt->x);
immVertex3fv(attr_id.pos, fpt);
}

View File

@ -466,9 +466,15 @@ static int gpencil_layer_copy_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
/* make copy of layer, and add it immediately after the existing layer */
/* Make copy of layer, and add it immediately after or before the existing layer. */
new_layer = BKE_gpencil_layer_duplicate(gpl, true, dup_strokes);
BLI_insertlinkafter(&gpd->layers, gpl, new_layer);
if (dup_strokes) {
BLI_insertlinkafter(&gpd->layers, gpl, new_layer);
}
else {
/* For empty strokes is better add below. */
BLI_insertlinkbefore(&gpd->layers, gpl, new_layer);
}
/* ensure new layer has a unique name, and is now the active layer */
BLI_uniquename(&gpd->layers,

View File

@ -4948,11 +4948,14 @@ static int gpencil_cutter_lasso_select(bContext *C,
GPencilTestFn is_inside_fn,
void *user_data)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *obact = CTX_data_active_object(C);
bGPdata *gpd = ED_gpencil_data_get_active(C);
ScrArea *area = CTX_wm_area(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const float scale = ts->gp_sculpt.isect_threshold;
const bool flat_caps = RNA_boolean_get(op->ptr, "flat_caps");
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
bGPDspoint *pt;
GP_SpaceConversion gsc = {NULL};
@ -4979,57 +4982,87 @@ static int gpencil_cutter_lasso_select(bContext *C,
}
CTX_DATA_END;
/* select points */
GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) {
int tot_inside = 0;
const int oldtot = gps->totpoints;
for (int i = 0; i < gps->totpoints; i++) {
pt = &gps->points[i];
if ((pt->flag & GP_SPOINT_SELECT) || (pt->flag & GP_SPOINT_TAG)) {
continue;
}
/* convert point coords to screen-space */
const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data);
if (is_inside) {
tot_inside++;
changed = true;
pt->flag |= GP_SPOINT_SELECT;
gps->flag |= GP_STROKE_SELECT;
float r_hita[3], r_hitb[3];
if (gps->totpoints > 1) {
ED_gpencil_select_stroke_segment(gpd, gpl, gps, pt, true, true, scale, r_hita, r_hitb);
/* Select points */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if ((gpl->flag & GP_LAYER_LOCKED) || ((gpl->flag & GP_LAYER_HIDE))) {
continue;
}
float diff_mat[4][4];
BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, 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;
}
/* avoid infinite loops */
if (gps->totpoints > oldtot) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
} /* check if the color is editable */
if (ED_gpencil_stroke_material_editable(obact, gpl, gps) == false) {
continue;
}
int tot_inside = 0;
const int oldtot = gps->totpoints;
for (int i = 0; i < gps->totpoints; i++) {
pt = &gps->points[i];
if ((pt->flag & GP_SPOINT_SELECT) || (pt->flag & GP_SPOINT_TAG)) {
continue;
}
/* convert point coords to screen-space */
const bool is_inside = is_inside_fn(gps, pt, &gsc, diff_mat, user_data);
if (is_inside) {
tot_inside++;
changed = true;
pt->flag |= GP_SPOINT_SELECT;
gps->flag |= GP_STROKE_SELECT;
float r_hita[3], r_hitb[3];
if (gps->totpoints > 1) {
ED_gpencil_select_stroke_segment(
gpd, gpl, gps, pt, true, true, scale, r_hita, r_hitb);
}
/* avoid infinite loops */
if (gps->totpoints > oldtot) {
break;
}
}
}
/* if mark all points inside lasso set to remove all stroke */
if ((tot_inside == oldtot) || ((tot_inside == 1) && (oldtot == 2))) {
for (int i = 0; i < gps->totpoints; i++) {
pt = &gps->points[i];
pt->flag |= GP_SPOINT_SELECT;
}
}
}
/* if not multiedit, exit loop. */
if (!is_multiedit) {
break;
}
}
}
/* if mark all points inside lasso set to remove all stroke */
if ((tot_inside == oldtot) || ((tot_inside == 1) && (oldtot == 2))) {
for (int i = 0; i < gps->totpoints; i++) {
pt = &gps->points[i];
pt->flag |= GP_SPOINT_SELECT;
}
}
}
GP_EDITABLE_STROKES_END(gpstroke_iter);
/* dissolve selected points */
/* Dissolve selected points. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if (gpl->flag & GP_LAYER_LOCKED) {
continue;
}
bGPDframe *gpf = gpl->actframe;
if (gpf == NULL) {
continue;
}
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
if (gps->flag & GP_STROKE_SELECT) {
gpencil_cutter_dissolve(gpd, gpl, gps, flat_caps);
bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
bGPDframe *gpf_act = gpl->actframe;
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
gpl->actframe = gpf;
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
if (gps->flag & GP_STROKE_SELECT) {
gpencil_cutter_dissolve(gpd, gpl, gps, flat_caps);
}
}
/* if not multiedit, exit loop. */
if (!is_multiedit) {
break;
}
}
gpl->actframe = gpf_act;
}
/* updates */

File diff suppressed because it is too large Load Diff

View File

@ -1306,6 +1306,12 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps);
/* In Multiframe mode, duplicate the stroke in other frames. */
if (GPENCIL_MULTIEDIT_SESSIONS_ON(p->gpd)) {
const bool tail = (ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK);
BKE_gpencil_stroke_copy_to_keyframes(gpd, gpl, p->gpf, gps, tail);
}
gpencil_stroke_added_enable(p);
}
@ -1323,10 +1329,8 @@ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3])
}
/* only erase stroke points that are visible */
static bool gpencil_stroke_eraser_is_occluded(tGPsdata *p,
const bGPDspoint *pt,
const int x,
const int y)
static bool gpencil_stroke_eraser_is_occluded(
tGPsdata *p, bGPDlayer *gpl, const bGPDspoint *pt, const int x, const int y)
{
Object *obact = (Object *)p->ownerPtr.data;
Brush *brush = p->brush;
@ -1343,7 +1347,6 @@ static bool gpencil_stroke_eraser_is_occluded(tGPsdata *p,
if ((gp_settings != NULL) && (p->area->spacetype == SPACE_VIEW3D) &&
(gp_settings->flag & GP_BRUSH_OCCLUDE_ERASER)) {
RegionView3D *rv3d = p->region->regiondata;
bGPDlayer *gpl = p->gpl;
const int mval_i[2] = {x, y};
float mval_3d[3];
@ -1454,6 +1457,7 @@ static void gpencil_stroke_soft_refine(bGPDstroke *gps)
/* eraser tool - evaluation per stroke */
static void gpencil_stroke_eraser_dostroke(tGPsdata *p,
bGPDlayer *gpl,
bGPDframe *gpf,
bGPDstroke *gps,
const float mval[2],
@ -1579,9 +1583,9 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p,
* - this assumes that linewidth is irrelevant
*/
if (gpencil_stroke_inside_circle(mval, radius, pc0[0], pc0[1], pc2[0], pc2[1])) {
if ((gpencil_stroke_eraser_is_occluded(p, pt0, pc0[0], pc0[1]) == false) ||
(gpencil_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) ||
(gpencil_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false)) {
if ((gpencil_stroke_eraser_is_occluded(p, gpl, pt0, pc0[0], pc0[1]) == false) ||
(gpencil_stroke_eraser_is_occluded(p, gpl, pt1, pc1[0], pc1[1]) == false) ||
(gpencil_stroke_eraser_is_occluded(p, gpl, pt2, pc2[0], pc2[1]) == false)) {
/* Point is affected: */
/* Adjust thickness
* - Influence of eraser falls off with distance from the middle of the eraser
@ -1683,6 +1687,8 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p,
/* erase strokes which fall under the eraser strokes */
static void gpencil_stroke_doeraser(tGPsdata *p)
{
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(p->gpd);
rcti rect;
Brush *brush = p->brush;
Brush *eraser = p->eraser;
@ -1723,40 +1729,53 @@ static void gpencil_stroke_doeraser(tGPsdata *p)
* on multiple layers...
*/
LISTBASE_FOREACH (bGPDlayer *, gpl, &p->gpd->layers) {
bGPDframe *gpf = gpl->actframe;
/* only affect layer if it's editable (and visible) */
if (BKE_gpencil_layer_is_editable(gpl) == false) {
continue;
}
if (gpf == NULL) {
bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
if (init_gpf == NULL) {
continue;
}
/* calculate difference matrix */
BKE_gpencil_layer_transform_matrix_get(p->depsgraph, p->ob, gpl, p->diff_mat);
/* loop over strokes, checking segments for intersections */
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
/* check if the color is editable */
if (ED_gpencil_stroke_material_editable(p->ob, gpl, gps) == false) {
continue;
}
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
if (gpf == NULL) {
continue;
}
/* calculate difference matrix */
BKE_gpencil_layer_transform_matrix_get(p->depsgraph, p->ob, gpl, p->diff_mat);
/* Check if the stroke collide with mouse. */
if (!ED_gpencil_stroke_check_collision(&p->gsc, gps, p->mval, calc_radius, p->diff_mat)) {
continue;
}
/* loop over strokes, checking segments for intersections */
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
/* check if the color is editable */
if (ED_gpencil_stroke_material_editable(p->ob, gpl, gps) == false) {
continue;
}
/* Not all strokes in the datablock may be valid in the current editor/context
* (e.g. 2D space strokes in the 3D view, if the same datablock is shared)
*/
if (ED_gpencil_stroke_can_use_direct(p->area, gps)) {
gpencil_stroke_eraser_dostroke(p, gpf, gps, p->mval, calc_radius, &rect);
/* Check if the stroke collide with mouse. */
if (!ED_gpencil_stroke_check_collision(
&p->gsc, gps, p->mval, calc_radius, p->diff_mat)) {
continue;
}
/* Not all strokes in the datablock may be valid in the current editor/context
* (e.g. 2D space strokes in the 3D view, if the same datablock is shared)
*/
if (ED_gpencil_stroke_can_use_direct(p->area, gps)) {
gpencil_stroke_eraser_dostroke(p, gpl, gpf, gps, p->mval, calc_radius, &rect);
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
}
/* ******************************************* */
/* Sketching Operator */

View File

@ -1377,6 +1377,12 @@ static void gpencil_primitive_interaction_end(bContext *C,
BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps);
}
/* In Multiframe mode, duplicate the stroke in other frames. */
if (GPENCIL_MULTIEDIT_SESSIONS_ON(tgpi->gpd)) {
const bool tail = (ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK);
BKE_gpencil_stroke_copy_to_keyframes(tgpi->gpd, tgpi->gpl, gpf, gps, tail);
}
DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_COPY_ON_WRITE);
DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);

View File

@ -3032,12 +3032,12 @@ void ED_gpencil_sbuffer_vertex_color_set(Depsgraph *depsgraph,
}
}
/* Helper to get the bigger 2D bound box points. */
static void gpencil_projected_2d_bound_box(GP_SpaceConversion *gsc,
bGPDstroke *gps,
const float diff_mat[4][4],
float r_min[2],
float r_max[2])
/* Get the bigger 2D bound box points. */
void ED_gpencil_projected_2d_bound_box(GP_SpaceConversion *gsc,
bGPDstroke *gps,
const float diff_mat[4][4],
float r_min[2],
float r_max[2])
{
float bounds[8][2];
BoundBox bb;
@ -3082,7 +3082,7 @@ bool ED_gpencil_stroke_check_collision(GP_SpaceConversion *gsc,
BKE_gpencil_stroke_boundingbox_calc(gps);
}
gpencil_projected_2d_bound_box(gsc, gps, diff_mat, boundbox_min, boundbox_max);
ED_gpencil_projected_2d_bound_box(gsc, gps, diff_mat, boundbox_min, boundbox_max);
rcti rect_stroke = {boundbox_min[0], boundbox_max[0], boundbox_min[1], boundbox_max[1]};

View File

@ -370,6 +370,11 @@ bool ED_gpencil_stroke_point_is_inside(struct bGPDstroke *gps,
struct GP_SpaceConversion *gsc,
int mouse[2],
const float diff_mat[4][4]);
void ED_gpencil_projected_2d_bound_box(struct GP_SpaceConversion *gsc,
struct bGPDstroke *gps,
const float diff_mat[4][4],
float r_min[2],
float r_max[2]);
struct bGPDstroke *ED_gpencil_stroke_nearest_to_ends(struct bContext *C,
struct GP_SpaceConversion *gsc,

View File

@ -75,6 +75,10 @@ typedef enum eGPDbrush_Flag {
GP_BRUSH_USE_STRENGTH_PRESSURE = (1 << 1),
/* brush use pressure for alpha factor */
GP_BRUSH_USE_JITTER_PRESSURE = (1 << 2),
/* Disable automatic zoom for filling. */
GP_BRUSH_FILL_FIT_DISABLE = (1 << 3),
/* Show extend fill help lines. */
GP_BRUSH_FILL_SHOW_EXTENDLINES = (1 << 4),
/* fill hide transparent */
GP_BRUSH_FILL_HIDE = (1 << 6),
/* show fill help lines */

View File

@ -47,8 +47,6 @@ typedef struct BrushClone {
char _pad[4];
} BrushClone;
#define GPENCIL_MIN_FILL_FAC 0.05f
typedef struct BrushGpencilSettings {
/** Amount of smoothing to apply to newly created strokes. */
float draw_smoothfac;
@ -133,6 +131,10 @@ typedef struct BrushGpencilSettings {
/** Randomness for Value. */
float random_value;
/** Factor to extend stroke extremes using fill tool. */
float fill_extend_fac;
char _pad3[4];
struct CurveMapping *curve_sensitivity;
struct CurveMapping *curve_strength;
struct CurveMapping *curve_jitter;

View File

@ -45,6 +45,9 @@ struct MDeformVert;
#define GP_DEFAULT_CURVE_ERROR 0.1f
#define GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE M_PI_2
#define GPENCIL_MIN_FILL_FAC 0.05f
#define GPENCIL_MAX_FILL_FAC 5.0f
/* ***************************************** */
/* GP Stroke Points */
@ -809,8 +812,9 @@ typedef enum eGP_DrawMode {
/* Check if 'multiedit sessions' is enabled */
#define GPENCIL_MULTIEDIT_SESSIONS_ON(gpd) \
((gpd) && \
((gpd)->flag & (GP_DATA_STROKE_EDITMODE | GP_DATA_STROKE_SCULPTMODE | \
GP_DATA_STROKE_WEIGHTMODE | GP_DATA_STROKE_VERTEXMODE)) && \
((gpd)->flag & \
(GP_DATA_STROKE_PAINTMODE | GP_DATA_STROKE_EDITMODE | GP_DATA_STROKE_SCULPTMODE | \
GP_DATA_STROKE_WEIGHTMODE | GP_DATA_STROKE_VERTEXMODE)) && \
((gpd)->flag & GP_DATA_STROKE_MULTIEDIT))
#define GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd) \

View File

@ -1466,7 +1466,7 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
/* fill factor size */
prop = RNA_def_property(srna, "fill_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "fill_factor");
RNA_def_property_range(prop, GPENCIL_MIN_FILL_FAC, 8.0f);
RNA_def_property_range(prop, GPENCIL_MIN_FILL_FAC, GPENCIL_MAX_FILL_FAC);
RNA_def_property_ui_text(
prop,
"Precision",
@ -1608,6 +1608,15 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Value", "Random factor to modify original value");
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
/* Factor to extend stroke extremes in Fill tool. */
prop = RNA_def_property(srna, "extend_stroke_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "fill_extend_fac");
RNA_def_property_range(prop, 0.0f, 10.0f);
RNA_def_property_float_default(prop, 0.0f);
RNA_def_property_ui_text(
prop, "Stroke Extension", "Strokes end extension for closing gaps, use zero to disable");
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
/* Flags */
prop = RNA_def_property(srna, "use_pressure", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_USE_PRESSURE);
@ -1827,6 +1836,12 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Show Lines", "Show help lines for filling to see boundaries");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "show_fill_extend", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_FILL_SHOW_EXTENDLINES);
RNA_def_property_boolean_default(prop, true);
RNA_def_property_ui_text(prop, "Show Extend Lines", "Show help lines for stroke extension");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "show_fill", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", GP_BRUSH_FILL_HIDE);
RNA_def_property_boolean_default(prop, true);
@ -1834,6 +1849,15 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
prop, "Show Fill", "Show transparent lines to use as boundary for filling");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "use_fill_autofit", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", GP_BRUSH_FILL_FIT_DISABLE);
RNA_def_property_boolean_default(prop, true);
RNA_def_property_ui_text(
prop,
"Automatic Fit",
"Fit the shape of the stroke to try to fill areas outside visible viewport");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "use_default_eraser", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_DEFAULT_ERASER);
RNA_def_property_boolean_default(prop, true);