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:
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
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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]};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue