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/gpencil_utils.c')
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c272
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;
+}
+
/* ******************************************************** */