diff options
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_utils.c')
-rw-r--r-- | source/blender/editors/gpencil/gpencil_utils.c | 272 |
1 files changed, 270 insertions, 2 deletions
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 0d7aac7f48f..f54da91af71 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -51,7 +51,9 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" +#include "UI_resources.h" #include "UI_view2d.h" #include "ED_gpencil.h" @@ -76,8 +78,6 @@ bGPdata **ED_gpencil_data_get_pointers_direct(ID *screen_id, Scene *scene, ScrAr switch (sa->spacetype) { case SPACE_VIEW3D: /* 3D-View */ - case SPACE_TIME: /* Timeline - XXX: this is a hack to get it to show GP keyframes for 3D view */ - case SPACE_ACTION: /* DepeSheet - XXX: this is a hack to get the keyframe jump operator to take GP Keyframes into account */ { BLI_assert(scene && ELEM(scene->toolsettings->gpencil_src, GP_TOOL_SOURCE_SCENE, GP_TOOL_SOURCE_OBJECT)); @@ -212,6 +212,45 @@ bGPdata *ED_gpencil_data_get_active_v3d(Scene *scene, View3D *v3d) } /* ******************************************************** */ +/* Keyframe Indicator Checks */ + +/* Check whether there's an active GP keyframe on the current frame */ +bool ED_gpencil_has_keyframe_v3d(Scene *scene, Object *ob, int cfra) +{ + /* just check both for now... */ + // XXX: this could get confusing (e.g. if only on the object, but other places don't show this) + if (scene->gpd) { + bGPDlayer *gpl = gpencil_layer_getactive(scene->gpd); + if (gpl) { + if (gpl->actframe) { + // XXX: assumes that frame has been fetched already + return (gpl->actframe->framenum == cfra); + } + else { + /* XXX: disabled as could be too much of a penalty */ + /* return BKE_gpencil_layer_find_frame(gpl, cfra); */ + } + } + } + + if (ob && ob->gpd) { + bGPDlayer *gpl = gpencil_layer_getactive(ob->gpd); + if (gpl) { + if (gpl->actframe) { + // XXX: assumes that frame has been fetched already + return (gpl->actframe->framenum == cfra); + } + else { + /* XXX: disabled as could be too much of a penalty */ + /* return BKE_gpencil_layer_find_frame(gpl, cfra); */ + } + } + } + + return false; +} + +/* ******************************************************** */ /* Poll Callbacks */ /* poll callback for adding data/layers - special */ @@ -231,6 +270,92 @@ int gp_active_layer_poll(bContext *C) } /* ******************************************************** */ +/* Dynamic Enums of GP Layers */ +/* NOTE: These include an option to create a new layer and use that... */ + +/* Just existing layers */ +EnumPropertyItem *ED_gpencil_layers_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDlayer *gpl; + EnumPropertyItem *item = NULL, item_tmp = {0}; + int totitem = 0; + int i = 0; + + if (ELEM(NULL, C, gpd)) { + return DummyRNA_DEFAULT_items; + } + + /* Existing layers */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next, i++) { + item_tmp.identifier = gpl->info; + item_tmp.name = gpl->info; + item_tmp.value = i; + + if (gpl->flag & GP_LAYER_ACTIVE) + item_tmp.icon = ICON_GREASEPENCIL; + else + item_tmp.icon = ICON_NONE; + + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +/* Existing + Option to add/use new layer */ +EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDlayer *gpl; + EnumPropertyItem *item = NULL, item_tmp = {0}; + int totitem = 0; + int i = 0; + + if (ELEM(NULL, C, gpd)) { + return DummyRNA_DEFAULT_items; + } + + /* Create new layer */ + /* TODO: have some way of specifying that we don't want this? */ + { + /* active Keying Set */ + item_tmp.identifier = "__CREATE__"; + item_tmp.name = "New Layer"; + item_tmp.value = -1; + item_tmp.icon = ICON_ZOOMIN; + RNA_enum_item_add(&item, &totitem, &item_tmp); + + /* separator */ + RNA_enum_item_add_separator(&item, &totitem); + } + + /* Existing layers */ + for (gpl = gpd->layers.first, i = 0; gpl; gpl = gpl->next, i++) { + item_tmp.identifier = gpl->info; + item_tmp.name = gpl->info; + item_tmp.value = i; + + if (gpl->flag & GP_LAYER_ACTIVE) + item_tmp.icon = ICON_GREASEPENCIL; + else + item_tmp.icon = ICON_NONE; + + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + + + +/* ******************************************************** */ /* Brush Tool Core */ /* Check if part of stroke occurs within last segment drawn by eraser */ @@ -372,4 +497,147 @@ void gp_point_to_xy(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt, } } +/** + * Project screenspace coordinates to 3D-space + * + * \note We include this as a utility function, since the standard method + * involves quite a few steps, which are invariably always the same + * for all GPencil operations. So, it's nicer to just centralize these. + * + * \warning Assumes that it is getting called in a 3D view only. + */ +bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, Scene *scene, const float screen_co[2], float r_out[3]) +{ + View3D *v3d = gsc->sa->spacedata.first; + RegionView3D *rv3d = gsc->ar->regiondata; + float *rvec = ED_view3d_cursor3d_get(scene, v3d); + float ref[3] = {rvec[0], rvec[1], rvec[2]}; + float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL); + + float mval_f[2], mval_prj[2]; + float dvec[3]; + + copy_v2_v2(mval_f, screen_co); + + if (ED_view3d_project_float_global(gsc->ar, ref, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) { + sub_v2_v2v2(mval_f, mval_prj, mval_f); + ED_view3d_win_to_delta(gsc->ar, mval_f, dvec, zfac); + sub_v3_v3v3(r_out, rvec, dvec); + + return true; + } + else { + zero_v3(r_out); + + return false; + } +} + +/** + * Apply smooth to stroke point + * \param gps Stroke to smooth + * \param i Point index + * \param inf Amount of smoothing to apply + * \param affect_pressure Apply smoothing to pressure values too? + */ +bool gp_smooth_stroke(bGPDstroke *gps, int i, float inf, bool affect_pressure) +{ + bGPDspoint *pt = &gps->points[i]; + float pressure = 0.0f; + float sco[3] = {0.0f}; + + /* Do nothing if not enough points to smooth out */ + if (gps->totpoints <= 2) { + return false; + } + + /* Only affect endpoints by a fraction of the normal strength, + * to prevent the stroke from shrinking too much + */ + if ((i == 0) || (i == gps->totpoints - 1)) { + inf *= 0.1f; + } + + /* Compute smoothed coordinate by taking the ones nearby */ + /* XXX: This is potentially slow, and suffers from accumulation error as earlier points are handled before later ones */ + { + // XXX: this is hardcoded to look at 2 points on either side of the current one (i.e. 5 items total) + const int steps = 2; + const float average_fac = 1.0f / (float)(steps * 2 + 1); + int step; + + /* add the point itself */ + madd_v3_v3fl(sco, &pt->x, average_fac); + + if (affect_pressure) { + pressure += pt->pressure * average_fac; + } + + /* n-steps before/after current point */ + // XXX: review how the endpoints are treated by this algorithm + // XXX: falloff measures should also introduce some weighting variations, so that further-out points get less weight + for (step = 1; step <= steps; step++) { + bGPDspoint *pt1, *pt2; + int before = i - step; + int after = i + step; + + CLAMP_MIN(before, 0); + CLAMP_MAX(after, gps->totpoints - 1); + + pt1 = &gps->points[before]; + pt2 = &gps->points[after]; + + /* add both these points to the average-sum (s += p[i]/n) */ + madd_v3_v3fl(sco, &pt1->x, average_fac); + madd_v3_v3fl(sco, &pt2->x, average_fac); + + /* do pressure too? */ + if (affect_pressure) { + pressure += pt1->pressure * average_fac; + pressure += pt2->pressure * average_fac; + } + } + } + + /* Based on influence factor, blend between original and optimal smoothed coordinate */ + interp_v3_v3v3(&pt->x, &pt->x, sco, inf); + + if (affect_pressure) { + pt->pressure = pressure; + } + + return true; +} + +/** + * Subdivide a stroke once, by adding a point half way between each pair of existing points + * \param gps Stroke data + * \param new_totpoints Total number of points (after subdividing) + */ +void gp_subdivide_stroke(bGPDstroke *gps, const int new_totpoints) +{ + /* Move points towards end of enlarged points array to leave space for new points */ + int y = 1; + for (int i = gps->totpoints - 1; i > 0; i--) { + gps->points[new_totpoints - y] = gps->points[i]; + y += 2; + } + + /* Create interpolated points */ + for (int i = 0; i < new_totpoints - 1; i += 2) { + bGPDspoint *prev = &gps->points[i]; + bGPDspoint *pt = &gps->points[i + 1]; + bGPDspoint *next = &gps->points[i + 2]; + + /* Interpolate all values */ + interp_v3_v3v3(&pt->x, &prev->x, &next->x, 0.5f); + + pt->pressure = interpf(prev->pressure, next->pressure, 0.5f); + pt->time = interpf(prev->time, next->time, 0.5f); + } + + /* Update to new total number of points */ + gps->totpoints = new_totpoints; +} + /* ******************************************************** */ |